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.

675 lines
15 KiB

/*
* seqinfo.c
*
* Copyright (C) Thomas Oestreich - June 2001
*
* 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.
*
*/
#include "transcode.h"
#include "seqinfo.h"
pthread_mutex_t seq_list_lock=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t seq_ctr_lock=PTHREAD_MUTEX_INITIALIZER;
seq_list_t *seq_list_head=NULL;
seq_list_t *seq_list_tail=NULL;
static int _sfd=0;
static double fps;
static int seq_ctr=0, drop_ctr=0;
seq_list_t *seq_register(int id)
{
/* objectives:
===========
register new seq
allocate space for new seq and establish backward reference
*/
seq_list_t *ptr;
pthread_mutex_lock(&seq_list_lock);
// retrive a valid pointer from the pool
if((ptr = tc_malloc(sizeof(seq_list_t))) == NULL) {
pthread_mutex_unlock(&seq_list_lock);
return(NULL);
}
memset(ptr, 0, sizeof(seq_list_t));
ptr->status = BUFFER_EMPTY;
ptr->next = NULL;
ptr->prev = NULL;
ptr->id = id;
if(seq_list_tail != NULL)
{
seq_list_tail->next = ptr;
ptr->prev = seq_list_tail;
}
seq_list_tail = ptr;
/* first seq registered must set seq_list_head */
if(seq_list_head == NULL) seq_list_head = ptr;
pthread_mutex_unlock(&seq_list_lock);
return(ptr);
}
/* ------------------------------------------------------------------ */
void seq_remove(seq_list_t *ptr)
{
/* objectives:
===========
remove seq from chained list
*/
if(ptr == NULL) return; // do nothing if null pointer
pthread_mutex_lock(&seq_list_lock);
if(ptr->prev != NULL) (ptr->prev)->next = ptr->next;
if(ptr->next != NULL) (ptr->next)->prev = ptr->prev;
if(ptr == seq_list_tail) seq_list_tail = ptr->prev;
if(ptr == seq_list_head) seq_list_head = ptr->next;
free(ptr);
ptr=NULL;
pthread_mutex_unlock(&seq_list_lock);
}
/* ------------------------------------------------------------------ */
seq_list_t *seq_retrieve()
{
/* objectives:
===========
get pointer to next full seq
*/
seq_list_t *ptr;
pthread_mutex_lock(&seq_list_lock);
ptr = seq_list_head;
/* move along the chain and check for status */
while(ptr != NULL)
{
if(ptr->status == BUFFER_READY)
{
pthread_mutex_unlock(&seq_list_lock);
return(ptr);
}
ptr = ptr->next;
}
pthread_mutex_unlock(&seq_list_lock);
return(NULL);
}
/* ------------------------------------------------------------------ */
static void seq_flush_thread(void)
{
seq_list_t *ptr, *tmp;
ptr = seq_retrieve();
if(ptr!=NULL) {
if(verbose & TC_SYNC) {
tc_log_msg(__FILE__, "syncinfo write (%d)", ptr->id);
}
seq_write(ptr);
// release valid pointer to pool
ptr->status = BUFFER_EMPTY;
tmp=ptr->prev;
seq_remove(tmp);
pthread_mutex_lock(&seq_ctr_lock);
--seq_ctr;
pthread_mutex_unlock(&seq_ctr_lock);
} else
tc_log_error(__FILE__, "called but no work to do - this shouldn't happen");
return;
}
static long frame_ctr=0;
static long check_ctr=0;
/* ------------------------------------------------------------------ */
void seq_write(seq_list_t *ptr)
{
int i, k, clone[256];
sync_info_t sync_info;
int tmp;
int inc=0;
double ftot_pts=0.0f;
// -----------------
//
// write to log file
//
// -----------------
// set clone flag for each frame:
// 0 = drop this frame
// 1 = unique frame
// N = use N copies of this frame
//default
for(i=0; i<ptr->enc_pics; ++i) clone[i]=1;
if(ptr->adj_pics<0) {
tmp = ptr->adj_pics;
inc = -ptr->enc_pics/ptr->adj_pics;
while(-tmp>0) {
k=(-tmp*inc)%ptr->enc_pics;
clone[k]=0;
++tmp;
}
}
if(ptr->adj_pics>0) {
tmp = ptr->adj_pics;
inc = (ptr->adj_pics<ptr->enc_pics) ? (int) ptr->enc_pics/ptr->adj_pics : 1;
while(tmp>0) {
k=(tmp*inc)%ptr->enc_pics;
++clone[k];
--tmp;
}
}
//write out
for(i=0; i<ptr->enc_pics; ++i) {
//check for pulldown flag
sync_info.pulldown = ptr->pulldown;
//master flag makes final decision
if(ptr->sync_active == 0) {
clone[i]=0;
sync_info.drop_seq=1;
sync_info.pulldown=0;
} else
sync_info.drop_seq=0;
if(verbose & TC_PRIVATE) {
tc_log_msg(__FILE__, "[%ld] %d %d %d %ld",
frame_ctr, ptr->id, i, clone[i], check_ctr);
}
drop_ctr += (int) (clone[i]-1);
sync_info.sequence = ptr->id;
sync_info.enc_frame = (long) frame_ctr++;
sync_info.adj_frame = (long) clone[i];
ftot_pts=(double) ptr->tot_pts/90000;
sync_info.dec_fps = (double) (ptr->tot_dec_pics)/ftot_pts;
sync_info.enc_fps = (ptr->ptime) ? (double) (ptr->enc_pics*90000)/ptr->ptime:0.0f;
sync_info.pts = (double) ptr->tot_pts/90000;
if((tmp=tc_pwrite(_sfd, (uint8_t *) &sync_info, sizeof(sync_info_t)))!= sizeof(sync_info_t)) {
tc_log_warn(__FILE__, "syncinfo write error (%d): %s",
tmp, strerror(errno));
}
check_ctr += clone[i];
if(verbose & TC_SYNC && i==ptr->enc_pics-1) {
tc_log_msg(__FILE__, "sync data for sequence %d flushed [%ld]",
ptr->id, sync_info.enc_frame);
}
}
if(verbose & TC_PRIVATE) {
tc_log_msg(__FILE__, "frames=%6ld seq=%4ld adj=%4d AV=%8.4f [fps] ratio= %.4f PTS= %.2f",
sync_info.enc_frame, sync_info.sequence, drop_ctr,
sync_info.dec_fps-fps, sync_info.enc_fps/fps, sync_info.pts);
}
return;
}
/* ------------------------------------------------------------------ */
void seq_update(seq_list_t *ptr, int end_pts, int pictures, int packets, int flag, int hard_fps)
{
int tmp;
long int adj=0;
long int request_pics=0, delay=0;
double ftot_pts=0.0;
//set basic parameter from inout variables
ptr->seq_pics = pictures + ptr->pics_first_packet;
ptr->enc_pics = ptr->seq_pics;
ptr->packet_ctr = packets;
ptr->ptime = end_pts - ptr->pts;
ptr->sync_active = flag; //master flag
if(ptr->ptime<=0 || ptr->id == 0) {
adj=0;
delay=0;
goto skip;
}
if(ptr->id && ptr->sync_reset==0) {
// (1) calculate total encoded frames:
ptr->tot_enc_pics = ptr->prev->tot_enc_pics + ptr->enc_pics;
// (2) calculate total decoded frames:
ptr->tot_dec_pics = ptr->prev->tot_dec_pics + ptr->seq_pics;
// (3) total number of packets:
ptr->tot_packet_ctr = ptr->prev->tot_packet_ctr + ptr->packet_ctr;
// (4) total time since first sequence
ptr->tot_pts = ptr->prev->tot_pts + ptr->ptime;
} else {
// first sequence
ptr->tot_enc_pics = ptr->enc_pics;
ptr->tot_dec_pics = ptr->seq_pics;
ptr->tot_packet_ctr = ptr->packet_ctr;
ptr->tot_pts = ptr->ptime;
}
// (5) total frames as requested by transcode
ftot_pts=(double) ptr->tot_pts/90000.;
request_pics = (long) (fps * ftot_pts);
// (6) drop or clone frames of this sequence?
delay = request_pics - ptr->tot_dec_pics;
adj=0;
if(delay>0) { //frame cloning no limit yet
if(delay<ptr->seq_pics) {
adj = delay;
} else {
adj = delay - (delay%ptr->seq_pics);
}
}
if(delay<0) { //frame dropping, maximum seq_pics/2
tmp=-delay;
if ( (-delay) >= (ptr->seq_pics/2)) {
adj = -(ptr->seq_pics/2);
} else {
adj = delay;
}
}
//3:2 pulldown?
// disable smooth dropping, pulldown, etc. This makes sense for variable
// framerates when the fps changes back and forth from 29.9 to 23.9 fps. The
// smooth dropping will work very badly then.
if (hard_fps == 0) {
ptr->pulldown=0;
if(adj == -3 && ptr->ptime == 45045 && ptr->seq_pics==15) ptr->pulldown = 1;
if(adj == -4 && ptr->ptime == 45045 && ptr->seq_pics==15) ptr->pulldown = 2;
if(adj == -2 && ptr->ptime == 6006 && ptr->seq_pics== 4) ptr->pulldown = 3;
if(adj == -1 && ptr->ptime == 39039 && ptr->seq_pics==11) ptr->pulldown = 4;
//smooth drop/copy algorithm 2002-08-21
if(ptr->pulldown==0) {
if(adj==-1 || adj==1 || adj==2) adj=0;
if(adj== 3) adj=1;
}
}
skip:
if(verbose & TC_PRIVATE) {
tc_log_msg(__FILE__, "---------------------------------------------------------");
tc_log_msg(__FILE__, "MPEG sequence: %d (reset=%d)", ptr->id, ptr->sync_reset);
tc_log_msg(__FILE__, "2k packets: %d (%d) | stream size %.2f MB", ptr->packet_ctr, ptr->tot_packet_ctr, (double) 2*ptr->tot_packet_ctr/(1<<10));
tc_log_msg(__FILE__, "PTS: %f (abs) --> runtime=%f (sec)", (double) ptr->pts/90000, ftot_pts);
tc_log_msg(__FILE__, "sequence length: %f | ftime: %.4f (sec)", (double) ptr->ptime/90000, (double) ptr->ptime/90000/ptr->seq_pics);
tc_log_msg(__FILE__, "sequence frames: %2d (current=%.3f fps) %ld (average=%.3f fps)", ptr->seq_pics, (double) ptr->seq_pics*90000/ptr->ptime, ptr->ptime, (double) ptr->tot_dec_pics/ftot_pts);
tc_log_msg(__FILE__, "3:2 pulldown flag: %d (%f) | master_flag = %d", ptr->pulldown, fps * ftot_pts - ptr->tot_dec_pics, flag);
tc_log_msg(__FILE__, "total frames (encoded in sequence 0-%d): %d (requested=%ld) %ld --> adjust: %ld", ptr->id, ptr->tot_enc_pics, request_pics, delay, adj);
}
//save the adjustment:
ptr->tot_dec_pics +=adj;
ptr->seq_pics +=adj;
ptr->adj_pics =adj;
// A-V shift at end of this sequence
ptr->av_sync = (ptr->tot_dec_pics - request_pics)/fps;
if(verbose & TC_PRIVATE) {
tc_log_msg(__FILE__, "adjusted frames (decoded in sequence 0-%d): %d --> A-V: %.4f", ptr->id, ptr->tot_dec_pics, ptr->av_sync);
tc_log_msg(__FILE__, "---------------------------------------------------------");
}
// -----------------
//
// write to log file
//
// -----------------
ptr->status = BUFFER_READY;
pthread_mutex_lock(&seq_ctr_lock);
++seq_ctr;
pthread_mutex_unlock(&seq_ctr_lock);
seq_flush_thread();
return;
}
/* ------------------------------------------------------------------ */
/********
* FIXME: these two functions, and the printf()s below, are only used
* with the undocumented TC_DEMUX_SEQ_LIST (-M 5) mode. Is printf()
* appropriate, or for that matter is this even needed at all? --AC
********/
static int seq_offset=0, unit_ctr=-1;
void seq_list_frames()
{
if(unit_ctr==-1) return;
tc_log_info(__FILE__, "%8ld video frame(s) in unit %d detected",
(long) frame_ctr, unit_ctr);
}
/* ------------------------------------------------------------------ */
void seq_list(seq_list_t *ptr, int end_pts, int pictures, int packets, int flag)
{
int n, id;
int tmp;
long int adj=0;
long int request_pics=0, delay=0;
double ftot_pts=0.0;
//set basic parameter from inout variables
//-------------------------------------------------------
//for NTSC film
//we need to recalculate ptr->seq_pics
//-------------------------------------------------------
id=ptr->id-seq_offset;
ptr->seq_pics = pictures + ptr->pics_first_packet;
ptr->enc_pics = ptr->seq_pics;
ptr->ptime = end_pts - ptr->pts;
ptr->sync_active = flag; //master flag
//first sequence timing info may be wrong
if(ptr->ptime<=0 || id == 0 || ptr->sync_reset) {
adj=0;
delay=0;
goto skip;
}
if(ptr->id && ptr->sync_reset==0) {
// (1) calculate total encoded frames:
ptr->tot_enc_pics = ptr->prev->tot_enc_pics + ptr->enc_pics;
// (2) calculate total decoded frames:
ptr->tot_dec_pics = ptr->prev->tot_dec_pics + ptr->seq_pics;
// (3) total number of packets:
ptr->tot_packet_ctr = ptr->prev->tot_packet_ctr + ptr->packet_ctr;
// (4) total time since first sequence
ptr->tot_pts = ptr->prev->tot_pts + ptr->ptime;
} else {
ptr->tot_enc_pics = ptr->enc_pics;
ptr->tot_dec_pics = ptr->seq_pics;
ptr->tot_packet_ctr = ptr->packet_ctr;
ptr->tot_pts = ptr->ptime;
}
// (5) total frames as requested by transcode
ftot_pts=(double) ptr->tot_pts/90000;
request_pics = (long) (fps * ftot_pts);
// (6) drop or clone frames of this sequence?
delay = request_pics - ptr->tot_dec_pics;
adj=0;
if(delay>0) { //frame cloning no limit yet
if(delay<ptr->seq_pics) {
adj = delay;
} else {
adj = delay - (delay%ptr->seq_pics);
}
}
if(delay<0) { //frame dropping, maximum seq_pics/2
tmp=-delay;
if ( (-delay) >= (ptr->seq_pics/2)) {
adj = -(ptr->seq_pics/2);
} else {
adj = delay;
}
}
ptr->pulldown=0;
//smooth drop/copy algorithm 2002-06-04
if((adj==1 || adj == -1 || adj==2 || adj== -2 || adj== -3 || adj== 3)
&& ptr->pulldown==0) adj=0;
if(adj > 3 && ptr->pulldown==0) adj= 1;
if(adj < -3 && ptr->pulldown==0) adj=-1;
//FIXME: drop all NTSC stuff, let transcode handle frame count
adj=0;
skip:
//save the adjustment:
ptr->tot_dec_pics +=adj;
ptr->seq_pics +=adj;
ptr->adj_pics =adj;
// A-V shift at end of this sequence
ptr->av_sync = (ptr->tot_dec_pics - request_pics)/fps;
// -----------------
//
// write to log file
//
// -----------------
ptr->status = BUFFER_READY;
pthread_mutex_lock(&seq_ctr_lock);
++seq_ctr;
pthread_mutex_unlock(&seq_ctr_lock);
//print frame navigation information:
if(ptr->sync_reset) {
seq_list_frames();
frame_ctr=0;
seq_offset=ptr->id;
++unit_ctr;
id=0;
}
// tc_log_msg(__FILE__, "--- %d %d %d %d %d %d", delay, adj, ptr->seq_pics , ptr->enc_pics, ptr->pics_first_packet, pictures);
//
// first sequence of stream or new unit
//
if(id==0 || ptr->sync_reset) {
for(n=0; n<ptr->enc_pics; ++n)
printf("%2d %6ld %5d %5d %6ld %3d\n", unit_ctr, (long) frame_ctr++, id, id, (long) ptr->packet_ctr, n);
return;
}
//
// regular sequence
//
for(n=0; n<ptr->enc_pics; ++n) {
if(n==0 || n==1) {
printf("%2d %6ld %5d %5d %6ld %3d\n", unit_ctr, (long) frame_ctr++, id, id-1, (long) ptr->prev->packet_ctr, ptr->prev->seq_pics+n);
} else {
printf("%2d %6ld %5d %5d %6ld %3d\n", unit_ctr, (long) frame_ctr++, id, id, (long) ptr->packet_ctr, n);
}
}
return;
}
/* ------------------------------------------------------------------ */
int seq_init(const char *logfile, int _ext_sfd, double _fps, int _verbose)
{
//need to open the file
if(logfile != NULL) {
if((_sfd = open(logfile, O_WRONLY|O_CREAT, 0666))<0) {
tc_log_error(__FILE__, "open logfile: %s", strerror(errno));
return(-1);
}
}
fps = _fps;
//done
if(_verbose & TC_DEBUG)
tc_log_msg(__FILE__, "open %s for frame sync information", logfile);
return(0);
}
/* ------------------------------------------------------------------ */
void seq_close()
{
if(_sfd != 0) close(_sfd);
_sfd=0;
return;
}