diff options
author | jstebbins <[email protected]> | 2015-04-22 21:12:21 +0000 |
---|---|---|
committer | jstebbins <[email protected]> | 2015-04-22 21:12:21 +0000 |
commit | ddcecd907b532f867a5c7a1aa89ea6909205caab (patch) | |
tree | 3c2deafb87344c300243f11609366998c3d246be | |
parent | c48e55d5563dc05aed1652a34bc5359376aed2a7 (diff) |
stream: Improve transport stream PCR handling
When a new PCR is seen, emit all currently cached data. This prevents
data that was referenced to the previous PCR from being associated with
the new PCR.
git-svn-id: svn://svn.handbrake.fr/HandBrake/trunk@7120 b64f7644-9d1e-0410-96f1-a4d463321fa5
-rw-r--r-- | libhb/bd.c | 12 | ||||
-rw-r--r-- | libhb/demuxmpeg.c | 106 | ||||
-rw-r--r-- | libhb/internal.h | 4 | ||||
-rw-r--r-- | libhb/stream.c | 502 |
4 files changed, 338 insertions, 286 deletions
diff --git a/libhb/bd.c b/libhb/bd.c index abe13fa5d..86a956675 100644 --- a/libhb/bd.c +++ b/libhb/bd.c @@ -698,7 +698,7 @@ hb_buffer_t * hb_bd_read( hb_bd_t * d ) uint8_t buf[192]; BD_EVENT event; uint64_t pos; - hb_buffer_t * b; + hb_buffer_t * out = NULL; uint8_t discontinuity; int new_chap = 0; @@ -753,13 +753,9 @@ hb_buffer_t * hb_bd_read( hb_bd_t * d ) } } // buf+4 to skip the BD timestamp at start of packet - b = hb_ts_decode_pkt( d->stream, buf+4 ); - if ( b ) - { - b->s.discontinuity = discontinuity; - b->s.new_chap = new_chap; - return b; - } + out = hb_ts_decode_pkt( d->stream, buf+4, new_chap, discontinuity ); + if (out != NULL) + return out; } return NULL; } diff --git a/libhb/demuxmpeg.c b/libhb/demuxmpeg.c index 988b1ca5c..32e0d6387 100644 --- a/libhb/demuxmpeg.c +++ b/libhb/demuxmpeg.c @@ -9,7 +9,7 @@ #include "hb.h" -static inline void check_mpeg_scr( hb_psdemux_t *state, int64_t scr, int tol ) +static inline int check_mpeg_scr( hb_psdemux_t *state, int64_t scr, int tol ) { /* * This section of code implements the timing model of @@ -38,14 +38,17 @@ static inline void check_mpeg_scr( hb_psdemux_t *state, int64_t scr, int tol ) // we declare a discontinuity if there's a gap of more than // 'tol'ms between the last scr & this or if this scr goes back // by more than half a frame time. + int discontinuity = 0; int64_t scr_delta = scr - state->last_scr; if (state->last_scr == AV_NOPTS_VALUE || scr_delta > 90*tol || scr_delta < -90*10) { ++state->scr_changes; state->last_pts = AV_NOPTS_VALUE; + discontinuity = 1; } state->last_scr = scr; + return discontinuity; } static inline void save_chap( hb_psdemux_t *state, hb_buffer_t *buf ) @@ -246,64 +249,78 @@ void hb_demux_dvd_ps( hb_buffer_t * buf, hb_list_t * list_es, hb_psdemux_t* stat // start contains the pts (if any), renderOffset contains the dts (if any) // and stop contains the pcr (if it changed). void hb_demux_mpeg(hb_buffer_t *buf, hb_list_t *list_es, - hb_psdemux_t *state, int pcr_tolerance) + hb_psdemux_t *state, int tolerance) { while ( buf ) { save_chap( state, buf ); if ( state ) { - if ( buf->s.discontinuity ) - { - // Buffer has been flagged as a discontinuity. This happens - // when a blueray changes clips. - ++state->scr_changes; - state->last_scr = buf->s.start; - state->scr_delta = 0; - } - + int discontinuity = 0; // we're keeping track of timing (i.e., not in scan) // check if there's a new pcr in this packet if ( buf->s.pcr >= 0 ) { // we have a new pcr - check_mpeg_scr( state, buf->s.pcr, pcr_tolerance ); + discontinuity = check_mpeg_scr( state, buf->s.pcr, tolerance ); buf->s.pcr = AV_NOPTS_VALUE; // Some streams have consistantly bad PCRs or SCRs // So filter out the offset if ( buf->s.start >= 0 ) state->scr_delta = buf->s.start - state->last_scr; + else + state->scr_delta = 0; } + if ( !discontinuity && buf->s.discontinuity ) + { + // Buffer has been flagged as a discontinuity. This happens + // when a blueray changes clips. + ++state->scr_changes; + state->last_scr = buf->s.start; + state->scr_delta = 0; + } + if ( buf->s.start >= 0 ) { - // Program streams have an SCR in every PACK header so they - // can't lose their clock reference. But the PCR in Transport - // streams is typically on <.1% of the packets. If a PCR - // packet gets lost and it marks a clock discontinuity then - // the data following it will be referenced to the wrong - // clock & introduce huge gaps or throw our A/V sync off. - // We try to protect against that here by sanity checking - // timestamps against the current reference clock and discarding - // packets where the DTS is "too far" from its clock. - int64_t fdelta = buf->s.start - state->last_scr - state->scr_delta; - if ( fdelta < -300 * 90000LL || fdelta > 300 * 90000LL ) + int64_t fdelta; + if (state->last_scr != AV_NOPTS_VALUE) { - // packet too far behind or ahead of its clock reference - ++state->dts_drops; - hb_buffer_t *tmp = buf->next; - buf->next = NULL; - hb_buffer_close( &buf ); - buf = tmp; - continue; - } - else - { - // Some streams have no PCRs. In these cases, we - // will only get an "PCR" update if a large change - // in DTS or PTS is detected. So we need to update - // our scr_delta with each valid timestamp so that - // fdelta does not continually grow. - state->scr_delta = buf->s.start - state->last_scr; + // Program streams have an SCR in every PACK header so they + // can't lose their clock reference. But the PCR in + // Transport streams is typically on <.1% of the packets. + // If a PCR packet gets lost and it marks a clock + // discontinuity then the data following it will be + // referenced to the wrong clock & introduce huge gaps or + // throw our A/V sync off. We try to protect against that + // here by sanity checking timestamps against the current + // reference clock and discarding packets where the DTS + // is "too far" from its clock. + fdelta = buf->s.start - state->last_scr - state->scr_delta; + if ( fdelta < -300 * 90000LL || fdelta > 300 * 90000LL ) + { + // packet too far behind or ahead of its clock reference + ++state->dts_drops; + ++state->dts_drop_run; + hb_buffer_t *tmp = buf->next; + buf->next = NULL; + hb_buffer_close( &buf ); + buf = tmp; + continue; + } + else + { + // Some streams have no PCRs. In these cases, we + // will only get an "PCR" update if a large change + // in DTS or PTS is detected. So we need to update + // our scr_delta with each valid timestamp so that + // fdelta does not continually grow. + state->scr_delta = buf->s.start - state->last_scr; + if (state->dts_drop_run > 0) + { + hb_error("Packet clock reference error. Dropped %d frames.", state->dts_drop_run); + state->dts_drop_run = 0; + } + } } if (buf->s.type == AUDIO_BUF || buf->s.type == VIDEO_BUF) { @@ -312,10 +329,11 @@ void hb_demux_mpeg(hb_buffer_t *buf, hb_list_t *list_es, fdelta = buf->s.start - state->last_pts; if ( fdelta < -5 * 90000LL || fdelta > 5 * 90000LL ) { - // Packet too far from last. This may be a NZ TV broadcast - // as they like to change the PCR without sending a PCR - // update. Since it may be a while until they actually tell - // us the new PCR use the PTS as the PCR. + // Packet too far from last. This may be a NZ TV + // broadcast as they like to change the PCR without + // sending a PCR update. Since it may be a while + // until they actually tell us the new PCR use the + // PTS as the PCR. ++state->scr_changes; state->last_scr = buf->s.start; state->scr_delta = 0; @@ -361,7 +379,7 @@ void hb_demux_null( hb_buffer_t * buf, hb_list_t * list_es, hb_psdemux_t* state save_chap( state, buf ); if ( state ) { - // if we don't have a time offset yet, + // if we don't have a time offset yet, // use this timestamp as the offset. if (state->scr_changes == 0 && (buf->s.start != AV_NOPTS_VALUE || diff --git a/libhb/internal.h b/libhb/internal.h index 7337a1387..442816fa7 100644 --- a/libhb/internal.h +++ b/libhb/internal.h @@ -286,6 +286,7 @@ typedef struct { int64_t last_pts; /* last pts we saw */ int scr_changes; /* number of SCR discontinuities */ int dts_drops; /* number of drops because DTS too far from SCR */ + int dts_drop_run; /* number of consecutive drops */ int new_chap; } hb_psdemux_t; @@ -354,7 +355,8 @@ int hb_stream_seek_ts( hb_stream_t * stream, int64_t ts ); int hb_stream_seek_chapter( hb_stream_t *, int ); int hb_stream_chapter( hb_stream_t * ); -hb_buffer_t * hb_ts_decode_pkt( hb_stream_t *stream, const uint8_t * pkt ); +hb_buffer_t * hb_ts_decode_pkt( hb_stream_t *stream, const uint8_t * pkt, + int chapter, int discontinuity ); void hb_stream_set_need_keyframe( hb_stream_t *stream, int need_keyframe ); diff --git a/libhb/stream.c b/libhb/stream.c index d8da41153..b1e15cea9 100644 --- a/libhb/stream.c +++ b/libhb/stream.c @@ -111,16 +111,31 @@ typedef enum { #define MAX_PS_PROBE_SIZE (5*1024*1024) #define kMaxNumberPMTStreams 32 -typedef struct { - hb_buffer_t *buf; - hb_buffer_t *extra_buf; - int8_t skipbad; - int8_t continuity; - uint8_t pkt_summary[8]; - int pid; - uint8_t is_pcr; - int pes_list; +typedef struct +{ + uint8_t has_stream_id_ext; + uint8_t stream_id; + uint8_t stream_id_ext; + uint8_t bd_substream_id; + int64_t pts; + int64_t dts; + int64_t scr; + int header_len; + int packet_len; +} hb_pes_info_t; +typedef struct { + hb_buffer_t * buf; + hb_pes_info_t pes_info; + int8_t pes_info_valid; + int packet_len; + int packet_offset; + int8_t skipbad; + int8_t continuity; + uint8_t pkt_summary[8]; + int pid; + uint8_t is_pcr; + int pes_list; } hb_ts_stream_t; typedef struct { @@ -161,11 +176,8 @@ struct hb_stream_s struct { + int discontinuity; uint8_t found_pcr; // non-zero if we've found at least one pcr - int pcr_out; // sequence number of most recent output pcr - int pcr_in; // sequence number of most recent input pcr - int pcr_discontinuity; // sequence number of last discontinuity - int pcr_current; // last discontinuity sent to reader int64_t pcr; // most recent input pcr int64_t last_timestamp; // used for discontinuity detection when // there are no PCRs @@ -234,19 +246,6 @@ typedef struct { int size; } bitbuf_t; -typedef struct -{ - uint8_t has_stream_id_ext; - uint8_t stream_id; - uint8_t stream_id_ext; - uint8_t bd_substream_id; - int64_t pts; - int64_t dts; - int64_t scr; - int header_len; - int packet_len; -} hb_pes_info_t; - /*********************************************************************** * Local prototypes @@ -378,7 +377,7 @@ static int index_of_ps_stream(hb_stream_t *stream, int id, int sid) return i; } } - // If there is no match on the stream_id_ext, try matching + // If there is no match on the stream_id_ext, try matching // on only the stream_id. for ( i = 0; i < stream->pes.count; ++i ) { @@ -658,9 +657,7 @@ static void hb_stream_delete_dynamic( hb_stream_t *d ) if (d->ts.list[i].buf) { hb_buffer_close(&(d->ts.list[i].buf)); - hb_buffer_close(&(d->ts.list[i].extra_buf)); d->ts.list[i].buf = NULL; - d->ts.list[i].extra_buf = NULL; } } } @@ -775,7 +772,7 @@ static void prune_streams(hb_stream_t *d) } } } - // reset to beginning of file and reset some stream + // reset to beginning of file and reset some stream // state information hb_stream_seek( d, 0. ); } @@ -800,7 +797,7 @@ static void prune_streams(hb_stream_t *d) continue; } } - // reset to beginning of file and reset some stream + // reset to beginning of file and reset some stream // state information hb_stream_seek( d, 0. ); } @@ -851,7 +848,7 @@ hb_stream_open(hb_handle_t *h, char *path, hb_title_t *title, int scan) { prune_streams( d ); } - // reset to beginning of file and reset some stream + // reset to beginning of file and reset some stream // state information hb_stream_seek( d, 0. ); return d; @@ -992,9 +989,7 @@ hb_stream_t * hb_bd_stream_open( hb_handle_t *h, hb_title_t *title ) for ( ii = 0; ii < d->ts.count; ii++ ) { d->ts.list[ii].buf = hb_buffer_init(d->packetsize); - d->ts.list[ii].extra_buf = hb_buffer_init(d->packetsize); d->ts.list[ii].buf->size = 0; - d->ts.list[ii].extra_buf->size = 0; } return d; @@ -1104,7 +1099,7 @@ hb_title_t * hb_stream_title_scan(hb_stream_t *stream, hb_title_t * title) title->flags |= HBTF_NO_IDR; } - if ( stream->hb_stream_type == transport && + if ( stream->hb_stream_type == transport && ( stream->ts_flags & TS_HAS_PCR ) == 0 ) { hb_log( "transport stream missing PCRs - using video DTS instead" ); @@ -2227,9 +2222,7 @@ static int hb_ts_stream_init(hb_stream_t *stream) { // demuxing buffer for TS to PS conversion stream->ts.list[i].buf = hb_buffer_init(stream->packetsize); - stream->ts.list[i].extra_buf = hb_buffer_init(stream->packetsize); stream->ts.list[i].buf->size = 0; - stream->ts.list[i].extra_buf->size = 0; } hb_ts_resolve_pid_types(stream); @@ -2243,7 +2236,7 @@ static int hb_ts_stream_init(hb_stream_t *stream) { hb_log( " 0x%x type %s (0x%x)%s", stream->ts.list[i].pid, - stream_type_name2(stream, + stream_type_name2(stream, &stream->pes.list[stream->ts.list[i].pes_list]), ts_stream_type( stream, i ), stream->ts.list[i].is_pcr ? " (PCR)" : ""); @@ -2487,9 +2480,9 @@ static inline unsigned int bits_peek(bitbuf_t *bb, int bits) if (bits > 0) { int pos = bpos >> 3; - int bval = (bb->buf[pos] << 24) | - (bb->buf[pos + 1] << 16) | - (bb->buf[pos + 2] << 8) | + int bval = (bb->buf[pos] << 24) | + (bb->buf[pos + 1] << 16) | + (bb->buf[pos + 2] << 8) | bb->buf[pos + 3]; val |= (bval >> (32 - bits)) & bitmask[bits]; } @@ -2972,8 +2965,8 @@ static int parse_pes_header( return 0; } - int expect = (!!has_pts) * 5 + (has_pts & 0x01) * 5 + has_escr * 6 + - has_esrate * 3 + has_dsm + has_copy_info + has_crc * 2 + + int expect = (!!has_pts) * 5 + (has_pts & 0x01) * 5 + has_escr * 6 + + has_esrate * 3 + has_dsm + has_copy_info + has_crc * 2 + has_ext; if ( bits_bytes_left(&bb_hdr) < expect ) @@ -3031,7 +3024,7 @@ static int parse_pes_header( bits_skip(&bb_hdr, 3); // reserved bits int has_ext2 = bits_get(&bb_hdr, 1); - expect = (has_private) * 16 + has_pack + has_counter * 2 + + expect = (has_private) * 16 + has_pack + has_counter * 2 + has_pstd * 2 + has_ext2 * 2; if ( bits_bytes_left(&bb_hdr) < expect ) @@ -3538,7 +3531,7 @@ static int update_ps_streams( hb_stream_t * stream, int stream_id, int stream_id { // Assume primary video stream has the smallest stream id // and only use the primary. move the current item - // to the end of the list. we want to keep it for + // to the end of the list. we want to keep it for // debug and informational purposes. int jj = new_pes( stream ); memcpy( &stream->pes.list[jj], &stream->pes.list[ii], @@ -3618,14 +3611,14 @@ static int update_ts_streams( hb_stream_t * stream, int pid, int stream_id_ext, break; } // Resolve multiple videos - if ( kind == V && ts_stream_kind( stream, ii ) == V && + if ( kind == V && ts_stream_kind( stream, ii ) == V && pes_idx < stream->ts.list[ii].pes_list ) { // We have a new candidate for the primary video. Move // the current video to the end of the list. And put the // new video in this slot int jj = new_pid( stream ); - memcpy( &stream->ts.list[jj], &stream->ts.list[ii], + memcpy( &stream->ts.list[jj], &stream->ts.list[ii], sizeof( hb_ts_stream_t ) ); break; } @@ -3711,7 +3704,7 @@ static int decode_ps_map( hb_stream_t * stream, uint8_t *buf, int len ) // This is how most PS streams specify this type of audio. // // TiVo sets the stream id to 0xbd and does not - // give a substream id. This limits them to one audio + // give a substream id. This limits them to one audio // stream and differs from how everyone else specifies // this type of audio. if ( stream_id < 0xb9 ) @@ -3780,7 +3773,7 @@ static void hb_ps_stream_find_streams(hb_stream_t *stream) { // program stream map // Note that if there is a program map, any - // extrapolation that is made below based on + // extrapolation that is made below based on // stream id may be overridden by entry in the map. if ( decode_ps_map( stream, buf->data, buf->size ) ) { @@ -3813,7 +3806,7 @@ static void hb_ps_stream_find_streams(hb_stream_t *stream) pes_info.bd_substream_id, 0, -1 ); stream->pes.list[idx].stream_kind = S; stream->pes.list[idx].codec = WORK_DECVOBSUB; - strncpy(stream->pes.list[idx].codec_name, + strncpy(stream->pes.list[idx].codec_name, "DVD Subtitle", 80); continue; } @@ -3856,7 +3849,7 @@ static void hb_ps_stream_find_streams(hb_stream_t *stream) // HD-DVD uses this for both ac3 and eac3. // Check ac3 bitstream_id to distinguish between them. bitbuf_t bb; - bits_init(&bb, buf->data + pes_info.header_len, + bits_init(&bb, buf->data + pes_info.header_len, buf->size - pes_info.header_len, 0); int sync = bits_get(&bb, 16); if ( sync == 0x0b77 ) @@ -3891,7 +3884,7 @@ static void hb_ps_stream_find_streams(hb_stream_t *stream) else if ( ( pes_info.stream_id & 0xf0 ) == 0xe0 ) { // Normally this is MPEG video, but MPEG-1 PS streams - // (which do not have a program stream map) may use + // (which do not have a program stream map) may use // this for other types of video. // // Also, the hddvd tards decided to use 0xe2 and 0xe3 for @@ -3912,7 +3905,7 @@ static void hb_ps_stream_find_streams(hb_stream_t *stream) stream_type = 0xea; } else - { + { // mark as unknown and probe. stream_type = 0x00; } @@ -4169,7 +4162,7 @@ static void hb_ts_resolve_pid_types(hb_stream_t *stream) // 0xa2 is DTS-HD LBR used in HD-DVD and bluray for // secondary audio streams. Libav can not decode yet. // Having it in the audio list causes delays during scan - // while we try to get stream parameters. So skip + // while we try to get stream parameters. So skip // this type for now. if ( stype == 0x85 && stream->reg_desc == STR4_TO_UINT32("HDMV") ) @@ -4449,53 +4442,81 @@ static int64_t pes_timestamp( const uint8_t *buf ) return ts; } +static int stream_kind_to_buf_type(int kind) +{ + switch (kind) + { + case A: + return AUDIO_BUF; + case V: + return VIDEO_BUF; + case S: + return SUBTITLE_BUF; + default: + return OTHER_BUF; + } +} + static hb_buffer_t * generate_output_data(hb_stream_t *stream, int curstream) { hb_buffer_t *buf = NULL, *first = NULL; - hb_pes_info_t pes_info; - hb_buffer_t * b = stream->ts.list[curstream].buf; - if ( !hb_parse_ps( stream, b->data, b->size, &pes_info ) ) + hb_ts_stream_t * ts_stream = &stream->ts.list[curstream]; + hb_buffer_t * b = ts_stream->buf; + if (!ts_stream->pes_info_valid) { - b->size = 0; - return NULL; + if (!hb_parse_ps(stream, b->data, b->size, &ts_stream->pes_info)) + { + b->size = 0; + ts_stream->packet_len = 0; + ts_stream->packet_offset = 0; + return NULL; + } + ts_stream->pes_info_valid = 1; + ts_stream->packet_offset = ts_stream->pes_info.header_len; } - uint8_t *tdat = b->data + pes_info.header_len; - int size = b->size - pes_info.header_len; + uint8_t *tdat = b->data + ts_stream->packet_offset; + int es_size = b->size - ts_stream->packet_offset; - if ( size <= 0 ) + if (es_size <= 0) { - b->size = 0; return NULL; } int pes_idx; - pes_idx = stream->ts.list[curstream].pes_list; - if( stream->need_keyframe ) + pes_idx = ts_stream->pes_list; + hb_pes_stream_t *pes_stream = &stream->pes.list[pes_idx]; + if (stream->need_keyframe) { // we're looking for the first video frame because we're // doing random access during 'scan' - int kind = stream->pes.list[pes_idx].stream_kind; - if( kind != V || !isIframe( stream, tdat, size ) ) + int kind = pes_stream->stream_kind; + if (kind != V || !isIframe(stream, tdat, es_size)) { // not the video stream or didn't find an I frame // but we'll only wait 255 video frames for an I frame. - if ( kind != V || ++stream->need_keyframe < 512 ) + if (kind != V || ++stream->need_keyframe < 512) { b->size = 0; + ts_stream->pes_info_valid = 0; + ts_stream->packet_len = 0; + ts_stream->packet_offset = 0; return NULL; } } stream->need_keyframe = 0; } + // Some TS streams carry multiple substreams. E.g. DTS-HD contains + // a core DTS substream. We demux these as separate streams here. // Check all substreams to see if this packet matches - for ( pes_idx = stream->ts.list[curstream].pes_list; pes_idx != -1; - pes_idx = stream->pes.list[pes_idx].next ) + for (pes_idx = ts_stream->pes_list; pes_idx != -1; + pes_idx = stream->pes.list[pes_idx].next) { - if ( stream->pes.list[pes_idx].stream_id_ext != pes_info.stream_id_ext && - stream->pes.list[pes_idx].stream_id_ext != 0 ) + hb_pes_stream_t *pes_stream = &stream->pes.list[pes_idx]; + if (pes_stream->stream_id_ext != ts_stream->pes_info.stream_id_ext && + pes_stream->stream_id_ext != 0) { continue; } @@ -4504,83 +4525,88 @@ static hb_buffer_t * generate_output_data(hb_stream_t *stream, int curstream) // we want the whole TS stream including all substreams. // DTS-HD is an example of this. - if ( first == NULL ) - first = buf = hb_buffer_init( size ); + if (first == NULL) + { + first = buf = hb_buffer_init(es_size); + } else { - hb_buffer_t *tmp = hb_buffer_init( size ); + hb_buffer_t *tmp = hb_buffer_init(es_size); buf->next = tmp; buf = tmp; } - buf->s.id = get_id( &stream->pes.list[pes_idx] ); - switch (stream->pes.list[pes_idx].stream_kind) - { - case A: - buf->s.type = AUDIO_BUF; - break; - - case V: - buf->s.type = VIDEO_BUF; - break; + buf->s.id = get_id(pes_stream); + buf->s.type = stream_kind_to_buf_type(pes_stream->stream_kind); + buf->s.discontinuity = stream->ts.discontinuity; + stream->ts.discontinuity = 0; + buf->s.new_chap = b->s.new_chap; + b->s.new_chap = 0; - default: - buf->s.type = OTHER_BUF; - break; - } - - if( b->sequence > stream->ts.pcr_out ) + // put the PTS & possible DTS into 'start' & 'renderOffset' + // only put timestamps on the first output buffer for this PES packet. + if (ts_stream->packet_offset > 0) { - // we have a new pcr - stream->ts.pcr_out = b->sequence; - buf->s.pcr = b->s.pcr; - if( b->sequence >= stream->ts.pcr_discontinuity ) - stream->ts.pcr_current = stream->ts.pcr_discontinuity; + buf->s.pcr = stream->ts.pcr; + stream->ts.pcr = AV_NOPTS_VALUE; + buf->s.start = ts_stream->pes_info.pts; + buf->s.renderOffset = ts_stream->pes_info.dts; } else { buf->s.pcr = AV_NOPTS_VALUE; - } - - // check if this packet was referenced to an older pcr and if that - // pcr was prior to a discontinuity. - if( b->sequence < stream->ts.pcr_current ) - { - // we've sent up a new pcr but have a packet referenced to an - // old pcr and the difference was enough to trigger a discontinuity - // correction. smash the timestamps or we'll mess up the correction. buf->s.start = AV_NOPTS_VALUE; buf->s.renderOffset = AV_NOPTS_VALUE; - buf->s.stop = AV_NOPTS_VALUE; - buf->s.pcr = AV_NOPTS_VALUE; } - else - { - // put the PTS & possible DTS into 'start' & 'renderOffset' - // then strip off the PES header. - buf->s.start = pes_info.pts; - buf->s.renderOffset = pes_info.dts; - } - memcpy( buf->data, tdat, size ); + // copy the elementary stream data into the buffer + memcpy(buf->data, tdat, es_size); } + if (ts_stream->pes_info.packet_len > 0 && + ts_stream->packet_len >= ts_stream->pes_info.packet_len + 6) + { + ts_stream->pes_info_valid = 0; + ts_stream->packet_len = 0; + } b->size = 0; + ts_stream->packet_offset = 0; return first; } -static void hb_ts_stream_append_pkt(hb_stream_t *stream, int idx, const uint8_t *buf, int len) +static void hb_ts_stream_append_pkt(hb_stream_t *stream, int idx, + const uint8_t *buf, int len) { + if (stream->ts.list[idx].skipbad || len <= 0) + return; + if (stream->ts.list[idx].buf->size + len > stream->ts.list[idx].buf->alloc) { int size; - size = MAX( stream->ts.list[idx].buf->alloc * 2, - stream->ts.list[idx].buf->size + len); + size = MAX(stream->ts.list[idx].buf->alloc * 2, + stream->ts.list[idx].buf->size + len); hb_buffer_realloc(stream->ts.list[idx].buf, size); } - memcpy( stream->ts.list[idx].buf->data + stream->ts.list[idx].buf->size, - buf, len); + memcpy(stream->ts.list[idx].buf->data + stream->ts.list[idx].buf->size, + buf, len); stream->ts.list[idx].buf->size += len; + stream->ts.list[idx].packet_len += len; +} + +static hb_buffer_t * flush_ts_streams( hb_stream_t *stream ) +{ + hb_buffer_t *out, **last; + int ii; + + last = &out; + for (ii = 0; ii < stream->ts.count; ii++) + { + *last = generate_output_data(stream, ii); + // generate_output_data can generate 0 or multiple output buffers + while (*last != NULL) + last = &(*last)->next; + } + return out; } /*********************************************************************** @@ -4588,7 +4614,8 @@ static void hb_ts_stream_append_pkt(hb_stream_t *stream, int idx, const uint8_t *********************************************************************** * **********************************************************************/ -hb_buffer_t * hb_ts_decode_pkt( hb_stream_t *stream, const uint8_t * pkt ) +hb_buffer_t * hb_ts_decode_pkt( hb_stream_t *stream, const uint8_t * pkt, + int chapter, int discontinuity ) { /* * stash the output buffer pointer in our stream so we don't have to @@ -4596,7 +4623,19 @@ hb_buffer_t * hb_ts_decode_pkt( hb_stream_t *stream, const uint8_t * pkt ) */ int video_index = ts_index_of_video(stream); int curstream; - hb_buffer_t *buf; + hb_buffer_t *out = NULL; + hb_buffer_t **last; + + last = &out; + + if (chapter > 0) + { + stream->chapter = chapter; + } + if (discontinuity) + { + stream->ts.discontinuity = 1; + } /* This next section validates the packet */ @@ -4635,9 +4674,17 @@ hb_buffer_t * hb_ts_decode_pkt( hb_stream_t *stream, const uint8_t * pkt ) } } - if ( adapt_len > 0 ) + if (discontinuity) { - if ( pkt[5] & 0x40 ) + // If there is a discontinuity, flush all data + *last = flush_ts_streams(stream); + // flush_ts_streams can generate 0 or multiple output buffers + while (*last != NULL) + last = &(*last)->next; + } + if (adapt_len > 0) + { + if (pkt[5] & 0x40) { // found a random access point } @@ -4647,29 +4694,26 @@ hb_buffer_t * hb_ts_decode_pkt( hb_stream_t *stream, const uint8_t * pkt ) // JAS: I have a badly mastered BD that does adaptation field // stuffing incorrectly which results in invalid PCRs. Test // for all 0xff to guard against this. - if ( adapt_len > 7 && ( pkt[5] & 0x10 ) != 0 && + if (adapt_len > 7 && (pkt[5] & 0x10) != 0 && !(pkt[5] == 0xff && pkt[6] == 0xff && pkt[7] == 0xff && pkt[8] == 0xff && pkt[9] == 0xff && pkt[10] == 0xff)) { + // When we get a new pcr, we flush all data that was + // referenced to the last pcr. This makes it easier + // for reader to resolve pcr discontinuities. + *last = flush_ts_streams(stream); + // flush_ts_streams can generate 0 or multiple output buffers + while (*last != NULL) + last = &(*last)->next; + int64_t pcr; - pcr = ( (uint64_t)pkt[6] << (33 - 8) ) | - ( (uint64_t)pkt[7] << (33 - 16) ) | - ( (uint64_t)pkt[8] << (33 - 24) ) | - ( (uint64_t)pkt[9] << (33 - 32) ) | - ( pkt[10] >> 7 ); - ++stream->ts.pcr_in; + pcr = ((uint64_t)pkt[ 6] << (33 - 8) ) | + ((uint64_t)pkt[ 7] << (33 - 16) ) | + ((uint64_t)pkt[ 8] << (33 - 24) ) | + ((uint64_t)pkt[ 9] << (33 - 32) ) | + ( pkt[10] >> 7 ); stream->ts.found_pcr = 1; stream->ts_flags |= TS_HAS_PCR; - // Check for a pcr discontinuity. - // The reason for the uint cast on the pcr difference is that the - // difference is significant if it advanced by more than 200ms or - // if it went backwards by any amount. The negative numbers look - // like huge unsigned ints so the cast allows both conditions to - // be checked at once. - if ( (uint64_t)( pcr - stream->ts.pcr ) > 200*90LL ) - { - stream->ts.pcr_discontinuity = stream->ts.pcr_in; - } stream->ts.pcr = pcr; } } @@ -4679,21 +4723,21 @@ hb_buffer_t * hb_ts_decode_pkt( hb_stream_t *stream, const uint8_t * pkt ) // Unfortunately the HD Home Run appears to null out the PCR so if // we didn't detect a PCR during scan keep going and we'll use // the video stream DTS for the PCR. - if ( !stream->ts.found_pcr && ( stream->ts_flags & TS_HAS_PCR ) ) + if (!stream->ts.found_pcr && (stream->ts_flags & TS_HAS_PCR)) { - return NULL; + return out; } // Get continuity // Continuity only increments for adaption values of 0x3 or 0x01 // and is not checked for start packets. - + hb_ts_stream_t * ts_stream = &stream->ts.list[curstream]; int start = (pkt[1] & 0x40) != 0; if ( (adaption & 0x01) != 0 ) { int continuity = (pkt[3] & 0xF); - if ( continuity == stream->ts.list[curstream].continuity ) + if ( continuity == ts_stream->continuity ) { // Spliced transport streams can have duplicate // continuity counts at the splice boundary. @@ -4712,45 +4756,45 @@ hb_buffer_t * hb_ts_decode_pkt( hb_stream_t *stream, const uint8_t * pkt ) { memset(&summary[2], 0, 6); } - if ( memcmp( summary, stream->ts.list[curstream].pkt_summary, 8 ) == 0 ) + if ( memcmp( summary, ts_stream->pkt_summary, 8 ) == 0 ) { // 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. - return NULL; + return out; } } - if ( !start && (stream->ts.list[curstream].continuity != -1) && - !stream->ts.list[curstream].skipbad && - (continuity != ( (stream->ts.list[curstream].continuity + 1) & 0xf ) ) ) + if ( !start && (ts_stream->continuity != -1) && + !ts_stream->skipbad && + (continuity != ( (ts_stream->continuity + 1) & 0xf ) ) ) { ts_err( stream, curstream, "continuity error: got %d expected %d", (int)continuity, - (stream->ts.list[curstream].continuity + 1) & 0xf ); - stream->ts.list[curstream].continuity = continuity; - return NULL; + (ts_stream->continuity + 1) & 0xf ); + ts_stream->continuity = continuity; + return out; } - stream->ts.list[curstream].continuity = continuity; + ts_stream->continuity = continuity; // Save a summary of this packet for later duplicate // testing. The summary includes some header information // and payload bytes. Should be enough to detect // non-duplicates. - stream->ts.list[curstream].pkt_summary[0] = adaption; - stream->ts.list[curstream].pkt_summary[1] = adapt_len; + ts_stream->pkt_summary[0] = adaption; + ts_stream->pkt_summary[1] = adapt_len; if (adapt_len + 4 + 6 + 9 <= 188) { - memcpy(&stream->ts.list[curstream].pkt_summary[2], + memcpy(&ts_stream->pkt_summary[2], pkt+4+adapt_len+9, 6); } else { - memset(&stream->ts.list[curstream].pkt_summary[2], 0, 6); + memset(&ts_stream->pkt_summary[2], 0, 6); } } - if ( ts_stream_kind( stream, curstream ) == P ) + if (ts_stream_kind( stream, curstream ) == P) { // This is a stream that only contains PCRs. No need to process // the remainder of the packet. @@ -4758,107 +4802,104 @@ hb_buffer_t * hb_ts_decode_pkt( hb_stream_t *stream, const uint8_t * pkt ) // I ran across a poorly mastered BD that does not properly pad // the adaptation field and causes parsing errors below if we // do not exit early here. - return NULL; + return out; } /* If we get here the packet is valid - process its data */ - - - if ( start ) + if (start) { - // Found a random access point or we have finished generating a PES - // and must start a new one. + // Found the start of a new PES packet. + // If we have previous packet data on this stream, + // output the elementary stream data for that packet. + if (ts_stream->buf->size > 0) + { + // we have to ship the old packet before updating the pcr + // since the packet we've been accumulating is referenced + // to the old pcr. + *last = generate_output_data(stream, curstream); + // generate_output_data can generate 0 or multiple output buffers + while (*last != NULL) + last = &(*last)->next; + ts_stream->pes_info_valid = 0; + ts_stream->packet_len = 0; + } // PES must begin with an mpeg start code const uint8_t *pes = pkt + adapt_len + 4; - if ( pes[0] != 0x00 || pes[1] != 0x00 || pes[2] != 0x01 ) + if (pes[0] != 0x00 || pes[1] != 0x00 || pes[2] != 0x01) { ts_err( stream, curstream, "missing start code" ); - stream->ts.list[curstream].skipbad = 1; - return NULL; + ts_stream->skipbad = 1; + return out; } // If we were skipping a bad packet, start fresh on this new PES packet - if (stream->ts.list[curstream].skipbad == 1) - { - stream->ts.list[curstream].skipbad = 0; - } + ts_stream->skipbad = 0; - if ( curstream == video_index ) + if (curstream == video_index) { ++stream->frames; // if we don't have a pcr yet use the dts from this frame // to attempt to detect discontinuities - if ( !stream->ts.found_pcr ) + if (!stream->ts.found_pcr) { // PES must begin with an mpeg start code & contain // a DTS or PTS. - const uint8_t *pes = pkt + adapt_len + 4; - if ( pes[0] != 0x00 || pes[1] != 0x00 || pes[2] != 0x01 || - ( pes[7] >> 6 ) == 0 ) + if (stream->ts.last_timestamp < 0 && (pes[7] >> 6) == 0) { - return NULL; + return out; } - // if we have a dts use it otherwise use the pts - int64_t timestamp; - timestamp = pes_timestamp( pes + ( pes[7] & 0x40?14:9 ) ); - if( stream->ts.last_timestamp < 0 || - timestamp - stream->ts.last_timestamp > 90 * 600 || - stream->ts.last_timestamp - timestamp > 90 * 600 ) + if ((pes[7] >> 6) != 0) { - stream->ts.pcr = timestamp; - ++stream->ts.pcr_in; - stream->ts.pcr_discontinuity = stream->ts.pcr_in; + // if we have a dts use it otherwise use the pts + // We simulate a psuedo-PCR here by sampling a timestamp + // about every 600ms. + int64_t timestamp; + timestamp = pes_timestamp(pes + (pes[7] & 0x40 ? 14 : 9)); + if (stream->ts.last_timestamp < 0 || + timestamp - stream->ts.last_timestamp > 90 * 600 || + stream->ts.last_timestamp - timestamp > 90 * 600) + { + stream->ts.pcr = timestamp; + } + stream->ts.last_timestamp = timestamp; } - stream->ts.last_timestamp = timestamp; } } + } - // 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.list[curstream].buf->size ) - { - // we have to ship the old packet before updating the pcr - // since the packet we've been accumulating is referenced - // to the old pcr. - buf = generate_output_data(stream, curstream); + // Add the payload for this packet to the current buffer + hb_ts_stream_append_pkt(stream, curstream, pkt + 4 + adapt_len, + 184 - adapt_len); + if (stream->chapter > 0 && + stream->pes.list[ts_stream->pes_list].stream_kind == V) + { + ts_stream->buf->s.new_chap = stream->chapter; + stream->chapter = 0; + } - if ( buf ) - { - // Output data is ready. - // remember the pcr that was in effect when we started - // this packet. - stream->ts.list[curstream].buf->sequence = stream->ts.pcr_in; - stream->ts.list[curstream].buf->s.pcr = stream->ts.pcr; - hb_ts_stream_append_pkt(stream, curstream, pkt + 4 + adapt_len, - 184 - adapt_len); - return buf; - } + if (!ts_stream->pes_info_valid && ts_stream->buf->size >= 19) + { + if (hb_parse_ps(stream, ts_stream->buf->data, ts_stream->buf->size, + &ts_stream->pes_info)) + { + ts_stream->pes_info_valid = 1; + ts_stream->packet_offset = ts_stream->pes_info.header_len; } - // remember the pcr that was in effect when we started this packet. - stream->ts.list[curstream].buf->sequence = stream->ts.pcr_in; - stream->ts.list[curstream].buf->s.pcr = stream->ts.pcr; } - // Add the payload for this packet to the current buffer - if (!stream->ts.list[curstream].skipbad && (184 - adapt_len) > 0) - { - hb_ts_stream_append_pkt(stream, curstream, pkt + 4 + adapt_len, - 184 - adapt_len); - // see if we've hit the end of this PES packet - const uint8_t *pes = stream->ts.list[curstream].buf->data; - int len = ( pes[4] << 8 ) + pes[5] + 6; - if ( len > 6 && stream->ts.list[curstream].buf->size == len && - pes[0] == 0x00 && pes[1] == 0x00 && pes[2] == 0x01 ) - { - buf = generate_output_data(stream, curstream); - if ( buf ) - return buf; - } + // see if we've hit the end of this PES packet + if (ts_stream->pes_info_valid && + ts_stream->pes_info.packet_len > 0 && + ts_stream->packet_len >= ts_stream->pes_info.packet_len + 6) + { + // generate_output_data can generate 0 or multiple output buffers + *last = generate_output_data(stream, curstream); + while (*last != NULL) + last = &(*last)->next; } - return NULL; + return out; } static hb_buffer_t * hb_ts_stream_decode( hb_stream_t *stream ) @@ -4877,7 +4918,7 @@ static hb_buffer_t * hb_ts_stream_decode( hb_stream_t *stream ) return NULL; } - b = hb_ts_decode_pkt( stream, buf ); + b = hb_ts_decode_pkt( stream, buf, 0, 0 ); if ( b ) { return b; @@ -4908,8 +4949,6 @@ void hb_ts_stream_reset(hb_stream_t *stream) { if ( stream->ts.list[i].buf ) stream->ts.list[i].buf->size = 0; - if ( stream->ts.list[i].extra_buf ) - stream->ts.list[i].extra_buf->size = 0; stream->ts.list[i].skipbad = 1; stream->ts.list[i].continuity = -1; } @@ -4917,10 +4956,7 @@ void hb_ts_stream_reset(hb_stream_t *stream) stream->need_keyframe = 1; stream->ts.found_pcr = 0; - stream->ts.pcr_out = 0; - stream->ts.pcr_in = 0; stream->ts.pcr = AV_NOPTS_VALUE; - stream->ts.pcr_current = -1; stream->ts.last_timestamp = AV_NOPTS_VALUE; stream->frames = 0; |