/* * 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; ienc_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_picsenc_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; ienc_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(delayseq_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(delayseq_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; nenc_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; nenc_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; }