/* * divx4_vbr.c * * Copyright (C) Thomas Oestreich - June 2001 * * 2-pass code OpenDivX port: * Copyright (C) 2001 Christoph Lampert * * 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 #include #include #include #include #include #include #ifdef HAVE_DLFCN_H #include #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+11.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_iQuantmax_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); }