You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
435 lines
12 KiB
435 lines
12 KiB
/*
|
|
* divx4_vbr.c
|
|
*
|
|
* Copyright (C) Thomas Oestreich - June 2001
|
|
*
|
|
* 2-pass code OpenDivX port:
|
|
* Copyright (C) 2001 Christoph Lampert <gruel@gmx.de>
|
|
*
|
|
* This file is part of transcode, a video stream processing tool
|
|
*
|
|
* transcode is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* transcode is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with GNU Make; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*
|
|
*/
|
|
|
|
|
|
/**********************************************************
|
|
* Two-pass-code from OpenDivX *
|
|
* *
|
|
* Large parts of this code were taken from VbrControl() *
|
|
* from the OpenDivX project, (C) divxnetworks, *
|
|
* this code is published under DivX Open license, which *
|
|
* can be found... somewhere... oh, whatever... *
|
|
**********************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <math.h>
|
|
#include <stdint.h>
|
|
|
|
#ifdef HAVE_DLFCN_H
|
|
#include <dlfcn.h>
|
|
#else
|
|
# ifdef OS_DARWIN
|
|
# include "libdldarwin/dlfcn.h"
|
|
# endif
|
|
#endif
|
|
|
|
#include "transcode.h"
|
|
#include "libtc/libtc.h"
|
|
#include "vbr.h"
|
|
|
|
/* Absolute maximum and minimum quantizers used in VBR modes */
|
|
static const int min_quantizer=1;
|
|
static const int max_quantizer=31;
|
|
|
|
/* Limits on frame-level deviation of quantizer ( higher values
|
|
correspond to frames with more changes and vice versa ) */
|
|
static const float min_quant_delta=-10.f;
|
|
static const float max_quant_delta=5.f;
|
|
/* Limits on stream-level deviation of quantizer ( used to make
|
|
overall bitrate of stream close to requested value ) */
|
|
static const float min_rc_quant_delta=.6f;
|
|
static const float max_rc_quant_delta=1.5f;
|
|
|
|
typedef struct entry_s {
|
|
/* max 28 bytes/frame or 5 Mb for 2-hour movie */
|
|
int quant;
|
|
int text_bits;
|
|
int motion_bits;
|
|
int total_bits;
|
|
float mult;
|
|
int is_key_frame;
|
|
int drop;
|
|
} entry;
|
|
|
|
int m_iCount;
|
|
int m_iQuant;
|
|
int m_iCrispness;
|
|
short m_bDrop;
|
|
float m_fQuant;
|
|
|
|
int64_t m_lEncodedBits;
|
|
int64_t m_lExpectedBits;
|
|
|
|
FILE *m_pFile;
|
|
|
|
entry vFrame;
|
|
entry *m_vFrames;
|
|
long lFrameStart;
|
|
|
|
int iNumFrames;
|
|
int dummy;
|
|
|
|
void VbrControl_init_1pass_vbr(int quality, int crispness)
|
|
{
|
|
m_fQuant = min_quantizer+((max_quantizer-min_quantizer)/6.)*(6-quality);
|
|
m_iCount = 0;
|
|
m_bDrop = TC_FALSE;
|
|
VbrControl_update_1pass_vbr();
|
|
}
|
|
|
|
int VbrControl_init_2pass_vbr_analysis(const char *filename, int quality)
|
|
{
|
|
m_pFile = fopen(filename, "wb");
|
|
if (m_pFile == NULL) {
|
|
return -1;
|
|
}
|
|
m_iCount = 0;
|
|
m_bDrop = TC_FALSE;
|
|
fprintf(m_pFile, "##version 1\n");
|
|
fprintf(m_pFile, "quality %d\n", quality);
|
|
return 0;
|
|
}
|
|
|
|
int VbrControl_init_2pass_vbr_encoding(const char *filename, int bitrate,
|
|
double framerate, int crispness,
|
|
int quality)
|
|
{
|
|
int i;
|
|
|
|
int64_t text_bits = 0;
|
|
int64_t total_bits = 0;
|
|
int64_t complexity = 0;
|
|
int64_t new_complexity = 0;
|
|
int64_t motion_bits = 0;
|
|
int64_t denominator = 0;
|
|
float qual_multiplier = 1.;
|
|
char head[20];
|
|
|
|
int64_t desired_bits;
|
|
int64_t non_text_bits;
|
|
|
|
float average_complexity;
|
|
|
|
m_pFile = fopen(filename, "rb");
|
|
if (m_pFile == NULL) {
|
|
return -1;
|
|
}
|
|
m_bDrop = TC_FALSE;
|
|
m_iCount = 0;
|
|
|
|
fread(head, 10, 1, m_pFile);
|
|
if (strncmp("##version ", head, 10) == 0) {
|
|
int version;
|
|
int iOldQual;
|
|
float old_qual = 0, new_qual = 0;
|
|
|
|
fscanf(m_pFile, "%d\n", &version);
|
|
fscanf(m_pFile, "quality %d\n", &iOldQual);
|
|
switch (iOldQual) {
|
|
case 5:
|
|
old_qual = 1.f;
|
|
break;
|
|
case 4:
|
|
old_qual = 1.1f;
|
|
break;
|
|
case 3:
|
|
old_qual = 1.25f;
|
|
break;
|
|
case 2:
|
|
old_qual = 1.4f;
|
|
break;
|
|
case 1:
|
|
old_qual = 2.f;
|
|
break;
|
|
}
|
|
|
|
switch (quality) {
|
|
case 5:
|
|
new_qual = 1.f;
|
|
break;
|
|
case 4:
|
|
new_qual = 1.1f;
|
|
break;
|
|
case 3:
|
|
new_qual = 1.25f;
|
|
break;
|
|
case 2:
|
|
new_qual = 1.4f;
|
|
break;
|
|
case 1:
|
|
new_qual = 2.f;
|
|
break;
|
|
}
|
|
qual_multiplier = new_qual/old_qual;
|
|
} else { /* strncmp("##version ", head, 10) != 0 */
|
|
fseek(m_pFile, 0, SEEK_SET);
|
|
}
|
|
|
|
lFrameStart = ftell(m_pFile); // save current position
|
|
|
|
/* removed C++ dependencies, now read file twice :-( */
|
|
|
|
while (!feof(m_pFile)) {
|
|
fscanf(m_pFile, "Frame %d: intra %d, quant %d, texture %d, "
|
|
"motion %d, total %d\n",
|
|
&iNumFrames, (int *) &(vFrame.is_key_frame),
|
|
&(vFrame.quant), &(vFrame.text_bits),
|
|
&(vFrame.motion_bits), &(vFrame.total_bits));
|
|
|
|
vFrame.total_bits += vFrame.text_bits*(qual_multiplier-1);
|
|
vFrame.text_bits *= qual_multiplier;
|
|
text_bits += (int64_t)vFrame.text_bits;
|
|
motion_bits += (int64_t)vFrame.motion_bits;
|
|
total_bits += (int64_t)vFrame.total_bits;
|
|
complexity += (int64_t)vFrame.text_bits*vFrame.quant;
|
|
|
|
}
|
|
iNumFrames++;
|
|
average_complexity = complexity/iNumFrames;
|
|
|
|
if (verbose & TC_DEBUG) {
|
|
tc_log_info(__FILE__, "frames %d, texture %lld, motion %lld, "
|
|
"total %lld, complexity %lld",
|
|
iNumFrames, (long long)text_bits, (long long)motion_bits,
|
|
(long long)total_bits, (long long)complexity);
|
|
}
|
|
|
|
m_vFrames = tc_malloc(iNumFrames*sizeof(entry));
|
|
if (!m_vFrames) {
|
|
return TC_EXPORT_ERROR;
|
|
}
|
|
|
|
fseek(m_pFile, lFrameStart, SEEK_SET); /* start again */
|
|
|
|
for (i = 0; i < iNumFrames; i++) {
|
|
fscanf(m_pFile, "Frame %d: intra %d, quant %d, texture %d, "
|
|
"motion %d, total %d\n",
|
|
&dummy, (int *) &(m_vFrames[i].is_key_frame),
|
|
&(m_vFrames[i].quant), &(m_vFrames[i].text_bits),
|
|
&(m_vFrames[i].motion_bits), &(m_vFrames[i].total_bits));
|
|
|
|
m_vFrames[i].total_bits += m_vFrames[i].text_bits*(qual_multiplier-1);
|
|
m_vFrames[i].text_bits *= qual_multiplier;
|
|
}
|
|
|
|
if (m_pFile != NULL) {
|
|
fclose(m_pFile);
|
|
m_pFile=NULL;
|
|
}
|
|
|
|
desired_bits = (int64_t)bitrate*(int64_t)iNumFrames/framerate;
|
|
non_text_bits = total_bits-text_bits;
|
|
|
|
if (desired_bits <= non_text_bits) {
|
|
tc_log_warn(__FILE__, "Specified bitrate is too low for this clip.\n"
|
|
"Minimum possible bitrate for the clip is %.0f"
|
|
" kbps. Overriding user-specified value.\n",
|
|
(float)(non_text_bits*framerate/(int64_t)iNumFrames));
|
|
|
|
desired_bits=non_text_bits*3/2;
|
|
}
|
|
|
|
desired_bits -= non_text_bits;
|
|
/**
|
|
BRIEF EXPLANATION OF WHAT'S GOING ON HERE.
|
|
We assume that
|
|
text_bits=complexity / quantizer
|
|
total_bits-text_bits = const(complexity)
|
|
where 'complexity' is a characteristic of the frame
|
|
and does not depend much on quantizer dynamics.
|
|
Using this equation, we calculate 'average' quantizer
|
|
to be used for encoding ( 1st order effect ).
|
|
Having constant quantizer for the entire stream is not
|
|
very convenient - reconstruction errors are
|
|
more noticeable in low-motion scenes. To compensate
|
|
this effect, we multiply quantizer for each frame by
|
|
(complexity/average_complexity)^k,
|
|
( k - parameter of adjustment ). k=0 means 'no compensation'
|
|
and k=1 is 'constant bitrate mode'. We choose something in
|
|
between, like 0.5 ( 2nd order effect ).
|
|
**/
|
|
|
|
average_complexity = complexity/iNumFrames;
|
|
|
|
for (i = 0; i < iNumFrames; i++) {
|
|
float mult;
|
|
if (m_vFrames[i].is_key_frame) {
|
|
if ((i+1<iNumFrames) && (m_vFrames[i+1].is_key_frame))
|
|
mult=1.25;
|
|
else
|
|
mult=.75;
|
|
} else {
|
|
mult = m_vFrames[i].text_bits*m_vFrames[i].quant;
|
|
mult = (float)sqrt(mult/average_complexity);
|
|
|
|
if (mult<0.5)
|
|
mult=0.5;
|
|
if (mult>1.5)
|
|
mult=1.5;
|
|
}
|
|
|
|
m_vFrames[i].mult = mult;
|
|
m_vFrames[i].drop = TC_FALSE;
|
|
new_complexity += m_vFrames[i].text_bits*m_vFrames[i].quant;
|
|
|
|
denominator += desired_bits*m_vFrames[i].mult/iNumFrames;
|
|
}
|
|
|
|
m_fQuant = ((double)new_complexity)/(double)denominator;
|
|
|
|
if (m_fQuant < min_quantizer)
|
|
m_fQuant=min_quantizer;
|
|
if (m_fQuant > max_quantizer)
|
|
m_fQuant=max_quantizer;
|
|
m_pFile = fopen("analyse.log", "wb");
|
|
if (m_pFile) {
|
|
fprintf(m_pFile, "Total frames: %d Avg quantizer: %f\n",
|
|
iNumFrames, m_fQuant);
|
|
fprintf(m_pFile, "Expecting %12lld bits\n",
|
|
(long long)desired_bits+(long long)non_text_bits);
|
|
fflush(m_pFile);
|
|
}
|
|
VbrControl_set_quant(m_fQuant*m_vFrames[0].mult);
|
|
m_lEncodedBits = m_lExpectedBits=0;
|
|
return 0;
|
|
}
|
|
|
|
int VbrControl_get_intra()
|
|
{
|
|
return m_vFrames[m_iCount].is_key_frame;
|
|
}
|
|
|
|
short VbrControl_get_drop()
|
|
{
|
|
return m_bDrop;
|
|
}
|
|
|
|
int VbrControl_get_quant()
|
|
{
|
|
return m_iQuant;
|
|
}
|
|
|
|
void VbrControl_set_quant(float quant)
|
|
{
|
|
m_iQuant=quant;
|
|
if((rand() % 10) < ((quant-m_iQuant) * 10))
|
|
m_iQuant++;
|
|
if(m_iQuant<min_quantizer)
|
|
m_iQuant=min_quantizer;
|
|
if(m_iQuant>max_quantizer)
|
|
m_iQuant=max_quantizer;
|
|
}
|
|
|
|
void VbrControl_update_1pass_vbr()
|
|
{
|
|
VbrControl_set_quant(m_fQuant);
|
|
m_iCount++;
|
|
}
|
|
|
|
void VbrControl_update_2pass_vbr_analysis(int is_key_frame, int motion_bits,
|
|
int texture_bits, int total_bits,
|
|
int quant)
|
|
{
|
|
if(m_pFile != NULL) {
|
|
fprintf(m_pFile, "Frame %d: intra %d, quant %d, texture %d, "
|
|
"motion %d, total %d\n",
|
|
m_iCount, is_key_frame, quant, texture_bits, motion_bits,
|
|
total_bits);
|
|
m_iCount++;
|
|
}
|
|
}
|
|
|
|
void VbrControl_update_2pass_vbr_encoding(int motion_bits, int texture_bits,
|
|
int total_bits)
|
|
{
|
|
double q;
|
|
double dq;
|
|
|
|
if(m_iCount >= iNumFrames) {
|
|
return;
|
|
}
|
|
|
|
m_lExpectedBits += (m_vFrames[m_iCount].total_bits - m_vFrames[m_iCount].text_bits)
|
|
+ m_vFrames[m_iCount].text_bits*m_vFrames[m_iCount].quant/m_fQuant;
|
|
m_lEncodedBits += (int64_t)total_bits;
|
|
|
|
if (m_pFile != NULL) {
|
|
fprintf(m_pFile, "Frame %d: PRESENT, complexity %d, "
|
|
"quant multiplier %f, texture %d, total %d ",
|
|
m_iCount, m_vFrames[m_iCount].text_bits * m_vFrames[m_iCount].quant,
|
|
m_vFrames[m_iCount].mult, texture_bits, total_bits);
|
|
}
|
|
m_iCount++;
|
|
|
|
q = m_fQuant * m_vFrames[m_iCount].mult;
|
|
if (q < m_fQuant + min_quant_delta) {
|
|
q = m_fQuant+min_quant_delta;
|
|
}
|
|
if (q > m_fQuant + max_quant_delta) {
|
|
q = m_fQuant+max_quant_delta;
|
|
}
|
|
|
|
dq = (double)m_lEncodedBits/(double)m_lExpectedBits;
|
|
dq *= dq;
|
|
if (dq < min_rc_quant_delta) {
|
|
dq = min_rc_quant_delta;
|
|
}
|
|
if (dq > max_rc_quant_delta) {
|
|
dq = max_rc_quant_delta;
|
|
}
|
|
if (m_iCount < 20) { // no framerate corrections in first frames
|
|
dq = 1;
|
|
}
|
|
if (m_pFile) {
|
|
fprintf(m_pFile, "Progress: expected %12lld, achieved %12lld, dq %f",
|
|
(long long)m_lExpectedBits,
|
|
(long long)m_lEncodedBits,
|
|
dq);
|
|
}
|
|
q *= dq;
|
|
VbrControl_set_quant(q);
|
|
if (m_pFile != NULL) {
|
|
fprintf(m_pFile, ", new quant %d\n", m_iQuant);
|
|
}
|
|
}
|
|
|
|
void VbrControl_close()
|
|
{
|
|
if (m_pFile != NULL) {
|
|
fclose(m_pFile);
|
|
m_pFile = NULL;
|
|
}
|
|
free(m_vFrames);
|
|
}
|