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

/*
* 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);
}