Move firstpass motion map to stats packet

The first implementation of the firstpass motion map for motion
compensated temporal filtering created a file, fpmotionmap.stt,
in the current working directory. This was not safe for multiple
encoder instances. This patch merges this data into the first pass
stats packet interface, so that it is handled like the other
(numerical) firstpass stats.

The new stats packet is defined as follows:
    Numerical Stats (16 doubles) -- 128 bytes
    Motion Map                   -- 1 byte / Macroblock
    Padding                      -- to align packet to 8 bytes

The fpmotionmap.stt file can still be generated for debugging
purposes in the same way that the textual version of the stats
are available (defining OUTPUT_FPF in firstpass.c)

Change-Id: I083ffbfd95e7d6a42bb4039ba0e81f678c8183ca
This commit is contained in:
John Koleszar 2010-10-14 16:40:12 -04:00
Родитель bdf469c91e
Коммит bb7dd5b1ba
5 изменённых файлов: 115 добавлений и 119 удалений

Просмотреть файл

@ -30,7 +30,6 @@
#include "encodemv.h"
//#define OUTPUT_FPF 1
#define FIRSTPASS_MM 1
#if CONFIG_RUNTIME_CPU_DETECT
#define IF_RTCD(x) (x)
@ -108,15 +107,6 @@ static void reset_fpf_position(VP8_COMP *cpi, FIRSTPASS_STATS *Position)
static int lookup_next_frame_stats(VP8_COMP *cpi, FIRSTPASS_STATS *next_frame)
{
/*FIRSTPASS_STATS * start_pos;
int ret_val;
start_pos = cpi->stats_in;
ret_val = vp8_input_stats(cpi, next_frame);
reset_fpf_position(cpi, start_pos);
return ret_val;*/
if (cpi->stats_in >= cpi->stats_in_end)
return EOF;
@ -127,7 +117,7 @@ static int lookup_next_frame_stats(VP8_COMP *cpi, FIRSTPASS_STATS *next_frame)
// Calculate a modified Error used in distributing bits between easier and harder frames
static double calculate_modified_err(VP8_COMP *cpi, FIRSTPASS_STATS *this_frame)
{
double av_err = cpi->total_stats.ssim_weighted_pred_err;
double av_err = cpi->total_stats->ssim_weighted_pred_err;
double this_err = this_frame->ssim_weighted_pred_err;
double modified_err;
@ -238,7 +228,7 @@ int frame_max_bits(VP8_COMP *cpi)
else
{
// For VBR base this on the bits and frames left plus the two_pass_vbrmax_section rate passed in by the user
max_bits = (int)(((double)cpi->bits_left / (cpi->total_stats.count - (double)cpi->common.current_video_frame)) * ((double)cpi->oxcf.two_pass_vbrmax_section / 100.0));
max_bits = (int)(((double)cpi->bits_left / (cpi->total_stats->count - (double)cpi->common.current_video_frame)) * ((double)cpi->oxcf.two_pass_vbrmax_section / 100.0));
}
// Trap case where we are out of bits
@ -248,13 +238,31 @@ int frame_max_bits(VP8_COMP *cpi)
return max_bits;
}
void vp8_output_stats(struct vpx_codec_pkt_list *pktlist,
extern size_t vp8_firstpass_stats_sz(unsigned int mb_count)
{
/* Calculate the size of a stats packet, which is dependent on the frame
* resolution. The FIRSTPASS_STATS struct has a single element array,
* motion_map, which is virtually expanded to have one element per
* macroblock.
*/
size_t stats_sz;
FIRSTPASS_STATS stats;
stats_sz = sizeof(FIRSTPASS_STATS) + mb_count;
stats_sz = (stats_sz + 7) & ~7;
return stats_sz;
}
void vp8_output_stats(const VP8_COMP *cpi,
struct vpx_codec_pkt_list *pktlist,
FIRSTPASS_STATS *stats)
{
struct vpx_codec_cx_pkt pkt;
pkt.kind = VPX_CODEC_STATS_PKT;
pkt.data.twopass_stats.buf = stats;
pkt.data.twopass_stats.sz = sizeof(*stats);
pkt.data.twopass_stats.sz = vp8_firstpass_stats_sz(cpi->common.MBs);
vpx_codec_pkt_list_add(pktlist, &pkt);
// TEMP debug code
@ -280,16 +288,24 @@ void vp8_output_stats(struct vpx_codec_pkt_list *pktlist,
stats->mv_in_out_count,
stats->count);
fclose(fpfile);
fpfile = fopen("fpmotionmap.stt", "a");
fwrite(cpi->fp_motion_map, 1, cpi->common.MBs, fpfile);
fclose(fpfile);
}
#endif
}
int vp8_input_stats(VP8_COMP *cpi, FIRSTPASS_STATS *fps)
{
size_t stats_sz = vp8_firstpass_stats_sz(cpi->common.MBs);
if (cpi->stats_in >= cpi->stats_in_end)
return EOF;
*fps = *cpi->stats_in++;
*fps = *cpi->stats_in;
cpi->stats_in = (void*)((char *)cpi->stats_in + stats_sz);
return 1;
}
@ -352,59 +368,47 @@ void vp8_avg_stats(FIRSTPASS_STATS *section)
section->duration /= section->count;
}
int vp8_fpmm_get_pos(VP8_COMP *cpi)
unsigned char *vp8_fpmm_get_pos(VP8_COMP *cpi)
{
return ftell(cpi->fp_motion_mapfile);
return cpi->fp_motion_map_stats;
}
void vp8_fpmm_reset_pos(VP8_COMP *cpi, int target_pos)
void vp8_fpmm_reset_pos(VP8_COMP *cpi, unsigned char *target_pos)
{
int Offset;
if (cpi->fp_motion_mapfile)
{
Offset = ftell(cpi->fp_motion_mapfile) - target_pos;
fseek(cpi->fp_motion_mapfile, (int) - Offset, SEEK_CUR);
}
cpi->fp_motion_map_stats = target_pos;
}
void vp8_advance_fpmm(VP8_COMP *cpi, int count)
{
#if FIRSTPASS_MM
fseek(cpi->fp_motion_mapfile, (int)(count * cpi->common.MBs), SEEK_CUR);
#endif
cpi->fp_motion_map_stats = (void*)((char*)cpi->fp_motion_map_stats +
count * vp8_firstpass_stats_sz(cpi->common.MBs));
}
void vp8_input_fpmm(VP8_COMP *cpi)
{
#if FIRSTPASS_MM
unsigned char *fpmm = cpi->fp_motion_map;
int MBs = cpi->common.MBs;
int max_frames = cpi->active_arnr_frames;
int i;
if (!cpi->fp_motion_mapfile)
return; // Error
// Read the specified number of frame motion maps
if (fread(cpi->fp_motion_map, 1,
max_frames * MBs,
cpi->fp_motion_mapfile) != max_frames*MBs)
for (i=0; i<max_frames; i++)
{
// Read error
return;
char *motion_map = (char*)cpi->fp_motion_map_stats
+ sizeof(FIRSTPASS_STATS);
memcpy(fpmm, motion_map, MBs);
fpmm += MBs;
vp8_advance_fpmm(cpi, 1);
}
// Flag the use of weights in the temporal filter
cpi->use_weighted_temporal_filter = 1;
#endif
}
void vp8_init_first_pass(VP8_COMP *cpi)
{
vp8_zero_stats(&cpi->total_stats);
#ifdef FIRSTPASS_MM
cpi->fp_motion_mapfile = fopen("fpmotionmap.stt", "wb");
#endif
vp8_zero_stats(cpi->total_stats);
// TEMP debug code
#ifdef OUTPUT_FPF
@ -412,6 +416,8 @@ void vp8_init_first_pass(VP8_COMP *cpi)
FILE *fpfile;
fpfile = fopen("firstpass.stt", "w");
fclose(fpfile);
fpfile = fopen("fpmotionmap.stt", "wb");
fclose(fpfile);
}
#endif
@ -419,16 +425,10 @@ void vp8_init_first_pass(VP8_COMP *cpi)
void vp8_end_first_pass(VP8_COMP *cpi)
{
vp8_output_stats(cpi->output_pkt_list, &cpi->total_stats);
#if FIRSTPASS_MM
if (cpi->fp_motion_mapfile)
fclose(cpi->fp_motion_mapfile);
#endif
vp8_output_stats(cpi, cpi->output_pkt_list, cpi->total_stats);
}
void vp8_zz_motion_search( VP8_COMP *cpi, MACROBLOCK * x, YV12_BUFFER_CONFIG * recon_buffer, int * best_motion_err, int recon_yoffset )
{
MACROBLOCKD * const xd = & x->e_mbd;
@ -839,19 +839,20 @@ void vp8_first_pass(VP8_COMP *cpi)
fps.duration = cpi->source_end_time_stamp - cpi->source_time_stamp;
// don't want to do outputstats with a stack variable!
cpi->this_frame_stats = fps;
vp8_output_stats(cpi->output_pkt_list, &cpi->this_frame_stats);
vp8_accumulate_stats(&cpi->total_stats, &fps);
#if FIRSTPASS_MM
fwrite(cpi->fp_motion_map, 1, cpi->common.MBs, cpi->fp_motion_mapfile);
#endif
memcpy(cpi->this_frame_stats,
&fps,
sizeof(FIRSTPASS_STATS));
memcpy((char*)cpi->this_frame_stats + sizeof(FIRSTPASS_STATS),
cpi->fp_motion_map,
sizeof(cpi->fp_motion_map[0]) * cpi->common.MBs);
vp8_output_stats(cpi, cpi->output_pkt_list, cpi->this_frame_stats);
vp8_accumulate_stats(cpi->total_stats, &fps);
}
// Copy the previous Last Frame into the GF buffer if specific conditions for doing so are met
if ((cm->current_video_frame > 0) &&
(cpi->this_frame_stats.pcnt_inter > 0.20) &&
((cpi->this_frame_stats.intra_error / cpi->this_frame_stats.coded_error) > 2.0))
(cpi->this_frame_stats->pcnt_inter > 0.20) &&
((cpi->this_frame_stats->intra_error / cpi->this_frame_stats->coded_error) > 2.0))
{
vp8_yv12_copy_frame_ptr(lst_yv12, gld_yv12);
}
@ -1120,33 +1121,33 @@ void vp8_init_second_pass(VP8_COMP *cpi)
double two_pass_min_rate = (double)(cpi->oxcf.target_bandwidth * cpi->oxcf.two_pass_vbrmin_section / 100);
vp8_zero_stats(&cpi->total_stats);
vp8_zero_stats(cpi->total_stats);
if (!cpi->stats_in_end)
return;
cpi->total_stats = *cpi->stats_in_end;
*cpi->total_stats = *cpi->stats_in_end;
cpi->total_error_left = cpi->total_stats.ssim_weighted_pred_err;
cpi->total_intra_error_left = cpi->total_stats.intra_error;
cpi->total_coded_error_left = cpi->total_stats.coded_error;
cpi->total_error_left = cpi->total_stats->ssim_weighted_pred_err;
cpi->total_intra_error_left = cpi->total_stats->intra_error;
cpi->total_coded_error_left = cpi->total_stats->coded_error;
cpi->start_tot_err_left = cpi->total_error_left;
//cpi->bits_left = (long long)(cpi->total_stats.count * cpi->oxcf.target_bandwidth / DOUBLE_DIVIDE_CHECK((double)cpi->oxcf.frame_rate));
//cpi->bits_left -= (long long)(cpi->total_stats.count * two_pass_min_rate / DOUBLE_DIVIDE_CHECK((double)cpi->oxcf.frame_rate));
//cpi->bits_left = (long long)(cpi->total_stats->count * cpi->oxcf.target_bandwidth / DOUBLE_DIVIDE_CHECK((double)cpi->oxcf.frame_rate));
//cpi->bits_left -= (long long)(cpi->total_stats->count * two_pass_min_rate / DOUBLE_DIVIDE_CHECK((double)cpi->oxcf.frame_rate));
// each frame can have a different duration, as the frame rate in the source
// isn't guaranteed to be constant. The frame rate prior to the first frame
// encoded in the second pass is a guess. However the sum duration is not.
// Its calculated based on the actual durations of all frames from the first
// pass.
vp8_new_frame_rate(cpi, 10000000.0 * cpi->total_stats.count / cpi->total_stats.duration);
vp8_new_frame_rate(cpi, 10000000.0 * cpi->total_stats->count / cpi->total_stats->duration);
cpi->output_frame_rate = cpi->oxcf.frame_rate;
cpi->bits_left = (long long)(cpi->total_stats.duration * cpi->oxcf.target_bandwidth / 10000000.0) ;
cpi->bits_left -= (long long)(cpi->total_stats.duration * two_pass_min_rate / 10000000.0);
cpi->bits_left = (long long)(cpi->total_stats->duration * cpi->oxcf.target_bandwidth / 10000000.0) ;
cpi->bits_left -= (long long)(cpi->total_stats->duration * two_pass_min_rate / 10000000.0);
vp8_avg_stats(&cpi->total_stats);
vp8_avg_stats(cpi->total_stats);
// Scan the first pass file and calculate an average Intra / Inter error score ratio for the sequence
{
@ -1162,7 +1163,7 @@ void vp8_init_second_pass(VP8_COMP *cpi)
sum_iiratio += IIRatio;
}
cpi->avg_iiratio = sum_iiratio / DOUBLE_DIVIDE_CHECK((double)cpi->total_stats.count);
cpi->avg_iiratio = sum_iiratio / DOUBLE_DIVIDE_CHECK((double)cpi->total_stats->count);
// Reset file position
reset_fpf_position(cpi, start_pos);
@ -1184,21 +1185,11 @@ void vp8_init_second_pass(VP8_COMP *cpi)
}
#if FIRSTPASS_MM
cpi->fp_motion_mapfile = 0;
cpi->fp_motion_mapfile = fopen("fpmotionmap.stt", "rb");
#endif
cpi->fp_motion_map_stats = (unsigned char *)cpi->stats_in;
}
void vp8_end_second_pass(VP8_COMP *cpi)
{
#if FIRSTPASS_MM
if (cpi->fp_motion_mapfile)
fclose(cpi->fp_motion_mapfile);
#endif
}
// Analyse and define a gf/arf group .
@ -1231,18 +1222,14 @@ static void define_gf_group(VP8_COMP *cpi, FIRSTPASS_STATS *this_frame)
int max_bits = frame_max_bits(cpi); // Max for a single frame
#if FIRSTPASS_MM
int fpmm_pos;
#endif
unsigned char *fpmm_pos;
cpi->gf_group_bits = 0;
cpi->gf_decay_rate = 0;
vp8_clear_system_state(); //__asm emms;
#if FIRSTPASS_MM
fpmm_pos = vp8_fpmm_get_pos(cpi);
#endif
start_pos = cpi->stats_in;
@ -1494,7 +1481,7 @@ static void define_gf_group(VP8_COMP *cpi, FIRSTPASS_STATS *this_frame)
// Note: this_frame->frame has been updated in the loop
// so it now points at the ARF frame.
half_gf_int = cpi->baseline_gf_interval >> 1;
frames_after_arf = cpi->total_stats.count - this_frame->frame - 1;
frames_after_arf = cpi->total_stats->count - this_frame->frame - 1;
switch (cpi->oxcf.arnr_type)
{
@ -1531,12 +1518,11 @@ static void define_gf_group(VP8_COMP *cpi, FIRSTPASS_STATS *this_frame)
cpi->active_arnr_frames = frames_bwd + 1 + frames_fwd;
#if FIRSTPASS_MM
{
// Advance to & read in the motion map for those frames
// to be considered for filtering based on the position
// of the ARF
vp8_fpmm_reset_pos(cpi, cpi->fpmm_pos);
vp8_fpmm_reset_pos(cpi, cpi->fp_motion_map_stats_save);
// Position at the 'earliest' frame to be filtered
vp8_advance_fpmm(cpi,
@ -1545,7 +1531,6 @@ static void define_gf_group(VP8_COMP *cpi, FIRSTPASS_STATS *this_frame)
// Read / create a motion map for the region of interest
vp8_input_fpmm(cpi);
}
#endif
}
else
{
@ -1581,7 +1566,7 @@ static void define_gf_group(VP8_COMP *cpi, FIRSTPASS_STATS *this_frame)
// Now decide how many bits should be allocated to the GF group as a proportion of those remaining in the kf group.
// The final key frame group in the clip is treated as a special case where cpi->kf_group_bits is tied to cpi->bits_left.
// This is also important for short clips where there may only be one key frame.
if (cpi->frames_to_key >= (int)(cpi->total_stats.count - cpi->common.current_video_frame))
if (cpi->frames_to_key >= (int)(cpi->total_stats->count - cpi->common.current_video_frame))
{
cpi->kf_group_bits = (cpi->bits_left > 0) ? cpi->bits_left : 0;
}
@ -1781,10 +1766,8 @@ static void define_gf_group(VP8_COMP *cpi, FIRSTPASS_STATS *this_frame)
reset_fpf_position(cpi, start_pos);
}
#if FIRSTPASS_MM
// Reset the First pass motion map file position
vp8_fpmm_reset_pos(cpi, fpmm_pos);
#endif
}
// Allocate bits to a normal frame that is neither a gf an arf or a key frame.
@ -1798,7 +1781,7 @@ static void assign_std_frame_bits(VP8_COMP *cpi, FIRSTPASS_STATS *this_frame)
int max_bits = frame_max_bits(cpi); // Max for a single frame
// The final few frames have special treatment
if (cpi->frames_till_gf_update_due >= (int)(cpi->total_stats.count - cpi->common.current_video_frame))
if (cpi->frames_till_gf_update_due >= (int)(cpi->total_stats->count - cpi->common.current_video_frame))
{
cpi->gf_group_bits = (cpi->bits_left > 0) ? cpi->bits_left : 0;;
}
@ -1843,7 +1826,7 @@ static void assign_std_frame_bits(VP8_COMP *cpi, FIRSTPASS_STATS *this_frame)
void vp8_second_pass(VP8_COMP *cpi)
{
int tmp_q;
int frames_left = (int)(cpi->total_stats.count - cpi->common.current_video_frame);
int frames_left = (int)(cpi->total_stats->count - cpi->common.current_video_frame);
FIRSTPASS_STATS this_frame;
FIRSTPASS_STATS this_frame_copy;
@ -1866,14 +1849,12 @@ void vp8_second_pass(VP8_COMP *cpi)
if (EOF == vp8_input_stats(cpi, &this_frame))
return;
#if FIRSTPASS_MM
vpx_memset(cpi->fp_motion_map, 0,
cpi->oxcf.arnr_max_frames*cpi->common.MBs);
cpi->fpmm_pos = vp8_fpmm_get_pos(cpi);
cpi->fp_motion_map_stats_save = vp8_fpmm_get_pos(cpi);
// Step over this frame's first pass motion map
vp8_advance_fpmm(cpi, 1);
#endif
this_frame_error = this_frame.ssim_weighted_pred_err;
this_frame_intra_error = this_frame.intra_error;
@ -2562,7 +2543,7 @@ void vp8_find_next_key_frame(VP8_COMP *cpi, FIRSTPASS_STATS *this_frame)
cpi->common.vert_scale = NORMAL;
// Calculate Average bits per frame.
//av_bits_per_frame = cpi->bits_left/(double)(cpi->total_stats.count - cpi->common.current_video_frame);
//av_bits_per_frame = cpi->bits_left/(double)(cpi->total_stats->count - cpi->common.current_video_frame);
av_bits_per_frame = cpi->oxcf.target_bandwidth / DOUBLE_DIVIDE_CHECK((double)cpi->oxcf.frame_rate);
//if ( av_bits_per_frame < 0.0 )
// av_bits_per_frame = 0.0
@ -2625,7 +2606,7 @@ void vp8_find_next_key_frame(VP8_COMP *cpi, FIRSTPASS_STATS *this_frame)
}
else
{
long long clip_bits = (long long)(cpi->total_stats.count * cpi->oxcf.target_bandwidth / DOUBLE_DIVIDE_CHECK((double)cpi->oxcf.frame_rate));
long long clip_bits = (long long)(cpi->total_stats->count * cpi->oxcf.target_bandwidth / DOUBLE_DIVIDE_CHECK((double)cpi->oxcf.frame_rate));
long long over_spend = cpi->oxcf.starting_buffer_level - cpi->buffer_level;
long long over_spend2 = cpi->oxcf.starting_buffer_level - projected_buffer_level;

Просмотреть файл

@ -20,4 +20,5 @@ extern void vp8_init_second_pass(VP8_COMP *cpi);
extern void vp8_second_pass(VP8_COMP *cpi);
extern void vp8_end_second_pass(VP8_COMP *cpi);
extern size_t vp8_firstpass_stats_sz(unsigned int mb_count);
#endif

Просмотреть файл

@ -330,6 +330,8 @@ void vp8_dealloc_compressor_data(VP8_COMP *cpi)
cpi->mb.pip = 0;
vpx_free(cpi->total_stats);
vpx_free(cpi->this_frame_stats);
}
static void enable_segmentation(VP8_PTR ptr)
@ -1392,6 +1394,12 @@ void vp8_alloc_compressor_data(VP8_COMP *cpi)
CHECK_MEM_ERROR(cpi->gf_active_flags, vpx_calloc(1, cm->mb_rows * cm->mb_cols));
cpi->gf_active_count = cm->mb_rows * cm->mb_cols;
cpi->total_stats = vpx_calloc(1, vp8_firstpass_stats_sz(cpi->common.MBs));
cpi->this_frame_stats = vpx_calloc(1, vp8_firstpass_stats_sz(cpi->common.MBs));
if(!cpi->total_stats || !cpi->this_frame_stats)
vpx_internal_error(&cpi->common.error, VPX_CODEC_MEM_ERROR,
"Failed to allocate firstpass stats");
}
@ -2290,10 +2298,12 @@ VP8_PTR vp8_create_compressor(VP8_CONFIG *oxcf)
}
else if (cpi->pass == 2)
{
size_t packet_sz = vp8_firstpass_stats_sz(cpi->common.MBs);
int packets = oxcf->two_pass_stats_in.sz / packet_sz;
cpi->stats_in = oxcf->two_pass_stats_in.buf;
cpi->stats_in_end = cpi->stats_in
+ oxcf->two_pass_stats_in.sz / sizeof(FIRSTPASS_STATS)
- 1;
cpi->stats_in_end = (void*)((char *)cpi->stats_in
+ (packets - 1) * packet_sz);
vp8_init_second_pass(cpi);
}
@ -3481,7 +3491,7 @@ static void apply_temporal_filter
modifier = 16 - modifier;
#endif
modifier *= filter_weight;
count[k] += modifier;
accumulator[k] += modifier * pixel_value;
@ -3656,7 +3666,7 @@ static void vp8cx_temp_blur1_c
YV12_BUFFER_CONFIG *f = cpi->frames[alt_ref_index];
unsigned char *dst1, *dst2;
DECLARE_ALIGNED(16, unsigned char, predictor[384]);
// Save input state
unsigned char *y_buffer = mbd->pre.y_buffer;
unsigned char *u_buffer = mbd->pre.u_buffer;

Просмотреть файл

@ -461,14 +461,14 @@ typedef struct
int target_bandwidth;
long long bits_left;
FIRSTPASS_STATS total_stats;
FIRSTPASS_STATS this_frame_stats;
FIRSTPASS_STATS *total_stats;
FIRSTPASS_STATS *this_frame_stats;
FIRSTPASS_STATS *stats_in, *stats_in_end;
struct vpx_codec_pkt_list *output_pkt_list;
int first_pass_done;
unsigned char *fp_motion_map;
FILE *fp_motion_mapfile;
int fpmm_pos;
unsigned char *fp_motion_map_stats, *fp_motion_map_stats_save;
#if 0
// Experimental code for lagged and one pass

Просмотреть файл

@ -14,6 +14,7 @@
#include "vpx_version.h"
#include "onyx_int.h"
#include "vpx/vp8e.h"
#include "vp8/encoder/firstpass.h"
#include "onyx.h"
#include <stdlib.h>
#include <string.h>
@ -189,22 +190,25 @@ static vpx_codec_err_t validate_config(vpx_codec_alg_priv_t *ctx,
if (cfg->g_pass == VPX_RC_LAST_PASS)
{
int n_doubles = cfg->rc_twopass_stats_in.sz / sizeof(double);
int n_packets = cfg->rc_twopass_stats_in.sz / sizeof(FIRSTPASS_STATS);
double frames;
int mb_r = (cfg->g_h + 15) / 16;
int mb_c = (cfg->g_w + 15) / 16;
size_t packet_sz = vp8_firstpass_stats_sz(mb_r * mb_c);
int n_packets = cfg->rc_twopass_stats_in.sz / packet_sz;
FIRSTPASS_STATS *stats;
if (!cfg->rc_twopass_stats_in.buf)
ERROR("rc_twopass_stats_in.buf not set.");
if (cfg->rc_twopass_stats_in.sz % sizeof(FIRSTPASS_STATS))
if (cfg->rc_twopass_stats_in.sz % packet_sz)
ERROR("rc_twopass_stats_in.sz indicates truncated packet.");
if (cfg->rc_twopass_stats_in.sz < 2 * sizeof(FIRSTPASS_STATS))
if (cfg->rc_twopass_stats_in.sz < 2 * packet_sz)
ERROR("rc_twopass_stats_in requires at least two packets.");
frames = ((double *)cfg->rc_twopass_stats_in.buf)[n_doubles - 1];
stats = (void*)((char *)cfg->rc_twopass_stats_in.buf
+ (n_packets - 1) * packet_sz);
if ((int)(frames + 0.5) != n_packets - 1)
if ((int)(stats->count + 0.5) != n_packets - 1)
ERROR("rc_twopass_stats_in missing EOS stats packet");
}