diff options
author | van <[email protected]> | 2008-03-15 08:02:08 +0000 |
---|---|---|
committer | van <[email protected]> | 2008-03-15 08:02:08 +0000 |
commit | dced078041d66034f53d72cc282055a50a2d0687 (patch) | |
tree | 02bea4c568d1efec6890ed12c96a1e8c0ddd2753 /libhb | |
parent | cf31c8ed79b120aa0c6e4c7280374dcbfb3b7320 (diff) |
- Add mpeg2 "Standard Target Decoder" clock recovery to the low level mpeg stream reader so we don't have to guess about the clock in sync.
- Since sync now has a fairly reliable clock, make it just trim excess audio or video and fill holes so that we maintain cross media sync.
- Redo the TS-to-PS transmuxing code to work on smaller units so that we can reliably convert the TS clock (PCR) to a PS clock (SCR).
git-svn-id: svn://svn.handbrake.fr/HandBrake/trunk@1341 b64f7644-9d1e-0410-96f1-a4d463321fa5
Diffstat (limited to 'libhb')
-rw-r--r-- | libhb/demuxmpeg.c | 14 | ||||
-rw-r--r-- | libhb/reader.c | 73 | ||||
-rwxr-xr-x | libhb/stream.c | 1327 | ||||
-rw-r--r-- | libhb/sync.c | 641 |
4 files changed, 825 insertions, 1230 deletions
diff --git a/libhb/demuxmpeg.c b/libhb/demuxmpeg.c index 93b3709c3..40264fd2e 100644 --- a/libhb/demuxmpeg.c +++ b/libhb/demuxmpeg.c @@ -11,9 +11,8 @@ int hb_demux_ps( hb_buffer_t * buf_ps, hb_list_t * list_es ) { hb_buffer_t * buf_es; - int pos; - - pos = 0; + int pos = 0; + int64_t scr; #define d (buf_ps->data) @@ -26,6 +25,14 @@ int hb_demux_ps( hb_buffer_t * buf_ps, hb_list_t * list_es ) return 0; } pos += 4; /* pack_start_code */ + /* extract the system clock reference (scr) */ + scr = ((uint64_t)(d[pos] & 0x38) << 27) | + ((uint64_t)(d[pos] & 0x03) << 28) | + ((uint64_t)(d[pos+1]) << 20) | + ((uint64_t)(d[pos+2] >> 3) << 15) | + ((uint64_t)(d[pos+2] & 3) << 13) | + ((uint64_t)(d[pos+3]) << 5) | + (d[pos+4] >> 3); pos += 9; /* pack_header */ pos += 1 + ( d[pos] & 0x7 ); /* stuffing bytes */ @@ -112,6 +119,7 @@ int hb_demux_ps( hb_buffer_t * buf_ps, hb_list_t * list_es ) buf_es->id = id; buf_es->start = pts; + buf_es->stop = scr; if (id == 0xE0) { // Consume a chapter break, and apply it to the ES. buf_es->new_chap = buf_ps->new_chap; diff --git a/libhb/reader.c b/libhb/reader.c index a77cf38e9..b2bff03ef 100644 --- a/libhb/reader.c +++ b/libhb/reader.c @@ -17,6 +17,10 @@ typedef struct hb_stream_t * stream; uint sequence; + int saw_video; + int64_t scr_offset; + int64_t last_scr; + int scr_changes; } hb_reader_t; @@ -163,8 +167,73 @@ static void ReaderFunc( void * _r ) { hb_list_rem( list, buf ); fifos = GetFifoForId( r->job, buf->id ); + + if ( ! r->saw_video ) + { + /* The first video packet defines 'time zero' so discard + data until we get a video packet with a PTS */ + if ( buf->id == 0xE0 && buf->start != -1 ) + { + r->saw_video = 1; + r->scr_offset = buf->start; + r->last_scr = buf->stop; + hb_log( "reader: first SCR %llu scr_offset %llu", + r->last_scr, r->scr_offset ); + } + else + { + fifos = NULL; + } + } if( fifos ) { + /* + * This section of code implements the timing model of + * the "Standard Target Decoder" (STD) of the MPEG2 standard + * (specified in ISO 13818-1 sections 2.4.2, 2.5.2 & Annex D). + * The STD removes and corrects for clock discontinuities so + * that the time stamps on the video, audio & other media + * streams can be used for cross-media synchronization. To do + * this the STD has its own timestamp value, the System Clock + * Reference or SCR, in the PACK header. Clock discontinuities + * are detected using the SCR & and the adjustment needed + * to correct post-discontinuity timestamps to be contiguous + * with pre-discontinuity timestamps is computed from pre- and + * post-discontinuity values of the SCR. Then this adjustment + * is applied to every media timestamp (PTS). + * + * hb_demux_ps left the SCR for this pack in buf->stop. + * ISO 13818-1 says there must be an SCR at least every 700ms + * (100ms for Transport Streams) so if the difference between + * this SCR & the previous is >700ms it's a discontinuity. + * If the difference is negative it's non-physical (time doesn't + * go backward) and must also be a discontinuity. When we find a + * discontinuity we adjust the scr_offset so that the SCR of the + * new packet lines up with that of the previous packet. + */ + int64_t scr_delta = buf->stop - r->last_scr; + if ( scr_delta > 67500 || scr_delta < -900 ) + { + ++r->scr_changes; + r->scr_offset += scr_delta - 1; + } + r->last_scr = buf->stop; + buf->stop = -1; + + /* + * The last section detected discontinuites and computed the + * appropriate correction to remove them. The next couple of + * lines apply the correction to the media timestamps so the + * code downstream of us sees only timestamps relative to the + * same, continuous clock with time zero on that clock being + * the time of the first video packet. + */ + if ( buf->start != -1 ) + { + /* this packet has a PTS - correct it for the initial + video time offset & any timing discontinuities. */ + buf->start -= r->scr_offset; + } buf->sequence = r->sequence++; for( n = 0; fifos[n] != NULL; n++) { @@ -212,10 +281,10 @@ static void ReaderFunc( void * _r ) hb_stream_close(&r->stream); } + hb_log( "reader: done. %d scr changes", r->scr_changes ); + free( r ); _r = NULL; - - hb_log( "reader: done" ); } /*********************************************************************** diff --git a/libhb/stream.c b/libhb/stream.c index 5cc1be190..9b6fd432c 100755 --- a/libhb/stream.c +++ b/libhb/stream.c @@ -17,12 +17,8 @@ typedef enum { hb_stream_type_unknown = 0, hb_stream_type_transport, hb_stream_t #define kMaxNumberVideoPIDS 1 #define kMaxNumberAudioPIDS 16 #define kMaxNumberDecodeStreams (kMaxNumberVideoPIDS+kMaxNumberAudioPIDS) -#define kNumDecodeBuffers 2 #define kMaxNumberPMTStreams 32 -#define CLOCKRATE ((int64_t)27000000) // MPEG System clock rate -#define STREAMRATE ((int64_t)2401587) // Original HD stream rate 19.2 Mbps -#define DEMUX (((int)STREAMRATE * 8) / 50)// Demux value for HD content STREAMRATE / 50 struct hb_stream_s { @@ -30,23 +26,13 @@ struct hb_stream_s FILE * file_handle; hb_stream_type_t stream_type; - int ps_current_write_buffer_index; - int ps_current_read_buffer_index; + int frames; /* video frames so far */ + int errors; /* total errors so far */ + int last_error_frame; /* frame # at last error message */ + int last_error_count; /* # errors at last error message */ - struct { - int size; - int len; - int read_pos; - int write_pos; - unsigned char * data; - } ps_decode_buffer[kNumDecodeBuffers]; - - struct { - int lang_code; - int flags; - int rate; - int bitrate; - } a52_info[kMaxNumberAudioPIDS]; + int64_t ts_lastpcr; /* the last pcr we found in the TS stream */ + int64_t ts_nextpcr; /* the next pcr to put in a PS packet */ int ts_video_pids[kMaxNumberVideoPIDS]; int ts_audio_pids[kMaxNumberAudioPIDS]; @@ -54,14 +40,21 @@ struct hb_stream_s int ts_number_video_pids; int ts_number_audio_pids; - unsigned char* ts_packetbuf[kMaxNumberDecodeStreams]; - int ts_packetpos[kMaxNumberDecodeStreams]; -// int ts_bufpackets[kMaxNumberDecodeStreams]; - int ts_foundfirst[kMaxNumberDecodeStreams]; - int ts_skipbad[kMaxNumberDecodeStreams]; - int ts_streamcont[kMaxNumberDecodeStreams]; + uint8_t *ts_buf[kMaxNumberDecodeStreams]; + int ts_pos[kMaxNumberDecodeStreams]; int ts_streamid[kMaxNumberDecodeStreams]; - int ts_audio_stream_type[kMaxNumberAudioPIDS]; + int ts_audio_stream_type[kMaxNumberDecodeStreams]; + int8_t ts_foundfirst[kMaxNumberDecodeStreams]; + int8_t ts_skipbad[kMaxNumberDecodeStreams]; + int8_t ts_streamcont[kMaxNumberDecodeStreams]; + int8_t ts_start[kMaxNumberDecodeStreams]; + + struct { + int lang_code; + int flags; + int rate; + int bitrate; + } a52_info[kMaxNumberAudioPIDS]; struct { @@ -98,9 +91,8 @@ struct hb_stream_s static void hb_stream_duration(hb_stream_t *stream, hb_title_t *inTitle); static void hb_ts_stream_init(hb_stream_t *stream); static void hb_ts_stream_find_pids(hb_stream_t *stream); -static void hb_ts_stream_decode(hb_stream_t *stream); +static int hb_ts_stream_decode(hb_stream_t *stream, uint8_t *obuf); static void hb_ts_stream_reset(hb_stream_t *stream); -static void hb_stream_put_back(hb_stream_t *stream, int i); static hb_audio_t *hb_ts_stream_set_audio_id_and_codec(hb_stream_t *stream, int aud_pid_index); static void hb_ps_stream_find_audio_ids(hb_stream_t *stream, hb_title_t *title); @@ -195,21 +187,13 @@ static void hb_stream_delete( hb_stream_t ** _d ) } int i=0; - for (i = 0; i < kNumDecodeBuffers; i++) - { - if (d->ps_decode_buffer[i].data) - { - free(d->ps_decode_buffer[i].data); - d->ps_decode_buffer[i].data = NULL; - } - } for (i = 0; i < kMaxNumberDecodeStreams; i++) { - if (d->ts_packetbuf[i]) + if (d->ts_buf[i]) { - free(d->ts_packetbuf[i]); - d->ts_packetbuf[i] = NULL; + free(d->ts_buf[i]); + d->ts_buf[i] = NULL; } } free( d->path ); @@ -261,6 +245,13 @@ hb_stream_t * hb_stream_open( char * path ) **********************************************************************/ void hb_stream_close( hb_stream_t ** _d ) { + hb_stream_t *stream = * _d; + if ( stream->frames ) + { + hb_log( "stream: %d good frames, %d errors (%.0f%%)", stream->frames, + stream->errors, (double)stream->errors * 100. / + (double)stream->frames ); + } } /* when the file was first opened we made entries for all the audio elementary @@ -276,14 +267,27 @@ static void hb_stream_delete_audio_entry(hb_stream_t *stream, int indx) for (i = indx+1; i < stream->ts_number_audio_pids; ++i) { stream->ts_audio_pids[indx] = stream->ts_audio_pids[i]; - stream->ts_audio_stream_type[indx] = stream->ts_audio_stream_type[i]; - stream->ts_streamid[stream->ts_number_video_pids + indx] = - stream->ts_streamid[stream->ts_number_video_pids + i]; + stream->ts_audio_stream_type[1 + indx] = stream->ts_audio_stream_type[1+i]; + stream->ts_streamid[1 + indx] = stream->ts_streamid[1 + i]; ++indx; } --stream->ts_number_audio_pids; } +static int index_of_pid(int pid, hb_stream_t *stream) +{ + int i; + + if ( pid == stream->ts_video_pids[0] ) + return 0; + + for ( i = 0; i < stream->ts_number_audio_pids; ++i ) + if ( pid == stream->ts_audio_pids[i] ) + return i + 1; + + return -1; +} + /*********************************************************************** * hb_ps_stream_title_scan *********************************************************************** @@ -340,6 +344,13 @@ hb_title_t * hb_stream_title_scan(hb_stream_t *stream) --i; } } + + // add the PCR PID if we don't already have it + if ( index_of_pid( stream->pmt_info.PCR_PID, stream ) < 0 ) + { + stream->ts_audio_pids[stream->ts_number_audio_pids++] = + stream->pmt_info.PCR_PID; + } } else { @@ -537,7 +548,7 @@ static double compute_stream_rate( struct pts_pos *pp, int n ) for ( i = 0; i < n-1; ++i ) { // Bias the median filter by not including pairs that are "far" - // frome one another. This is to handle cases where the file is + // from one another. This is to handle cases where the file is // made of roughly equal size pieces where a symmetric choice of // pairs results in having the same number of intra-piece & // inter-piece rate estimates. This would mean that the median @@ -595,68 +606,13 @@ static void hb_stream_duration(hb_stream_t *stream, hb_title_t *inTitle) **********************************************************************/ int hb_stream_read( hb_stream_t * src_stream, hb_buffer_t * b ) { - if (src_stream->stream_type == hb_stream_type_program) - { - size_t amt_read; - amt_read = fread(b->data, HB_DVD_READ_BUFFER_SIZE, 1, src_stream->file_handle); - if (amt_read > 0) - return 1; - else - return 0; - } - else if (src_stream->stream_type == hb_stream_type_transport) - { - int read_buffer_index = src_stream->ps_current_read_buffer_index; - - // Transport streams are a little more complex - we might be able to just - // read from the transport stream conversion buffer (if there's enough data) - // or we may need to transfer what's left and fill it again. - if (src_stream->ps_decode_buffer[read_buffer_index].len - - src_stream->ps_decode_buffer[read_buffer_index].read_pos - >= HB_DVD_READ_BUFFER_SIZE) - { - memcpy(b->data, - src_stream->ps_decode_buffer[read_buffer_index].data + - src_stream->ps_decode_buffer[read_buffer_index].read_pos, - HB_DVD_READ_BUFFER_SIZE); - src_stream->ps_decode_buffer[read_buffer_index].read_pos += HB_DVD_READ_BUFFER_SIZE; - return 1; - } - else - { - // Not quite enough data in the buffer - transfer what is present, fill the buffer and then - // transfer what's still needed. - int transfer_size = HB_DVD_READ_BUFFER_SIZE; - int amt_avail_to_transfer = src_stream->ps_decode_buffer[read_buffer_index].len - src_stream->ps_decode_buffer[read_buffer_index].read_pos; - memcpy(b->data, src_stream->ps_decode_buffer[read_buffer_index].data + src_stream->ps_decode_buffer[read_buffer_index].read_pos, amt_avail_to_transfer); - transfer_size -= amt_avail_to_transfer; - - // Give up this buffer - decoding may well need it, and we're done - src_stream->ps_decode_buffer[read_buffer_index].read_pos = 0; - src_stream->ps_decode_buffer[read_buffer_index].write_pos = 0; - src_stream->ps_decode_buffer[read_buffer_index].len = 0; - - // Fill the buffer - hb_ts_stream_decode(src_stream); - - // Decoding will almost certainly have changed the current read buffer index - read_buffer_index = src_stream->ps_current_read_buffer_index; - - if (src_stream->ps_decode_buffer[read_buffer_index].len == 0) - { - hb_log("hb_stream_read - buffer after decode has zero length data"); - return 0; - } - - // Read the bit we still need - memcpy(b->data+amt_avail_to_transfer, src_stream->ps_decode_buffer[read_buffer_index].data + src_stream->ps_decode_buffer[read_buffer_index].read_pos,transfer_size); - src_stream->ps_decode_buffer[read_buffer_index].read_pos += transfer_size; - - return 1; - } - } - else - return 0; + if ( src_stream->stream_type == hb_stream_type_program ) + { + size_t amt_read = fread(b->data, HB_DVD_READ_BUFFER_SIZE, 1, + src_stream->file_handle); + return (amt_read > 0); + } + return hb_ts_stream_decode( src_stream, b->data ); } /*********************************************************************** @@ -688,28 +644,6 @@ int hb_stream_seek( hb_stream_t * src_stream, float f ) hb_ts_stream_reset(src_stream); } - // Now we must scan forwards for a valid start code (0x000001BA) - int done = 0; - hb_buffer_t *buf = hb_buffer_init(HB_DVD_READ_BUFFER_SIZE); - while (!done) - { - if (hb_stream_read(src_stream,buf) == 1) - { - int i=0; - for (i=0; (i <= HB_DVD_READ_BUFFER_SIZE-4) && (!done); i++) - { - if ((buf->data[i] == 0x00) && (buf->data[i+1] == 0x00) && (buf->data[i+2] == 0x01) && (buf->data[i+3] == 0xba)) - { - done = 1; - // 'Put Back' the data we've just read (up to this point) - hb_stream_put_back(src_stream, i); - } - } - } - else - done = 1; // End of data; - } - hb_buffer_close(&buf); return 1; } @@ -733,10 +667,10 @@ static hb_audio_t *hb_ts_stream_set_audio_id_and_codec(hb_stream_t *stream, audio->codec = HB_ACODEC_AC3; hb_log("transport stream pid 0x%x (type 0x%x) is AC-3 audio id 0x%x", stream->ts_audio_pids[aud_pid_index], - stream->ts_audio_stream_type[aud_pid_index], + stream->ts_audio_stream_type[1 + aud_pid_index], audio->id); - stream->ts_audio_stream_type[aud_pid_index] = 0x81; - stream->ts_streamid[stream->ts_number_video_pids + aud_pid_index] = buf[3]; + stream->ts_audio_stream_type[1 + aud_pid_index] = 0x81; + stream->ts_streamid[1 + aud_pid_index] = buf[3]; } else if ((buf[3] & 0xe0) == 0xc0) { @@ -744,10 +678,10 @@ static hb_audio_t *hb_ts_stream_set_audio_id_and_codec(hb_stream_t *stream, audio->codec = HB_ACODEC_MPGA; hb_log("transport stream pid 0x%x (type 0x%x) is MPEG audio id 0x%x", stream->ts_audio_pids[aud_pid_index], - stream->ts_audio_stream_type[aud_pid_index], + stream->ts_audio_stream_type[1 + aud_pid_index], audio->id); - stream->ts_audio_stream_type[aud_pid_index] = 0x03; - stream->ts_streamid[stream->ts_number_video_pids + aud_pid_index] = buf[3]; + stream->ts_audio_stream_type[1 + aud_pid_index] = 0x03; + stream->ts_streamid[1 + aud_pid_index] = buf[3]; } } fseeko(stream->file_handle, cur_pos, SEEK_SET); @@ -755,7 +689,7 @@ static hb_audio_t *hb_ts_stream_set_audio_id_and_codec(hb_stream_t *stream, { hb_log("transport stream pid 0x%x (type 0x%x) isn't audio", stream->ts_audio_pids[aud_pid_index], - stream->ts_audio_stream_type[aud_pid_index]); + stream->ts_audio_stream_type[1 + aud_pid_index]); } return audio; } @@ -949,81 +883,41 @@ void hb_stream_update_audio(hb_stream_t *stream, hb_audio_t *audio) HB_INPUT_CH_LAYOUT_GET_DISCRETE_LFE_COUNT(audio->input_channel_layout)); } - hb_log( "hb_stream_update_audio: id=%x, lang=%s, 3cc=%s, rate = %d, bitrate = %d, flags = 0x%x (%d)", audio->id, audio->lang, audio->iso639_2, audio->rate, audio->bitrate, audio->ac3flags, audio->ac3flags ); + hb_log( "stream: audio %x: lang %s, rate %d, bitrate %d, " + "flags = 0x%x", audio->id, audio->lang, audio->rate, + audio->bitrate, audio->ac3flags ); } /*********************************************************************** - * hb_stream_put_back - *********************************************************************** - * - **********************************************************************/ -static void hb_stream_put_back(hb_stream_t *stream, int i) -{ - if (stream->stream_type == hb_stream_type_program) - { - // Program streams are pretty easy - we just reposition the source file - // pointer - fseeko(stream->file_handle, -(HB_DVD_READ_BUFFER_SIZE-i), SEEK_CUR); - } - else if (stream->stream_type == hb_stream_type_transport) - { - int read_buffer_index = stream->ps_current_read_buffer_index; - - // Transport streams are a little more tricky - so long as the - // amount to back up is still within the current decode buffer - // we can just adjust the read pos. - if (stream->ps_decode_buffer[read_buffer_index].read_pos - i > 0) - { - stream->ps_decode_buffer[read_buffer_index].read_pos -= i; - } - else - hb_error("hb_stream_put_back - trying to step beyond the start of the buffer, read_pos = %d amt to put back = %d\n", stream->ps_decode_buffer[read_buffer_index].read_pos, i); - } -} - - -/*********************************************************************** * hb_ts_stream_init *********************************************************************** * **********************************************************************/ - #define PS_DECODE_BUFFER_SIZE ( 1024 * 1024 * 4) static void hb_ts_stream_init(hb_stream_t *stream) { - // Output Program Stream - int i=0; - for (i=0; i < kNumDecodeBuffers; i++) - { - stream->ps_decode_buffer[i].data = (unsigned char *) malloc(PS_DECODE_BUFFER_SIZE); - stream->ps_decode_buffer[i].read_pos = 0; - stream->ps_decode_buffer[i].size = PS_DECODE_BUFFER_SIZE; - stream->ps_decode_buffer[i].len = 0; - stream->ps_decode_buffer[i].write_pos = 0; - } + int i; for (i=0; i < kMaxNumberDecodeStreams; i++) { stream->ts_streamcont[i] = -1; } - - stream->ps_current_write_buffer_index = 0; - stream->ps_current_read_buffer_index = 1; + stream->ts_video_pids[0] = -1; + for ( i = 0; i < stream->ts_number_audio_pids; i++ ) + { + stream-> ts_audio_pids[i] = -1; + } // Find the audio and video pids in the stream hb_ts_stream_find_pids(stream); - for (i=0; i < stream->ts_number_video_pids; i++) - { - // In progress audio/video data during the transport stream -> program stream processing - stream->ts_packetbuf[i] = (unsigned char *) malloc(1024 * 1024); - stream->ts_streamid[i] = 0xE0; // Stream is Video - } + stream->ts_streamid[0] = 0xE0; // stream 0 must be video - for (i = stream->ts_number_video_pids; i < stream->ts_number_video_pids + stream->ts_number_audio_pids; i++) + for (i = 0; i < stream->ts_number_video_pids + stream->ts_number_audio_pids; i++) { - stream->ts_packetbuf[i] = (unsigned char *) malloc(1024 * 1024); + // demuxing buffer for TS to PS conversion + stream->ts_buf[i] = malloc( HB_DVD_READ_BUFFER_SIZE ); } } @@ -1233,7 +1127,7 @@ int decode_program_map(hb_stream_t* stream) if (i < kMaxNumberAudioPIDS) stream->ts_number_audio_pids++; stream->ts_audio_pids[i] = elementary_PID; - stream->ts_audio_stream_type[i] = stream_type; + stream->ts_audio_stream_type[1 + i] = stream_type; if (ES_info_length > 0) { @@ -1426,391 +1320,6 @@ int decode_PAT(unsigned char *buf, hb_stream_t *stream) return 1; } -static int flushbuf(hb_stream_t *stream) -{ - int old_write_index = stream->ps_current_write_buffer_index; - - // Flip the buffers and start moving on to the next - stream->ps_current_write_buffer_index++; - if (stream->ps_current_write_buffer_index > kNumDecodeBuffers-1) - stream->ps_current_write_buffer_index = 0; - - if ( (stream->ps_decode_buffer[stream->ps_current_write_buffer_index].len != 0) || (stream->ps_decode_buffer[stream->ps_current_write_buffer_index].write_pos != 0) ) - { - hb_log("flushbuf - new buffer (index %d) has non zero length and write position !", stream->ps_current_write_buffer_index); - return 0; - } - - stream->ps_current_read_buffer_index = old_write_index; - stream->ps_decode_buffer[stream->ps_current_read_buffer_index].read_pos = 0; - - return 1; -} - -static int fwrite64(void* buf, int elsize, int elnum, hb_stream_t* stream) -{ - int size = elsize; - if (elnum > 1) - size *= elnum; - - int written = 0; - int current_write_index = stream->ps_current_write_buffer_index; - - if (size <= stream->ps_decode_buffer[current_write_index].size - stream->ps_decode_buffer[current_write_index].write_pos) - { - memcpy(stream->ps_decode_buffer[current_write_index].data + stream->ps_decode_buffer[current_write_index].write_pos, buf, size); - stream->ps_decode_buffer[current_write_index].write_pos += size; - stream->ps_decode_buffer[current_write_index].len = stream->ps_decode_buffer[current_write_index].write_pos; - written = size; - } - else - { - memcpy(stream->ps_decode_buffer[current_write_index].data + stream->ps_decode_buffer[current_write_index].write_pos, buf, stream->ps_decode_buffer[current_write_index].size - stream->ps_decode_buffer[current_write_index].write_pos); - written += stream->ps_decode_buffer[current_write_index].size - stream->ps_decode_buffer[current_write_index].write_pos; - stream->ps_decode_buffer[current_write_index].write_pos += stream->ps_decode_buffer[current_write_index].size - stream->ps_decode_buffer[current_write_index].write_pos; - stream->ps_decode_buffer[current_write_index].len = stream->ps_decode_buffer[current_write_index].write_pos; - - if (flushbuf(stream)) - { - // FLushing the buffer will have change the current write buffer - current_write_index = stream->ps_current_write_buffer_index; - - memcpy(stream->ps_decode_buffer[current_write_index].data, (unsigned char*)buf + written, size - written); - stream->ps_decode_buffer[current_write_index].write_pos += size - written; - stream->ps_decode_buffer[current_write_index].len = stream->ps_decode_buffer[current_write_index].write_pos; - written += size - written; - } - } - - - if (elnum == 1 && written == size) - return 1; - else - return written / elsize; -} - -static int write_pack(hb_stream_t* stream, int64_t time) -{ - unsigned char buf[64]; - set_buf(buf, 64, 1); // clear buffer - - int64_t ext_time = time % 300; - time = time / 300; - - set_bits(0x000001ba, 32); // pack id 32 - set_bits(1, 2); // 0x01 2 - set_bits((unsigned int)(time >> 30), 3); // system_clock_reference_base 3 - set_bits(1, 1); // marker_bit 1 - set_bits((unsigned int)(time >> 15), 15); // system_clock_reference_base 15 - set_bits(1, 1); // marker_bit 1 - set_bits((unsigned int)time, 15); // system_clock_reference_base1 15 - set_bits(1, 1); // marker_bit 1 - set_bits((unsigned int)ext_time, 9); // system_clock_reference_extension 9 - set_bits(1, 1); // marker_bit 1 - set_bits(DEMUX, 22); // program_mux_rate 22 - set_bits(1, 1); // marker_bit 1 - set_bits(1, 1); // marker_bit 1 - set_bits(31, 5); // reserved 5 - set_bits(0, 3); // pack_stuffing_length 3 - - return fwrite64(buf, buf_size(), 1, stream) == 1; -} - -static int pad_buffer(hb_stream_t *stream, int pad) -{ - pad -= 6; - - char buf[6]; - buf[0] = '\x0'; buf[1] = '\x0'; buf[2] = '\x1'; buf[3] = '\xbe'; - buf[4] = pad >> 8; buf[5] = pad & 0xff; - - if (fwrite64(buf, 6, 1, stream) != 1) - return 0; - - unsigned char padbyte = 0xff; - int i=0; - for (i = 0; i < pad; i++) - { - if (fwrite64(&padbyte, 1, 1, stream) != 1) - return 0; - } - - return 1; -} - -int make_pes_header(unsigned char* buf, int streamid, int len, int64_t PTS, int64_t DTS) -{ - int hdrlen = 0; - int PTS_DTS_flags = 0; - if (PTS != -1) - { - if (DTS != -1) - { - PTS_DTS_flags = 3; - hdrlen += 10; - } - else - { - PTS_DTS_flags = 2; - hdrlen += 5; - } - } - - set_buf(buf, 9 + hdrlen, 1); // clear the buffer - - set_bits(0x000001, 24); // packet_start_code_prefix 24 - set_bits((unsigned int)streamid, 8); // directory_stream_id 8 - set_bits(len, 16); // PES_packet_length 16 - set_bits(0x2, 2); // '10' 2 - set_bits(0, 2); // PES_scrambling_control 2 - set_bits(1, 1); // PES_priority 1 - set_bits(0, 1); // data_alignment_indicator 1 - set_bits(0, 1); // copyright 1 - set_bits(0, 1); // original_or_copy 1 - set_bits(PTS_DTS_flags, 2); // PTS_DTS_flags 2 - set_bits(0, 1); // ESCR_flag 1 - set_bits(0, 1); // ES_rate_flag 1 - set_bits(0, 1); // DSM_trick_mode_flag 1 - set_bits(0, 1); // additional_copy_info_flag 1 - set_bits(0, 1); // PES_CRC_flag 1 - set_bits(0, 1); // PES_extension_flag 1 - set_bits(hdrlen, 8); // PES_header_data_length 8 - - if (PTS_DTS_flags == 2) - { - set_bits(2, 4); // '0010' 4 - set_bits((unsigned int)(PTS >> 30), 3); // PTS [32..30] 3 - set_bits(1, 1); // marker bit 1 - set_bits((unsigned int)(PTS >> 15), 15); // PTS [29..15] 15 - set_bits(1, 1); // marker bit 1 - set_bits((unsigned int)PTS, 15); // PTS [14..0] 15 - set_bits(1, 1); // marker bit 1 - } - else if (PTS_DTS_flags == 3) - { - set_bits(3, 4); // '0011' 4 - set_bits((unsigned int)(PTS >> 30), 3); // PTS [32..30] 3 - set_bits(1, 1); // marker bit 1 - set_bits((unsigned int)(PTS >> 15), 15); // PTS [29..15] 15 - set_bits(1, 1); // marker bit 1 - set_bits((unsigned int)PTS, 15); // PTS [14..0] 15 - set_bits(1, 1); // marker bit 1 - set_bits(1, 4); // '0001' 4 - set_bits((unsigned int)(DTS >> 30), 3); // DTS [32..30] 3 - set_bits(1, 1); // marker bit 1 - set_bits((unsigned int)(DTS >> 15), 15); // DTS [29..15] 15 - set_bits(1, 1); // marker bit 1 - set_bits((unsigned int)DTS, 15); // DTS [14..0] 15 - set_bits(1, 1); // marker bit 1 - } - - return buf_size(); -} - -int generate_output_data(hb_stream_t *stream, int write_ac3, int curstream, int pid) -{ - unsigned char ac3_substream_id[4]; - int ac3len = 0; - - if (write_ac3) - { - // Make a four byte DVD ac3 stream header - int ssid = (curstream - stream->ts_number_video_pids) & 0xf; - ac3_substream_id[0] = 0x80 | ssid; // substream id - ac3_substream_id[1] = 0x01; // number of sync words - ac3_substream_id[2] = 0x00; // first offset (16 bits) - ac3_substream_id[3] = 0x02; - ac3len = 4; - } - - int written = 0; // Bytes we've written to output file - int pos = 0; // Position in PES packet buffer - - for (;;) - { - if ((stream->ps_decode_buffer[stream->ps_current_write_buffer_index].len % HB_DVD_READ_BUFFER_SIZE) != 0) - { - hb_log("write_output_stream - Packet's not falling on read buffer size boundries!"); - return 1; - } - - // Get total length of this pack - int len = min(14 + ac3len + stream->ts_packetpos[curstream] - pos, HB_DVD_READ_BUFFER_SIZE); - - // Figure out stuffing (if we have less than 16 bytes left) - int stuffing = 0; - if (len < HB_DVD_READ_BUFFER_SIZE && HB_DVD_READ_BUFFER_SIZE - len < 16) - { - stuffing = HB_DVD_READ_BUFFER_SIZE - len; - len += stuffing; - } - - // Write out pack header - off_t file_offset = ftello(stream->file_handle); - int64_t packet_time = (file_offset * CLOCKRATE / STREAMRATE) + 0 /*file_time*/; - if (!write_pack(stream, packet_time)) - { - hb_log("write_output_stream - Couldn't write pack header!"); - return 1; - } - - stream->ts_packetbuf[curstream][pos + 3] = - stream->ts_streamid[curstream]; - - // Packet length.. - // Subtract pack size (14) and pes id and len (6) from lenth - stream->ts_packetbuf[curstream][pos + 4] = (len - 6 - 14) >> 8; stream->ts_packetbuf[curstream][pos + 5] = (len - 6 - 14) & 0xFF; - - // Add any stuffing bytes to header extra len - int hdrsize = 9 + stream->ts_packetbuf[curstream][pos + 8]; - stream->ts_packetbuf[curstream][pos + 8] += stuffing; // Add stuffing to header bytes - - // Write out id, streamid, len - if (fwrite64(stream->ts_packetbuf[curstream] + pos, hdrsize, 1, stream) != 1) // Write pes id, streamid, and len - { - hb_log("write_output_stream - Failed to write output file!"); - return 1; - } - - // Write stuffing - int i=0; - for (i = 0; i < stuffing; i++) // Write any stuffing bytes - { - unsigned char stuff = 0xff; - if (fwrite64(&stuff, 1, 1, stream) != 1) - { - hb_log("write_output_stream - Failed to write output file!"); - return 1; - } - } - - // Write ac3 streamid - if (ac3len != 0) - { - if (fwrite64(ac3_substream_id, ac3len, 1, stream) != 1) - { - hb_log("write_output_stream - Failed to write output file!"); - return 1; - } - } - - // Write rest of data len minus headersize (9) stuffing, and pack size (14) - if (fwrite64(stream->ts_packetbuf[curstream] + pos + hdrsize, len - hdrsize - 14 - stuffing - ac3len, 1, stream) != 1) // Write data bytes - { - hb_log("write_output_stream - Failed to write output file!"); - return 1; - } - written += len; - - // Add len minus stuff we added like the pack (14) and the stuffing. - pos += len - 14 - stuffing - ac3len; - if (pos == stream->ts_packetpos[curstream]) - break; - - // Add pes header for next packet - pos -= 9; - make_pes_header(stream->ts_packetbuf[curstream] + pos, stream->ts_streamid[curstream], 0, -1, -1); - } - - stream->ts_packetpos[curstream] = 0; - stream->ts_streamcont[curstream] = -1; - - // Write padding - if ((written % HB_DVD_READ_BUFFER_SIZE) != 0) - { - int left = HB_DVD_READ_BUFFER_SIZE - (written % HB_DVD_READ_BUFFER_SIZE); - - // Pad out to HB_DVD_READ_BUFFER_SIZE bytes - if (!pad_buffer(stream, left)) - { - hb_log("write_output_stream - Couldn't write pad buffer!"); - return 1; - } - } - - return 0; -} - -static void hb_ts_handle_mpeg_audio(hb_stream_t *stream, int curstream, unsigned char* buf, int adapt_len ) -{ - // Although we don't have AC3/A52 audio here we can still use the same structure to record this useful information. - - stream->a52_info[curstream - stream->ts_number_video_pids].flags = A52_STEREO; - stream->a52_info[curstream - stream->ts_number_video_pids].rate = 48000 /*Hz*/; - stream->a52_info[curstream - stream->ts_number_video_pids].bitrate = 384000 /*Bps*/; -} - -static int hb_ts_handle_ac3_audio(hb_stream_t *stream, int curstream, unsigned char* buf, int adapt_len ) -{ - int spos, dpos; - - // Make sure we start with 0x0b77 - if (stream->ts_packetbuf[curstream][9 + stream->ts_packetbuf[curstream][8]] != 0x0b || stream->ts_packetbuf[curstream][9 + stream->ts_packetbuf[curstream][8] + 1] != 0x77) - { - spos = 9 + stream->ts_packetbuf[curstream][8]; - dpos = 9 + stream->ts_packetbuf[curstream][8]; - while (spos <= stream->ts_packetpos[curstream] - 2 && !(stream->ts_packetbuf[curstream][spos] == 0x0b && stream->ts_packetbuf[curstream][spos + 1] == 0x77)) - spos++; - - if (!(stream->ts_packetbuf[curstream][spos] == 0x0b && stream->ts_packetbuf[curstream][spos + 1] == 0x77)) - { - hb_log("hb_ts_stream_decode - Couldn't sync AC3 packet!"); - stream->ts_skipbad[curstream] = 1; - return 0; - } - - while (spos < stream->ts_packetpos[curstream]) - { - stream->ts_packetbuf[curstream][dpos] = stream->ts_packetbuf[curstream][spos]; - spos++; - dpos++; - } - stream->ts_packetpos[curstream] = dpos; - } - - // Check the next packet to make sure IT starts with a 0x0b77 - int plen = 0; - plen = 9 + buf[4 + adapt_len + 8]; - int pstart = 4 + adapt_len + plen; - if (buf[pstart] != 0x0b || buf[pstart + 1] != 0x77) - { - spos = pstart; - while (spos < 188 - 2 && !(buf[spos] == 0x0b && buf[spos + 1] == 0x77)) - { - stream->ts_packetbuf[curstream][stream->ts_packetpos[curstream]] = buf[spos]; - stream->ts_packetpos[curstream]++; - spos++; - } - - if (!(buf[spos] == 0x0b && buf[spos + 1] == 0x77)) - { - hb_log("hb_ts_stream_decode - Couldn't sync AC3 packet!"); - stream->ts_skipbad[curstream] = 1; - return 0; - } - - adapt_len = spos - 4 - plen; - - dpos = spos - 1; - spos = pstart - 1; - while (spos >= pstart - plen) - { - buf[dpos] = buf[spos]; - spos--; - dpos--; - } - } - - int flags, rate, bitrate; - if( a52_syncinfo( &buf[pstart], &flags, &rate, &bitrate ) ) - { - stream->a52_info[curstream - stream->ts_number_video_pids].flags = flags; - stream->a52_info[curstream - stream->ts_number_video_pids].rate = rate; - stream->a52_info[curstream - stream->ts_number_video_pids].bitrate = bitrate; - } - return 1; -} - static void hb_ts_stream_find_pids(hb_stream_t *stream) { unsigned char buf[188]; @@ -1843,10 +1352,16 @@ static void hb_ts_stream_find_pids(hb_stream_t *stream) // Check sync byte if ((buf[0] != 0x47) && (buf[0] != 0x72) && (buf[0] != 0x29)) { - hb_log("hb_ts_stream_find_pids - Bad transport packet (no sync byte 0x47)!"); - int i = 0; - for (i=0; i < stream->ts_number_video_pids + stream->ts_number_audio_pids; i++) - stream->ts_skipbad[i] = 1; + off_t pos = ftello(stream->file_handle) - 188; + off_t pos2 = align_to_next_packet(stream->file_handle); + if ( pos2 == 0 ) + { + hb_log( "hb_ts_stream_find_pids: eof while re-establishing sync @ %lld", + pos ); + break; + } + hb_log("hb_ts_stream_decode: sync lost @%lld, regained after %lld bytes", + pos, pos2 ); continue; } @@ -1898,41 +1413,297 @@ static void hb_ts_stream_find_pids(hb_stream_t *stream) } } -int index_of_video_pid(int pid, hb_stream_t *stream) + +static uint8_t *fwrite_buf; +static uint8_t *fwrite_buf_orig; + +static void set_fwrite_buf( uint8_t *obuf ) { - int found_pid = -1, i = 0; + fwrite_buf = obuf; + fwrite_buf_orig = obuf; +} - for (i = 0; (i < stream->ts_number_video_pids) && (found_pid < 0); i++) - { - if (pid == stream->ts_video_pids[i]) - found_pid = i; - } - return found_pid; +static void fwrite64( void* buf, int size ) +{ + if ( (fwrite_buf - fwrite_buf_orig) + size > 2048 ) + { + hb_log( "steam fwrite64 buffer overflow - writing %d with %d already", + size, fwrite_buf - fwrite_buf_orig ); + return; + } + memcpy( fwrite_buf, buf, size ); + fwrite_buf += size; } -int index_of_audio_pid(int pid, hb_stream_t *stream) +static void write_pack(hb_stream_t* stream, uint64_t time, int stuffing) { - int i = 0, found_pid = -1; + uint8_t buf[64]; + set_buf(buf, 64, 1); // clear buffer + + set_bits(0x000001ba, 32); // pack id 32 + set_bits(1, 2); // 0x01 2 + set_bits(time >> 30, 3); // system_clock_reference_base 3 + set_bits(1, 1); // marker_bit 1 + set_bits(time >> 15, 15); // system_clock_reference_base 15 + set_bits(1, 1); // marker_bit 1 + set_bits(time, 15); // system_clock_reference_base1 15 + set_bits(1, 1); // marker_bit 1 + set_bits(0, 9); // system_clock_reference_extension 9 + set_bits(1, 1); // marker_bit 1 + set_bits(384000, 22); // program_mux_rate 22 + set_bits(1, 1); // marker_bit 1 + set_bits(1, 1); // marker_bit 1 + set_bits(31, 5); // reserved 5 + set_bits(stuffing, 3); // pack_stuffing_length 3 + while ( --stuffing >= 0 ) + { + set_bits(0xff, 8 ); + } + fwrite64(buf, buf_size()); +} - for (i = 0; (i < stream->ts_number_audio_pids) && (found_pid < 0); i++) - { - if (pid == stream->ts_audio_pids[i]) - found_pid = i; +static void pad_buffer(int pad) +{ + pad -= 6; + + uint8_t buf[6]; + buf[0] = 0; + buf[1] = 0; + buf[2] = 0; + buf[3] = 0xbe; + buf[4] = pad >> 8; + buf[5] = pad; + + fwrite64(buf, 6); + + buf[0] = 0xff; + while ( --pad >= 0 ) + { + fwrite64(buf, 1); } - return found_pid; } -int index_of_pid(int pid, hb_stream_t *stream) +static void make_pes_header(int len, uint8_t streamid) +{ + uint8_t buf[9]; + set_buf(buf, 9, 1); // clear the buffer + + set_bits(0x000001, 24); // packet_start_code_prefix 24 + set_bits(streamid, 8); // directory_stream_id 8 + set_bits(len + 3, 16); // PES_packet_length 16 + set_bits(0x2, 2); // '10' 2 + set_bits(0, 2); // PES_scrambling_control 2 + set_bits(1, 1); // PES_priority 1 + set_bits(0, 1); // data_alignment_indicator 1 + set_bits(0, 1); // copyright 1 + set_bits(0, 1); // original_or_copy 1 + set_bits(0, 2); // PTS_DTS_flags 2 + set_bits(0, 1); // ESCR_flag 1 + set_bits(0, 1); // ES_rate_flag 1 + set_bits(0, 1); // DSM_trick_mode_flag 1 + set_bits(0, 1); // additional_copy_info_flag 1 + set_bits(0, 1); // PES_CRC_flag 1 + set_bits(0, 1); // PES_extension_flag 1 + set_bits(0, 8); // PES_header_data_length 8 + + fwrite64(buf, 9); +} + +static void generate_output_data(hb_stream_t *stream, int curstream) +{ + uint8_t *tdat = stream->ts_buf[curstream]; + int len; + + // we always ship a PACK header plus all the data in our demux buf. + // AC3 audio also always needs it substream header. + len = 14 + stream->ts_pos[curstream]; + if ( stream->ts_audio_stream_type[curstream] == 0x81) + { + len += 4; + } + + if ( ! stream->ts_start[curstream] ) + { + // we're in the middle of a chunk of PES data - we need to add + // a 'continuation' PES header after the PACK header. + len += 9; + } + + // Write out pack header + // If we don't have 2048 bytes we need to pad to 2048. We can + // add a padding frame after our data but we need at least 7 + // bytes of space to do it (6 bytes of header & 1 of pad). If + // we have fewer than 7 bytes left we need to fill the excess + // space with stuffing bytes added to the pack header. + int stuffing = 0; + if ( len > HB_DVD_READ_BUFFER_SIZE ) + { + hb_log( "stream ts length botch %d", len ); + } + if ( HB_DVD_READ_BUFFER_SIZE - len < 8) + { + stuffing = HB_DVD_READ_BUFFER_SIZE - len; + } + write_pack(stream, stream->ts_nextpcr, stuffing ); + stream->ts_nextpcr += 10; + + if ( stream->ts_start[curstream] ) + { + // Start frames already have a PES header but we have modify it + // to map from TS PID to PS stream id. Also, if the stream is AC3 + // audio we have to insert an AC3 stream header between the end of + // the PES header and the start of the stream data. + + stream->ts_start[curstream] = 0; + tdat[3] = stream->ts_streamid[curstream]; + + uint16_t plen = stream->ts_pos[curstream] - 6; + if ( stream->ts_audio_stream_type[curstream] == 0x81) + { + // We have to add an AC3 header in front of the data. Add its + // size to the PES packet length. + plen += 4; + tdat[4] = plen >> 8; + tdat[5] = plen; + + // Write out the PES header + int hdrsize = 9 + tdat[8]; + fwrite64(tdat, hdrsize); + + // add a four byte DVD ac3 stream header + uint8_t ac3_substream_id[4]; + int ssid = (curstream - stream->ts_number_video_pids) & 0xf; + ac3_substream_id[0] = 0x80 | ssid; // substream id + ac3_substream_id[1] = 0x01; // number of sync words + ac3_substream_id[2] = 0x00; // first offset (16 bits) + ac3_substream_id[3] = 0x02; + fwrite64(ac3_substream_id, 4); + + // add the rest of the data + fwrite64(tdat + hdrsize, stream->ts_pos[curstream] - hdrsize); + } + else + { + // not audio - don't need to modify the stream so write what we've got + tdat[4] = plen >> 8; + tdat[5] = plen; + fwrite64( tdat, stream->ts_pos[curstream] ); + } + } + else + { + // data without a PES start header needs a simple 'continuation' + // PES header. AC3 audio also needs its substream header. + if ( stream->ts_audio_stream_type[curstream] != 0x81) + { + make_pes_header(stream->ts_pos[curstream], + stream->ts_streamid[curstream]); + } + else + { + make_pes_header(stream->ts_pos[curstream] + 4, + stream->ts_streamid[curstream]); + + // add a four byte DVD ac3 stream header + uint8_t ac3_substream_id[4]; + int ssid = (curstream - stream->ts_number_video_pids) & 0xf; + ac3_substream_id[0] = 0x80 | ssid; // substream id + ac3_substream_id[1] = 0x01; // number of sync words + ac3_substream_id[2] = 0x00; // first offset (16 bits) + ac3_substream_id[3] = 0x02; + fwrite64(ac3_substream_id, 4); + } + fwrite64( tdat, stream->ts_pos[curstream] ); + } + + // Write padding + int left = HB_DVD_READ_BUFFER_SIZE - len; + if ( left >= 8 ) + { + pad_buffer(left); + } + + stream->ts_pos[curstream] = 0; +} + +static void ts_warn_helper( hb_stream_t *stream, char *log, va_list args ) { - int found_pid = -1; + // limit error printing to at most one per minute of video (at 30fps) + ++stream->errors; + if ( stream->frames - stream->last_error_frame >= 30*60 ) + { + char msg[256]; - if ((found_pid = index_of_video_pid(pid, stream)) >= 0) - return found_pid; + vsnprintf( msg, sizeof(msg), log, args ); - if ((found_pid = index_of_audio_pid(pid, stream)) >= 0) - return found_pid; + if ( stream->errors - stream->last_error_count < 10 ) + { + hb_log( "stream: error near frame %d: %s", stream->frames, msg ); + } + else + { + int Edelta = stream->errors - stream->last_error_count; + double Epcnt = (double)Edelta * 100. / + (stream->frames - stream->last_error_frame); + hb_log( "stream: %d new errors (%.0f%%) up to frame %d: %s", + Edelta, Epcnt, stream->frames, msg ); + } + stream->last_error_frame = stream->frames; + stream->last_error_count = stream->errors; + } +} + +static void ts_warn( hb_stream_t *stream, char *log, ... ) +{ + va_list args; + va_start( args, log ); + ts_warn_helper( stream, log, args ); + va_end( args ); +} + +static void ts_err( hb_stream_t *stream, int curstream, char *log, ... ) +{ + va_list args; + va_start( args, log ); + ts_warn_helper( stream, log, args ); + va_end( args ); + + stream->ts_skipbad[curstream] = 1; + stream->ts_pos[curstream] = 0; + stream->ts_streamcont[curstream] = -1; +} + +static int isIframe( const uint8_t *buf, int adapt_len ) +{ + // Look for the Group of Pictures packet + int i; + uint32_t strid = 0; + + for (i = 4 + adapt_len; i < 188; i++) + { + strid = (strid << 8) | buf[i]; + switch ( strid ) + { + case 0x000001B8: // group_start_code (GOP header) + case 0x000001B3: // sequence_header code + return 1; - return found_pid; + case 0x00000100: // picture_start_code + // picture_header, let's see if it's an I-frame + if (i<185) + { + // check if picture_coding_type == 1 + if ((buf[i+2] & (0x7 << 3)) == (1 << 3)) + { + // found an I-frame picture + return 1; + } + } + break; + } + } + // didn't find an I frame + return 0; } /*********************************************************************** @@ -1940,40 +1711,27 @@ int index_of_pid(int pid, hb_stream_t *stream) *********************************************************************** * **********************************************************************/ -static void hb_ts_stream_decode(hb_stream_t *stream) +static int hb_ts_stream_decode( hb_stream_t *stream, uint8_t *obuf ) { - unsigned char buf[188]; + int64_t pcr = stream->ts_lastpcr; int curstream; - int doing_iframe; - - int i = 0; - for (i=0; i < stream->ts_number_video_pids + stream->ts_number_audio_pids; i++) - { - stream->ts_skipbad[i] = 0; - } + uint8_t buf[188]; - doing_iframe = 0; + set_fwrite_buf( obuf ); - if ((stream->ts_number_video_pids == 0) || (stream->ts_number_audio_pids == 0)) + // spin until we get a packet of data from some stream or hit eof + while ( 1 ) { - hb_log("hb_ts_stream_decode - no Video or Audio PID selected, cannot decode transport stream"); - return; - } - - int curr_write_buffer_index = stream->ps_current_write_buffer_index; - - // Write output data until a buffer switch occurs. - while (curr_write_buffer_index == stream->ps_current_write_buffer_index) - { - if ((fread(buf, 188, 1, stream->file_handle)) != 1) + if ((fread(buf, 188, 1, stream->file_handle)) != 1) { // end of file - we didn't finish filling our ps write buffer // so just discard the remainder (the partial buffer is useless) hb_log("hb_ts_stream_decode - eof"); - stream->ps_decode_buffer[stream->ps_current_write_buffer_index].len = 0; - return; + return 0; } + /* This next section validates the packet */ + // Check sync byte if ((buf[0] != 0x47) && (buf[0] != 0x72) && (buf[0] != 0x29)) { @@ -1985,81 +1743,32 @@ static void hb_ts_stream_decode(hb_stream_t *stream) { hb_log( "hb_ts_stream_decode: eof while re-establishing sync @ %lld", pos ); - stream->ps_decode_buffer[stream->ps_current_write_buffer_index].len = 0; - return; + return 0; } - hb_log("hb_ts_stream_decode: sync lost @%lld, regained after %lld bytes", - pos, pos2 ); - for (i=0; i < stream->ts_number_video_pids + stream->ts_number_audio_pids; i++) - { - stream->ts_skipbad[i] = 1; - } + ts_warn( stream, "hb_ts_stream_decode: sync lost @%lld, " + "regained after %lld bytes", pos, pos2 ); continue; } - // Get pid - int pid = (((buf[1] & 0x1F) << 8) | buf[2]) & 0x1FFF; - - // Get the pos and buf - we organize our streams as 'n' video streams then 'm' audio streams - int index_of_selected_pid; - if ((index_of_selected_pid = index_of_video_pid(pid,stream)) < 0) - { - // Not a video PID perhaps audio ? - if ((index_of_selected_pid = index_of_audio_pid(pid,stream)) < 0) - { - // not a pid we want - continue; - } - else - { - curstream = stream->ts_number_video_pids + index_of_selected_pid; - } - } - else - curstream = index_of_selected_pid; - - // Get start code - int start; - start = (buf[1] & 0x40) != 0; - - if (!start && stream->ts_skipbad[curstream]) - continue; + // Get pid and use it to find stream state. + int pid = ((buf[1] & 0x1F) << 8) | buf[2]; + if ( ( curstream = index_of_pid( pid, stream ) ) < 0 ) + continue; // Get error int errorbit = (buf[1] & 0x80) != 0; if (errorbit) { - hb_log("hb_ts_stream_decode - Error bit set in packet"); - stream->ts_skipbad[curstream] = 1; + ts_err( stream, curstream, "packet error bit set"); continue; } // Get adaption header info int adaption = (buf[3] & 0x30) >> 4; int adapt_len = 0; - - // Get continuity - // Continuity only increments for adaption values of 0x3 or 0x01 - int continuity = (buf[3] & 0xF); - if ((stream->ts_streamcont[curstream] != -1) && ((adaption & 0x01) != 0)) - { - if (continuity != ((stream->ts_streamcont[curstream] + 1) & 0xF)) - { - hb_log("hb_ts_stream_decode - Bad continuity code in packet"); - stream->ts_skipbad[curstream] = 1; - continue; - } - stream->ts_streamcont[curstream] = continuity; - } - - // Get adaption header size if (adaption == 0) { - hb_log("hb_ts_stream_decode - Bad adaption code (code was 0)!"); - for (i=0; i < stream->ts_number_video_pids + stream->ts_number_audio_pids; i++) - { - stream->ts_skipbad[i] = 1; - } + ts_err( stream, curstream, "adaptation code 0"); continue; } else if (adaption == 0x2) @@ -2069,161 +1778,136 @@ static void hb_ts_stream_decode(hb_stream_t *stream) adapt_len = buf[4] + 1; if (adapt_len > 184) { - hb_log("hb_ts_stream_decode - Invalid adapt len %d", adapt_len); - for (i=0; i < stream->ts_number_video_pids + stream->ts_number_audio_pids; i++) - { - stream->ts_skipbad[i] = 1; - } + ts_err( stream, curstream, "invalid adapt len %d", adapt_len); + continue; } } - // HBO is slick, it doesn't bother to sync AC3 packets with PES elementary stream packets.. so - // we have to swizzle them together! (ARGHH!) - if (start && curstream >= stream->ts_number_video_pids && - stream->ts_audio_stream_type[curstream - stream->ts_number_video_pids] - != 0x03) + // if there's an adaptation header & PCR_flag is set + // get the PCR (Program Clock Reference) + if ( adapt_len > 7 && ( buf[5] & 0x10 ) != 0 ) + { + pcr = ( (uint64_t)buf[6] << (33 - 8) ) | + ( (uint64_t)buf[7] << (33 - 16) ) | + ( (uint64_t)buf[8] << (33 - 24) ) | + ( (uint64_t)buf[9] << (33 - 32) ) | + ( buf[10] >> 7 ); + stream->ts_nextpcr = pcr; + + // remember the pcr across calls to this routine + stream->ts_lastpcr = pcr; + } + + if ( pcr == -1 ) { - // Is there an AC3 packet start 0b77 code in this packet?? - int sync_found = 0; - unsigned char *p = buf + 4 + adapt_len; - while (p <= buf + 186) - { - if (p[0] == 0x0b && p[1] == 0x77) - { - sync_found = 1; - break; - } - p++; - } - - // Couldn't find an AC3 sync start in this packet.. don't make a PES packet! - if (!sync_found) - { - adapt_len = 184; - start = 0; - } + // don't accumulate data until we get a pcr + continue; } - // Found a random access point (now we can start a frame/audio packet..) - if (start) + // Get continuity + // Continuity only increments for adaption values of 0x3 or 0x01 + // and is not checked for start packets. + + int start = (buf[1] & 0x40) != 0; + + if ( (adaption & 0x01) != 0 ) { - // Check to see if this is an i_frame (group of picture start) - if (pid == stream->ts_video_pids[0]) - { - // Look for the Group of Pictures packet.. indicates this is an I-Frame packet.. - doing_iframe = 0; - unsigned int strid = 0; - int i = 4; - for (i = 4 + adapt_len; i < 188; i++) - { - strid = (strid << 8) | buf[i]; - if (strid == 0x000001B8) // group_start_code - { - // found a Group of Pictures header, subsequent picture must be an I-frame - doing_iframe = 1; - } - else if (strid == 0x000001B3) // sequence_header code - { - doing_iframe = 1; - } - else if (strid == 0x00000100) // picture_start_code - { - // picture_header, let's see if it's an I-frame - if (i<187) - { - // check if picture_coding_type == 1 - if ((buf[i+2] & (0x7 << 3)) == (1 << 3)) - { - // found an I-frame picture - doing_iframe = 1; - } - } - } - - if (doing_iframe) - { - if (!stream->ts_foundfirst[curstream]) - { - stream->ts_foundfirst[curstream] = 1; -// first_video_PCR = PCR; - } - break; - } - } - } - else if (index_of_audio_pid(pid, stream) >= 0) + int continuity = (buf[3] & 0xF); + if ( continuity == stream->ts_streamcont[curstream] ) + { + // we got a duplicate packet (usually used to introduce + // a PCR when one is needed). The only thing that can + // change in the dup is the PCR which we grabbed above + // so ignore the rest. + continue; + } + if ( !start && (stream->ts_streamcont[curstream] != -1) && + (continuity != ( (stream->ts_streamcont[curstream] + 1) & 0xf ) ) ) { - if (stream->ts_foundfirst[0]) // Set audio found first ONLY after first video frame found. There's an assumption here that stream '0' is a video stream - { - stream->ts_foundfirst[curstream] |= 1; - } + ts_err( stream, curstream, "continuity error: got %d expected %d", + (int)continuity, + (stream->ts_streamcont[curstream] + 1) & 0xf ); + stream->ts_streamcont[curstream] = continuity; + continue; } + stream->ts_streamcont[curstream] = continuity; + } + + /* If we get here the packet is valid - process its data */ + + if ( start ) + { + // Found a random access point (now we can start a frame/audio packet..) // If we were skipping a bad packet, start fresh on this new PES packet.. if (stream->ts_skipbad[curstream] == 1) { + // video skips to an iframe after a bad packet to minimize + // screen corruption + if ( curstream == 0 && !isIframe( buf, adapt_len ) ) + { + continue; + } stream->ts_skipbad[curstream] = 0; - stream->ts_packetpos[curstream] = 0; } - // Get the continuity code of this packet - stream->ts_streamcont[curstream] = continuity; - } - - // Write a 2048 byte program stream packet.. - if (start && stream->ts_packetpos[curstream] > 0 && stream->ts_foundfirst[curstream] && !stream->ts_skipbad[curstream]) - { - // Save the substream id block so we can added it to subsequent blocks - int write_ac3 = 0; - if (curstream >= stream->ts_number_video_pids) - { - // Curstream is a zero based index of streams and includes both video and audio streams, so we must subtract the numver of video streams - // from the indes value used here since ts_audio_stream_type is indexed only by audio streams. - if (stream->ts_audio_stream_type[curstream - stream->ts_number_video_pids] == 0x03) - { - hb_ts_handle_mpeg_audio(stream, curstream, buf, adapt_len); - } - else - { - write_ac3 = hb_ts_handle_ac3_audio(stream, curstream, buf, adapt_len); - } + // If we don't have video yet, check to see if this is an + // i_frame (group of picture start) + if ( curstream == 0 ) + { + if ( !stream->ts_foundfirst[0] ) + { + if ( !isIframe( buf, adapt_len ) ) + { + // didn't find an I frame + continue; + } + stream->ts_foundfirst[0] = 1; + } + ++stream->frames; + } + else if ( ! stream->ts_foundfirst[curstream] ) + { + // start other streams only after first video frame found. + if ( ! stream->ts_foundfirst[0] ) + { + continue; + } + stream->ts_foundfirst[curstream] = 1; } - if (generate_output_data(stream, write_ac3, curstream, pid) != 0) - return ; - } + // If we have some data already on this stream, turn it into + // a program stream packet. Then add the payload for this + // packet to the current pid's buffer. + if ( stream->ts_pos[curstream] ) + { + generate_output_data(stream, curstream); + stream->ts_start[curstream] = 1; + memcpy(stream->ts_buf[curstream], + buf + 4 + adapt_len, 184 - adapt_len); + stream->ts_pos[curstream] = 184 - adapt_len; + return 1; + } + stream->ts_start[curstream] = 1; + } // Add the payload for this packet to the current buffer if (!stream->ts_skipbad[curstream] && stream->ts_foundfirst[curstream] && (184 - adapt_len) > 0) { - if (stream->ts_packetpos[curstream] + 184 - adapt_len > 1024*1024) + memcpy(stream->ts_buf[curstream] + stream->ts_pos[curstream], + buf + 4 + adapt_len, 184 - adapt_len); + stream->ts_pos[curstream] += 184 - adapt_len; + + // if the next TS packet could possibly overflow our 2K output buffer + // we need to generate a packet now. Overflow would be 184 bytes of + // data + the 9 byte PES hdr + the 14 byte PACK hdr = 211 bytes. + if ( stream->ts_pos[curstream] >= (HB_DVD_READ_BUFFER_SIZE - 216) ) { - int aindx = curstream - stream->ts_number_video_pids; - if ( aindx >= 0 && stream->ts_audio_stream_type[aindx] == 0x81) - { - /* we've searched through a megabyte & didn't find an AC3 - * sync frame so this probably isn't AC3. (DVB standard - * teletext uses the same code points as ATSC AC3 so we - * could easily have guessed wrong.) Delete this pid from - * the audio list so we don't waste any more time on it. */ - hb_log("hb_ts_stream_decode: removing pid 0x%x - " - "it isn't an AC3 stream.", stream->ts_audio_pids[aindx]); - hb_stream_delete_audio_entry( stream, aindx ); - } - else - { - hb_log("hb_ts_stream_decode: pid 0x%x ts_packetbuf overflow " - "pos %d len = %d", - aindx < 0 ? stream->ts_video_pids[curstream] : - stream->ts_audio_pids[aindx], - stream->ts_packetpos[curstream], 184 - adapt_len ); - } - stream->ts_packetpos[curstream] = 0; - continue; + // we have enough data to make a PS packet + generate_output_data(stream, curstream); + return 1; } - memcpy(stream->ts_packetbuf[curstream] + stream->ts_packetpos[curstream], buf + 4 + adapt_len, 184 - adapt_len); - stream->ts_packetpos[curstream] += 184 - adapt_len; } } } @@ -2235,21 +1919,24 @@ static void hb_ts_stream_decode(hb_stream_t *stream) **********************************************************************/ static void hb_ts_stream_reset(hb_stream_t *stream) { - int i=0; - for (i=0; i < kNumDecodeBuffers; i++) - { - stream->ps_decode_buffer[i].read_pos = 0; - stream->ps_decode_buffer[i].write_pos = 0; - stream->ps_decode_buffer[i].len = 0; - } + int i; for (i=0; i < kMaxNumberDecodeStreams; i++) { + stream->ts_pos[i] = 0; + stream->ts_foundfirst[i] = 0; + stream->ts_skipbad[i] = 0; stream->ts_streamcont[i] = -1; + stream->ts_start[i] = 0; } - stream->ps_current_write_buffer_index = 0; - stream->ps_current_read_buffer_index = 1; + stream->ts_lastpcr = -1; + stream->ts_nextpcr = -1; + + stream->frames = 0; + stream->errors = 0; + stream->last_error_frame = -10000; + stream->last_error_count = 0; align_to_next_packet(stream->file_handle); } diff --git a/libhb/sync.c b/libhb/sync.c index 04bdf162f..460d175ff 100644 --- a/libhb/sync.c +++ b/libhb/sync.c @@ -19,7 +19,13 @@ typedef struct { hb_audio_t * audio; - int64_t count_frames; + + int64_t next_start; /* start time of next output frame */ + int64_t next_pts; /* start time of next input frame */ + int64_t start_silence; /* if we're inserting silence, the time we started */ + int64_t first_drop; /* PTS of first 'went backwards' frame dropped */ + int drop_count; /* count of 'time went backwards' drops */ + int inserting_silence; /* Raw */ SRC_STATE * state; @@ -39,28 +45,22 @@ struct hb_work_private_s /* Video */ hb_subtitle_t * subtitle; int64_t pts_offset; - int64_t pts_offset_old; - int64_t next_start; - int64_t count_frames; - int64_t count_frames_max; - int64_t video_sequence; + int64_t next_start; /* start time of next output frame */ + int64_t next_pts; /* start time of next input frame */ + int64_t first_drop; /* PTS of first 'went backwards' frame dropped */ + int drop_count; /* count of 'time went backwards' drops */ + int video_sequence; + int count_frames; + int count_frames_max; hb_buffer_t * cur; /* The next picture to process */ /* Audio */ hb_sync_audio_t sync_audio[8]; - /* Flags */ - int discontinuity; - /* Statistics */ uint64_t st_counts[4]; uint64_t st_dates[4]; uint64_t st_first; - - /* Throttle message flags */ - int trashing_audio; - int inserting_silence; - int way_out_of_sync; }; /*********************************************************************** @@ -69,8 +69,8 @@ struct hb_work_private_s static void InitAudio( hb_work_object_t * w, int i ); static int SyncVideo( hb_work_object_t * w ); static void SyncAudio( hb_work_object_t * w, int i ); -static int NeedSilence( hb_work_object_t * w, hb_audio_t * ); -static void InsertSilence( hb_work_object_t * w, int i ); +static int NeedSilence( hb_work_object_t * w, hb_audio_t *, int i ); +static void InsertSilence( hb_work_object_t * w, int i, int64_t d ); static void UpdateState( hb_work_object_t * w ); /*********************************************************************** @@ -91,15 +91,8 @@ int syncInit( hb_work_object_t * w, hb_job_t * job ) pv->job = job; pv->pts_offset = INT64_MIN; - pv->pts_offset_old = INT64_MIN; pv->count_frames = 0; - pv->discontinuity = 0; - - pv->trashing_audio = 0; - pv->inserting_silence = 0; - pv->way_out_of_sync = 0; - /* Calculate how many video frames we are expecting */ duration = 0; for( i = job->chapter_start; i <= job->chapter_end; i++ ) @@ -111,7 +104,7 @@ int syncInit( hb_work_object_t * w, hb_job_t * job ) /* 1 second safety so we're sure we won't miss anything */ pv->count_frames_max = duration * job->vrate / job->vrate_base / 90000; - hb_log( "sync: expecting %lld video frames", pv->count_frames_max ); + hb_log( "sync: expecting %d video frames", pv->count_frames_max ); /* Initialize libsamplerate for every audio track we have */ for( i = 0; i < hb_list_count( title->list_audio ); i++ ) @@ -144,6 +137,13 @@ void syncClose( hb_work_object_t * w ) for( i = 0; i < hb_list_count( title->list_audio ); i++ ) { + if ( pv->sync_audio[i].start_silence ) + { + hb_log( "sync: added %d ms of silence to audio %d", + (int)((pv->sync_audio[i].next_pts - + pv->sync_audio[i].start_silence) / 90), i ); + } + if( job->acodec & HB_ACODEC_AC3 || job->audio_mixdowns[i] == HB_AMIXDOWN_AC3 ) { @@ -254,10 +254,6 @@ static void InitAudio( hb_work_object_t * w, int i ) } } - - -#define PTS_DISCONTINUITY_TOLERANCE 90000 - /*********************************************************************** * SyncVideo *********************************************************************** @@ -268,8 +264,6 @@ static int SyncVideo( hb_work_object_t * w ) hb_work_private_t * pv = w->private_data; hb_buffer_t * cur, * next, * sub = NULL; hb_job_t * job = pv->job; - int64_t pts_expected; - int chap_break; if( pv->done ) { @@ -282,7 +276,7 @@ static int SyncVideo( hb_work_object_t * w ) { /* All video data has been processed already, we won't get more */ - hb_log( "sync: got %lld frames, %lld expected", + hb_log( "sync: got %d frames, %d expected", pv->count_frames, pv->count_frames_max ); pv->done = 1; @@ -306,7 +300,7 @@ static int SyncVideo( hb_work_object_t * w ) /* At this point we have a frame to process. Let's check 1) if we will be able to push into the fifo ahead 2) if the next frame is there already, since we need it to - know whether we'll have to repeat the current frame or not */ + compute the duration of the current frame*/ while( !hb_fifo_is_full( job->fifo_sync ) && ( next = hb_fifo_see( job->fifo_raw ) ) ) { @@ -315,73 +309,59 @@ static int SyncVideo( hb_work_object_t * w ) if( pv->pts_offset == INT64_MIN ) { /* This is our first frame */ - hb_log( "sync: first pts is %lld", cur->start ); - pv->pts_offset = cur->start; + pv->pts_offset = 0; + if ( cur->start != 0 ) + { + /* + * The first pts from a dvd should always be zero but + * can be non-zero with a transport or program stream since + * we're not guaranteed to start on an IDR frame. If we get + * a non-zero initial PTS extend its duration so it behaves + * as if it started at zero so that our audio timing will + * be in sync. + */ + hb_log( "sync: first pts is %lld", cur->start ); + cur->start = 0; + } } /* - * Track the video sequence number localy so that we can sync the audio - * to it using the sequence number as well as the PTS. + * since the first frame is always 0 and the upstream reader code + * is taking care of adjusting for pts discontinuities, we just have + * to deal with the next frame's start being in the past. This can + * happen when the PTS is adjusted after data loss but video frame + * reordering causes some frames with the old clock to appear after + * the clock change. This creates frames that overlap in time which + * looks to us like time going backward. The downstream muxing code + * can deal with overlaps of up to a frame time but anything larger + * we handle by dropping frames here. */ - pv->video_sequence = cur->sequence; - - /* Check for PTS jumps over 0.5 second */ - if( next->start < cur->start - PTS_DISCONTINUITY_TOLERANCE || - next->start > cur->start + PTS_DISCONTINUITY_TOLERANCE ) + if ( pv->next_pts - next->start > 1000 ) { - hb_log( "Sync: Video PTS discontinuity %s (current buffer start=%lld, next buffer start=%lld)", - pv->discontinuity ? "second" : "first", cur->start, next->start ); - - /* - * Do we need to trash the subtitle, is it from the next->start period - * or is it from our old position. If the latter then trash it. - */ - if( pv->subtitle ) + if ( pv->first_drop == 0 ) { - while( ( sub = hb_fifo_see( pv->subtitle->fifo_raw ) ) ) - { - if( ( sub->start > ( cur->start - PTS_DISCONTINUITY_TOLERANCE ) ) && - ( sub->start < ( cur->start + PTS_DISCONTINUITY_TOLERANCE ) ) ) - { - /* - * The subtitle is from our current time region which we are - * jumping from. So trash it as we are about to jump backwards - * or forwards and don't want it blocking the subtitle fifo. - */ - hb_log("Trashing subtitle 0x%x due to PTS discontinuity", sub); - sub = hb_fifo_get( pv->subtitle->fifo_raw ); - hb_buffer_close( &sub ); - } else { - break; - } - } - } - - /* Trash current picture */ - /* Also, make sure we don't trash a chapter break */ - chap_break = cur->new_chap; - hb_buffer_close( &cur ); - pv->cur = cur = hb_fifo_get( job->fifo_raw ); - cur->new_chap |= chap_break; // Don't stomp existing chapter breaks - - /* Calculate new offset */ - pv->pts_offset_old = pv->pts_offset; - if ( job->vfr ) - { - pv->pts_offset = cur->start - pv->next_start; - } else { - pv->pts_offset = cur->start - - pv->count_frames * pv->job->vrate_base / 300; - } - - if( !pv->discontinuity ) - { - pv->discontinuity = 1; + pv->first_drop = next->start; } - - pv->video_sequence = cur->sequence; + ++pv->drop_count; + buf_tmp = hb_fifo_get( job->fifo_raw ); + hb_buffer_close( &buf_tmp ); continue; } + if ( pv->first_drop ) + { + hb_log( "sync: video time went backwards %d ms, dropped %d frames " + "(frame %lld, expected %lld)", + (int)( pv->next_pts - pv->first_drop ) / 90, pv->drop_count, + pv->first_drop, pv->next_pts ); + pv->first_drop = 0; + pv->drop_count = 0; + } + + /* + * Track the video sequence number localy so that we can sync the audio + * to it using the sequence number as well as the PTS. + */ + pv->video_sequence = cur->sequence; /* Look for a subtitle for this frame */ if( pv->subtitle ) @@ -418,23 +398,6 @@ static int SyncVideo( hb_work_object_t * w ) */ break; } - else - { - /* - * The stop time is in the past. But is it due to - * it having been played already, or has the PTS - * been reset to 0? - */ - if( ( cur->start - sub->stop ) > PTS_DISCONTINUITY_TOLERANCE ) { - /* - * There is a lot of time between our current - * video and where this subtitle is ending, - * assume that we are about to reset the PTS - * and do not throw away this subtitle. - */ - break; - } - } /* * The subtitle is older than this picture, trash it @@ -533,72 +496,26 @@ static int SyncVideo( hb_work_object_t * w ) } } - if ( job->vfr ) - { - /* - * adjust the pts of the current frame so that it's contiguous - * with the previous frame. pts_offset tracks the time difference - * between the pts values in the input content (which start at some - * random time) and our timestamps (which start at zero). We don't - * make any adjustments to the source timestamps other than removing - * the clock offsets (which also removes pts discontinuities). - * This means we automatically encode at the source's frame rate. - * MP2 uses an implicit duration (frames end when the next frame - * starts) but more advanced containers like MP4 use an explicit - * duration. Since we're looking ahead one frame we set the - * explicit stop time from the start time of the next frame. - */ - buf_tmp = cur; - pv->cur = cur = hb_fifo_get( job->fifo_raw ); - buf_tmp->start = pv->next_start; - pv->next_start = next->start - pv->pts_offset; - buf_tmp->stop = pv->next_start; - } - else - { - /* The PTS of the frame we are expecting now */ - pts_expected = pv->pts_offset + - pv->count_frames * pv->job->vrate_base / 300; - - //hb_log("Video expecting PTS %lld, current frame: %lld, next frame: %lld, cf: %lld", - // pts_expected, cur->start, next->start, pv->count_frames * pv->job->vrate_base / 300 ); - - if( cur->start < pts_expected - pv->job->vrate_base / 300 / 2 && - next->start < pts_expected + pv->job->vrate_base / 300 / 2 ) - { - /* The current frame is too old but the next one matches, - let's trash */ - /* Also, make sure we don't trash a chapter break */ - chap_break = cur->new_chap; - hb_buffer_close( &cur ); - pv->cur = cur = hb_fifo_get( job->fifo_raw ); - cur->new_chap |= chap_break; // Make sure we don't stomp the existing one. - - continue; - } - - if( next->start > pts_expected + 3 * pv->job->vrate_base / 300 / 2 ) - { - /* We'll need the current frame more than one time. Make a - copy of it and keep it */ - buf_tmp = hb_buffer_init( cur->size ); - memcpy( buf_tmp->data, cur->data, cur->size ); - buf_tmp->sequence = cur->sequence; - } - else - { - /* The frame has the expected date and won't have to be - duplicated, just put it through */ - buf_tmp = cur; - pv->cur = cur = hb_fifo_get( job->fifo_raw ); - } - - /* Replace those MPEG-2 dates with our dates */ - buf_tmp->start = (uint64_t) pv->count_frames * - pv->job->vrate_base / 300; - buf_tmp->stop = (uint64_t) ( pv->count_frames + 1 ) * - pv->job->vrate_base / 300; - } + /* + * Adjust the pts of the current frame so that it's contiguous + * with the previous frame. The start time of the current frame + * has to be the end time of the previous frame and the stop + * time has to be the start of the next frame. We don't + * make any adjustments to the source timestamps other than removing + * the clock offsets (which also removes pts discontinuities). + * This means we automatically encode at the source's frame rate. + * MP2 uses an implicit duration (frames end when the next frame + * starts) but more advanced containers like MP4 use an explicit + * duration. Since we're looking ahead one frame we set the + * explicit stop time from the start time of the next frame. + */ + buf_tmp = cur; + pv->cur = cur = hb_fifo_get( job->fifo_raw ); + pv->next_pts = next->start; + int64_t duration = next->start - buf_tmp->start; + buf_tmp->start = pv->next_start; + pv->next_start += duration; + buf_tmp->stop = pv->next_start; /* If we have a subtitle for this picture, copy it */ /* FIXME: we should avoid this memcpy */ @@ -621,7 +538,7 @@ static int SyncVideo( hb_work_object_t * w ) /* Make sure we won't get more frames then expected */ if( pv->count_frames >= pv->count_frames_max * 2) { - hb_log( "sync: got too many frames (%lld), exiting early", pv->count_frames ); + hb_log( "sync: got too many frames (%d), exiting early", pv->count_frames ); pv->done = 1; // Drop an empty buffer into our output to ensure that things @@ -636,6 +553,68 @@ static int SyncVideo( hb_work_object_t * w ) return HB_WORK_OK; } +static void OutputAudioFrame( hb_job_t *job, hb_audio_t *audio, hb_buffer_t *buf, + hb_sync_audio_t *sync, hb_fifo_t *fifo, int i ) +{ + int64_t start = sync->next_start; + int64_t duration = buf->stop - buf->start; + if (duration <= 0 || + duration > ( 90000 * AC3_SAMPLES_PER_FRAME ) / audio->rate ) + { + hb_log("sync: audio %d weird duration %lld, start %lld, stop %lld, next %lld", + i, duration, buf->start, buf->stop, sync->next_pts); + if ( duration <= 0 ) + { + duration = ( 90000 * AC3_SAMPLES_PER_FRAME ) / audio->rate; + buf->stop = buf->start + duration; + } + } + sync->next_pts += duration; + + if( /* audio->rate == job->arate || This should work but doesn't */ + job->acodec & HB_ACODEC_AC3 || + job->audio_mixdowns[i] == HB_AMIXDOWN_AC3 ) + { + /* + * If we don't have to do sample rate conversion or this audio is AC3 + * pass-thru just send the input buffer downstream after adjusting + * its timestamps to make the output stream continuous. + */ + } + else + { + /* Not pass-thru - do sample rate conversion */ + int count_in, count_out; + hb_buffer_t * buf_raw = buf; + int channel_count = HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->amixdown) * + sizeof( float ); + + count_in = buf_raw->size / channel_count; + count_out = ( buf_raw->stop - buf_raw->start ) * job->arate / 90000; + + sync->data.input_frames = count_in; + sync->data.output_frames = count_out; + sync->data.src_ratio = (double)count_out / (double)count_in; + + buf = hb_buffer_init( count_out * channel_count ); + sync->data.data_in = (float *) buf_raw->data; + sync->data.data_out = (float *) buf->data; + if( src_process( sync->state, &sync->data ) ) + { + /* XXX If this happens, we're screwed */ + hb_log( "sync: audio %d src_process failed", i ); + } + hb_buffer_close( &buf_raw ); + + buf->size = sync->data.output_frames_gen * channel_count; + } + buf->start = start; + buf->stop = start + duration; + buf->frametype = HB_FRAME_AUDIO; + sync->next_start = start + duration; + hb_fifo_push( fifo, buf ); +} + /*********************************************************************** * SyncAudio *********************************************************************** @@ -644,21 +623,13 @@ static int SyncVideo( hb_work_object_t * w ) static void SyncAudio( hb_work_object_t * w, int i ) { hb_work_private_t * pv = w->private_data; - hb_job_t * job; - hb_audio_t * audio; + hb_job_t * job = pv->job; + hb_sync_audio_t * sync = &pv->sync_audio[i]; + hb_audio_t * audio = sync->audio; hb_buffer_t * buf; - hb_sync_audio_t * sync; - hb_fifo_t * fifo; int rate; - int64_t pts_expected; - int64_t start; - - job = pv->job; - sync = &pv->sync_audio[i]; - audio = sync->audio; - if( job->acodec & HB_ACODEC_AC3 || job->audio_mixdowns[i] == HB_AMIXDOWN_AC3 ) { @@ -671,230 +642,90 @@ static void SyncAudio( hb_work_object_t * w, int i ) rate = job->arate; } - while( !hb_fifo_is_full( fifo ) && - ( buf = hb_fifo_see( audio->fifo_raw ) ) ) + while( !hb_fifo_is_full( fifo ) && ( buf = hb_fifo_see( audio->fifo_raw ) ) ) { - /* The PTS of the samples we are expecting now */ - pts_expected = pv->pts_offset + sync->count_frames * 90000 / rate; - - // hb_log("Video Sequence: %lld, Audio Sequence: %lld", pv->video_sequence, buf->sequence); - - /* - * Using the same logic as the Video have we crossed a VOB - * boundary as detected by the expected PTS and the PTS of our - * audio being out by more than the tolerance value. - */ - if( buf->start > pts_expected + PTS_DISCONTINUITY_TOLERANCE || - buf->start < pts_expected - PTS_DISCONTINUITY_TOLERANCE ) + if ( sync->next_pts - buf->start > 500 ) { - /* There has been a PTS discontinuity, and this frame might - be from before the discontinuity*/ - - if( pv->discontinuity ) - { - /* - * There is an outstanding discontinuity, so use the offset from - * that discontinuity. - */ - pts_expected = pv->pts_offset_old + sync->count_frames * - 90000 / rate; - } - else - { - /* - * No outstanding discontinuity, so the audio must be leading the - * video (or the PTS values are really stuffed). So lets mark this - * as a discontinuity ourselves for the audio to use until - * the video also crosses the discontinuity. - * - * pts_offset is used when we are in the same time space as the video - * pts_offset_old when in a discontinuity. - * - * Therefore set the pts_offset_old given the new pts_offset for this - * current buffer. - */ - pv->discontinuity = 1; - pv->pts_offset_old = buf->start - sync->count_frames * - 90000 / rate; - pts_expected = pv->pts_offset_old + sync->count_frames * - 90000 / rate; - - hb_log("Sync: Audio discontinuity (sequence: vid %lld aud %lld) (pts %lld < %lld < %lld)", - pv->video_sequence, buf->sequence, - pts_expected - PTS_DISCONTINUITY_TOLERANCE, buf->start, - pts_expected + PTS_DISCONTINUITY_TOLERANCE ); - } - /* - * Is the audio from a valid period given the previous - * Video PTS. I.e. has there just been a video PTS - * discontinuity and this audio belongs to the vdeo from - * before? + * audio time went backwards by more than a frame time (this can + * happen when we reset the PTS because of lost data). + * Discard data that's in the past. */ - if( buf->start > pts_expected + PTS_DISCONTINUITY_TOLERANCE || - buf->start < pts_expected - PTS_DISCONTINUITY_TOLERANCE ) + if ( sync->first_drop == 0 ) { - /* - * It's outside of our tolerance for where the video - * is now, and it's outside of the tolerance for - * where we have been in the case of a VOB change. - * Try and reconverge regardless. so continue on to - * our convergence code below which will kick in as - * it will be more than 100ms out. - * - * Note that trashing the Audio could make things - * worse if the Audio is in front because we will end - * up diverging even more. We need to hold on to the - * audio until the video catches up. - */ - if( !pv->way_out_of_sync ) - { - hb_log("Sync: Audio is way out of sync, attempt to reconverge from current video PTS"); - pv->way_out_of_sync = 1; - } - - /* - * It wasn't from the old place, so we must be from - * the new, but just too far out. So attempt to - * reconverge by resetting the point we want to be to - * where we are currently wanting to be. - */ - pts_expected = pv->pts_offset + sync->count_frames * 90000 / rate; - start = pts_expected - pv->pts_offset; - } else { - /* Use the older offset */ - start = pts_expected - pv->pts_offset_old; - } - } - else - { - start = pts_expected - pv->pts_offset; - - if( pv->discontinuity ) - { - /* - * The Audio is tracking the Video again using the normal pts_offset, so the - * discontinuity is over. - */ - hb_log( "Sync: Audio joined Video after discontinuity at PTS %lld", buf->start ); - pv->discontinuity = 0; - } - } - - /* Tolerance: 100 ms */ - if( buf->start < pts_expected - 9000 ) - { - if( !pv->trashing_audio ) - { - /* Audio is behind the Video, trash it, can't use it now. */ - hb_log( "Sync: Audio PTS (%lld) < Video PTS (%lld) by greater than 100ms, trashing audio to reconverge", - buf->start, pts_expected); - pv->trashing_audio = 1; + sync->first_drop = buf->start; } + ++sync->drop_count; buf = hb_fifo_get( audio->fifo_raw ); hb_buffer_close( &buf ); continue; } - else if( buf->start > pts_expected + 9000 ) + if ( sync->first_drop ) { - /* Audio is ahead of the Video, insert silence until we catch up*/ - if( !pv->inserting_silence ) - { - hb_log("Sync: Audio PTS (%lld) > Video PTS (%lld) by greater than 100ms insert silence until reconverged", buf->start, pts_expected); - pv->inserting_silence = 1; - } - InsertSilence( w, i ); - continue; + hb_log( "sync: audio %d time went backwards %d ms, dropped %d frames " + "(frame %lld, expected %lld)", i, + (int)( sync->next_pts - sync->first_drop ) / 90, + sync->drop_count, sync->first_drop, sync->next_pts ); + sync->first_drop = 0; + sync->drop_count = 0; } - else + + if ( sync->inserting_silence && buf->start - sync->next_pts > 0 ) { - if( pv->trashing_audio || pv->inserting_silence ) + /* + * if we're within one frame time of the amount of silence + * we need, insert just what we need otherwise insert a frame time. + */ + int64_t framedur = buf->stop - buf->start; + if ( buf->start - sync->next_pts <= framedur ) { - hb_log( "Sync: Audio back in Sync at PTS %lld", buf->start ); - pv->trashing_audio = 0; - pv->inserting_silence = 0; + InsertSilence( w, i, buf->start - sync->next_pts ); + sync->inserting_silence = 0; } - if( pv->way_out_of_sync ) + else { - hb_log( "Sync: Audio no longer way out of sync at PTS %lld", - buf->start ); - pv->way_out_of_sync = 0; + InsertSilence( w, i, framedur ); } + continue; } - - if( job->acodec & HB_ACODEC_AC3 || - job->audio_mixdowns[i] == HB_AMIXDOWN_AC3 ) - { - buf = hb_fifo_get( audio->fifo_raw ); - buf->start = start; - buf->stop = start + 90000 * AC3_SAMPLES_PER_FRAME / rate; - - sync->count_frames += AC3_SAMPLES_PER_FRAME; - } - else + if ( buf->start - sync->next_pts >= (90 * 100) ) { - hb_buffer_t * buf_raw = hb_fifo_get( audio->fifo_raw ); - - int count_in, count_out; - - count_in = buf_raw->size / HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->amixdown) / sizeof( float ); - count_out = ( buf_raw->stop - buf_raw->start ) * job->arate / 90000; - if( buf->start < pts_expected - 1500 ) - count_out--; - else if( buf->start > pts_expected + 1500 ) - count_out++; - - sync->data.data_in = (float *) buf_raw->data; - sync->data.input_frames = count_in; - sync->data.output_frames = count_out; - - sync->data.src_ratio = (double) sync->data.output_frames / - (double) sync->data.input_frames; - - buf = hb_buffer_init( sync->data.output_frames * HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->amixdown) * - sizeof( float ) ); - sync->data.data_out = (float *) buf->data; - if( src_process( sync->state, &sync->data ) ) + /* + * there's a gap of at least 100ms between the last + * frame we processed & the next. Fill it with silence. + */ + if ( ! sync->inserting_silence ) { - /* XXX If this happens, we're screwed */ - hb_log( "sync: src_process failed" ); + hb_log( "sync: adding %d ms of silence to audio %d" + " start %lld, next %lld", + (int)((buf->start - sync->next_pts) / 90), + i, buf->start, sync->next_pts ); + sync->inserting_silence = 1; } - hb_buffer_close( &buf_raw ); - - buf->size = sync->data.output_frames_gen * HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->amixdown) * sizeof( float ); - - /* Set dates for resampled data */ - buf->start = start; - buf->stop = start + sync->data.output_frames_gen * - 90000 / job->arate; - - sync->count_frames += sync->data.output_frames_gen; + InsertSilence( w, i, buf->stop - buf->start ); + continue; } - buf->frametype = HB_FRAME_AUDIO; - hb_fifo_push( fifo, buf ); - } - - if( hb_fifo_is_full( fifo ) && - pv->way_out_of_sync ) - { /* - * Trash the top audio packet to avoid dead lock as we reconverge. + * When we get here we've taken care of all the dups and gaps in the + * audio stream and are ready to inject the next input frame into + * the output stream. */ - if ( (buf = hb_fifo_get( audio->fifo_raw ) ) != NULL) - hb_buffer_close( &buf ); + buf = hb_fifo_get( audio->fifo_raw ); + OutputAudioFrame( job, audio, buf, sync, fifo, i ); } - if( NeedSilence( w, audio ) ) + if( NeedSilence( w, audio, i ) ) { - InsertSilence( w, i ); + InsertSilence( w, i, (90000 * AC3_SAMPLES_PER_FRAME) / sync->audio->rate ); } } -static int NeedSilence( hb_work_object_t * w, hb_audio_t * audio ) +static int NeedSilence( hb_work_object_t * w, hb_audio_t * audio, int i ) { hb_work_private_t * pv = w->private_data; hb_job_t * job = pv->job; + hb_sync_audio_t * sync = &pv->sync_audio[i]; if( hb_fifo_size( audio->fifo_in ) || hb_fifo_size( audio->fifo_raw ) || @@ -911,7 +742,11 @@ static int NeedSilence( hb_work_object_t * w, hb_audio_t * audio ) { /* We might miss some audio to complete encoding and muxing the video track */ - hb_log("Reader has exited early, inserting silence."); + if ( sync->start_silence == 0 ) + { + hb_log("sync: reader has exited, adding silence to audio %d", i); + sync->start_silence = sync->next_pts; + } return 1; } @@ -921,51 +756,47 @@ static int NeedSilence( hb_work_object_t * w, hb_audio_t * audio ) hb_fifo_is_full( job->fifo_render ) && hb_fifo_is_full( job->fifo_mpeg4 ) ) { - /* Too much video and no audio, oh-oh */ - hb_log("Still got some video - and nothing in the audio fifo, insert silence"); + if ( sync->start_silence == 0 ) + { + /* Too much video and no audio, oh-oh */ + hb_log("sync: have video but no audio, adding silence to audio %d", i); + sync->start_silence = sync->next_pts; + } return 1; } + if ( sync->start_silence ) + { + hb_log( "sync: added %d ms of silence to audio %d", + (int)((sync->next_pts - sync->start_silence) / 90), i ); + sync->start_silence = 0; + } return 0; } -static void InsertSilence( hb_work_object_t * w, int i ) +static void InsertSilence( hb_work_object_t * w, int i, int64_t duration ) { hb_work_private_t * pv = w->private_data; - hb_job_t * job; - hb_sync_audio_t * sync; - hb_buffer_t * buf; + hb_job_t *job = pv->job; + hb_sync_audio_t *sync = &pv->sync_audio[i]; + hb_buffer_t *buf; - job = pv->job; - sync = &pv->sync_audio[i]; - - if( job->acodec & HB_ACODEC_AC3 || - job->audio_mixdowns[i] == HB_AMIXDOWN_AC3 ) + if( job->acodec & HB_ACODEC_AC3 || job->audio_mixdowns[i] == HB_AMIXDOWN_AC3 ) { buf = hb_buffer_init( sync->ac3_size ); - buf->start = sync->count_frames * 90000 / sync->audio->rate; - buf->stop = buf->start + 90000 * AC3_SAMPLES_PER_FRAME / - sync->audio->rate; + buf->start = sync->next_pts; + buf->stop = buf->start + duration; memcpy( buf->data, sync->ac3_buf, buf->size ); - - hb_log( "sync: adding a silent AC-3 frame for track %x", - sync->audio->id ); - hb_fifo_push( sync->audio->fifo_out, buf ); - - sync->count_frames += AC3_SAMPLES_PER_FRAME; - + OutputAudioFrame( job, sync->audio, buf, sync, sync->audio->fifo_out, i ); } else { - buf = hb_buffer_init( HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(sync->audio->amixdown) * job->arate / 20 * - sizeof( float ) ); - buf->start = sync->count_frames * 90000 / job->arate; - buf->stop = buf->start + 90000 / 20; + buf = hb_buffer_init( duration * sizeof( float ) * + HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(sync->audio->amixdown) ); + buf->start = sync->next_pts; + buf->stop = buf->start + duration; memset( buf->data, 0, buf->size ); - - hb_fifo_push( sync->audio->fifo_sync, buf ); - - sync->count_frames += job->arate / 20; + OutputAudioFrame( job, sync->audio, buf, sync, sync->audio->fifo_sync, i ); } } |