LLONG get_data(struct lib_ccx_ctx *ctx, struct wtv_chunked_buffer *cb, struct demuxer_data *data) { static int video_streams[32]; static int alt_stream; //Stream to use for timestamps if the cc stream has broken timestamps static int use_alt_stream = 0; static int num_streams = 0; struct lib_cc_decode *dec_ctx = update_decoder_list(ctx); while(1) { int bytesread = 0; // Read the 32 bytes containing the GUID and length and stream_id info get_sized_buffer(ctx->demux_ctx, cb, 32); if(cb->buffer == NULL) return CCX_EOF; uint8_t guid[16]; memcpy(&guid, cb->buffer, 16); // Read the GUID int x; for(x=0; x<16; x++) dbg_print(CCX_DMT_PARSE, "%02X ", guid[x]); dbg_print(CCX_DMT_PARSE, "\n"); uint32_t len; memcpy(&len, cb->buffer+16, 4); // Read the length len-=32; dbg_print(CCX_DMT_PARSE, "len %X\n", len); uint32_t pad; pad = len%8==0 ? 0 : 8- (len % 8); // Calculate the padding to add to the length // to get to the next GUID dbg_print(CCX_DMT_PARSE, "pad %X\n", pad); uint32_t stream_id; memcpy(&stream_id, cb->buffer+20, 4); stream_id = stream_id & 0x7f; // Read and calculate the stream_id dbg_print(CCX_DMT_PARSE, "stream_id: 0x%X\n", stream_id); for(x=0; x<num_streams; x++) dbg_print(CCX_DMT_PARSE, "video stream_id: %X\n", video_streams[x]); if( !memcmp(guid, WTV_EOF, 16 )) { // This is the end of the data in this file // read the padding at the end of the file // and return one more byte dbg_print(CCX_DMT_PARSE, "WTV EOF\n"); uint8_t *parsebuf; parsebuf = (uint8_t*)malloc(1024); do { buffered_read(ctx->demux_ctx, parsebuf, 1024); ctx->demux_ctx->past+=1024; } while (result==1024); ctx->demux_ctx->past+=result; free(parsebuf); free(cb->buffer); cb->buffer=NULL; //return one more byte so the final percentage is shown correctly *(data->buffer+data->len)=0x00; data->len++; return CCX_EOF; } if( !memcmp(guid, WTV_STREAM2, 16 ) ) { // The WTV_STREAM2 GUID appares near the start of the data dir // It maps stream_ids to the type of stream dbg_print(CCX_DMT_PARSE, "WTV STREAM2\n"); get_sized_buffer(ctx->demux_ctx, cb, 0xc+16); if(cb->buffer==NULL) return CCX_EOF; static unsigned char stream_type[16]; memcpy(&stream_type, cb->buffer+0xc, 16); //Read the stream type GUID const void *stream_guid; if(ccx_options.wtvmpeg2) stream_guid = WTV_STREAM_VIDEO; // We want mpeg2 data if the user set -wtvmpeg2 else stream_guid = WTV_STREAM_MSTVCAPTION; // Otherwise we want the MSTV captions stream if(!memcmp(stream_type, stream_guid, 16 ) ) { video_streams[num_streams]=stream_id; // We keep a list of stream ids num_streams++; // Even though there should only be 1 } if (memcmp(stream_type, WTV_STREAM_AUDIO, 16)) alt_stream = stream_id; len-=28; } if (!memcmp(guid, WTV_TIMING, 16) && ((use_alt_stream < WTV_CC_TIMESTAMP_MAGIC_THRESH && check_stream_id(stream_id, video_streams, num_streams)) || (use_alt_stream == WTV_CC_TIMESTAMP_MAGIC_THRESH && stream_id == alt_stream))) { // The WTV_TIMING GUID contains a timestamp for the given stream_id dbg_print(CCX_DMT_PARSE, "WTV TIMING\n"); get_sized_buffer(ctx->demux_ctx, cb, 0x8+0x8); if(cb->buffer==NULL) return CCX_EOF; int64_t time; memcpy(&time, cb->buffer+0x8, 8); // Read the timestamp dbg_print(CCX_DMT_PARSE, "TIME: %ld\n", time); if (time != -1 && time != WTV_CC_TIMESTAMP_MAGIC) { // Ignore -1 timestamps set_current_pts(dec_ctx->timing, time_to_pes_time(time)); frames_since_ref_time = 0; set_fts(dec_ctx->timing); } else if (time == WTV_CC_TIMESTAMP_MAGIC && stream_id != alt_stream) { use_alt_stream++; mprint("WARNING: %i WTV_CC_TIMESTAMP_MAGIC detected in cc timestamps. \n", use_alt_stream); if (use_alt_stream == WTV_CC_TIMESTAMP_MAGIC_THRESH) mprint("WARNING: WTV_CC_TIMESTAMP_MAGIC_THRESH reached. Switching to alt timestamps!\n"); } len-=16; } if( !memcmp(guid, WTV_DATA, 16 ) && check_stream_id(stream_id, video_streams, num_streams) && dec_ctx->timing->current_pts != 0 && (ccx_options.wtvmpeg2 || (!ccx_options.wtvmpeg2 && len==2))) { // This is the data for a stream we want to process dbg_print(CCX_DMT_PARSE, "\nWTV DATA\n"); get_sized_buffer(ctx->demux_ctx, cb, len); if(cb->buffer==NULL) return CCX_EOF; memcpy(data->buffer+data->len, cb->buffer, len); data->len+=result; bytesread+=(int) len; frames_since_ref_time++; set_fts(dec_ctx->timing); if(pad>0) { //Make sure we skip any padding too, since we are returning here skip_sized_buffer(ctx->demux_ctx, cb, pad); } return bytesread; } if(len+pad>0) { // skip any remaining data // For any unhandled GUIDs this will be len+pad // For others it will just be pad skip_sized_buffer(ctx->demux_ctx, cb, len+pad); } } }
// Return TRUE if the data parsing finished, FALSE otherwise. // estream->pos is advanced. Data is only processed if esstream->error // is FALSE, parsing can set esstream->error to TRUE. static int gop_header(struct bitstream *esstream) { dbg_print(DMT_VERBOSE, "GOP header\n"); if (esstream->error || esstream->bitsleft <= 0) return 0; // We only get here after seeing that start code if (read_u32(esstream) != 0xB8010000) // LSB first (0x000001B8) fatal(EXIT_BUG_BUG, "Impossible!"); unsigned drop_frame_flag = (unsigned) read_bits(esstream,1); struct gop_time_code gtc; gtc.time_code_hours = (int) read_bits(esstream,5); gtc.time_code_minutes = (int) read_bits(esstream,6); skip_bits(esstream,1); // Marker bit gtc.time_code_seconds = (int) read_bits(esstream,6); gtc.time_code_pictures = (int) read_bits(esstream,6); gtc.inited = 1; calculate_ms_gop_time(>c); if (esstream->bitsleft < 0) return 0; if (gop_accepted(>c)) { // Do GOP padding during GOP header. The previous GOP and all // included captions are written. Use the current GOP time to // do the padding. // Flush buffered cc blocks before doing the housekeeping if (has_ccdata_buffered) { process_hdcc(); } // Last GOPs pulldown frames if ((current_pulldownfields>0) != (pulldownfields>0)) { current_pulldownfields = pulldownfields; dbg_print(DMT_VERBOSE, "Pulldown: %s", (pulldownfields ? "on" : "off")); if (pulldownfields) dbg_print(DMT_VERBOSE, " - %u fields in last GOP", pulldownfields); dbg_print(DMT_VERBOSE, "\n"); } pulldownfields = 0; // Report synchronization jumps between GOPs. Warn if there // are 20% or more deviation. if ( (debug_mask & DMT_TIME) && ((gtc.ms - gop_time.ms // more than 20% longer > frames_since_last_gop*1000.0/current_fps*1.2) || (gtc.ms - gop_time.ms // or 20% shorter < frames_since_last_gop*1000.0/current_fps*0.8)) && first_gop_time.inited ) { mprint("\rWarning: Jump in GOP timing.\n"); mprint(" (old) %s", print_mstime(gop_time.ms)); mprint(" + %s (%uF)", print_mstime(LLONG(frames_since_last_gop *1000.0/current_fps)), frames_since_last_gop); mprint(" != (new) %s\n", print_mstime(gtc.ms)); } if (first_gop_time.inited == 0) { first_gop_time = gtc; // It needs to be "+1" because the frame count starts at 0 and we // need the length of all frames. if ( total_frames_count == 0 ) { // If this is the first frame there cannot be an offset fts_fc_offset = 0; // first_gop_time.ms stays unchanged } else { fts_fc_offset = LLONG((total_frames_count+1) *1000.0/current_fps); // Compensate for those written before first_gop_time.ms -= fts_fc_offset; } dbg_print(DMT_TIME, "\nFirst GOP time: %02u:%02u:%02u:%03u %+lldms\n", gtc.time_code_hours, gtc.time_code_minutes, gtc.time_code_seconds, unsigned(1000.0*gtc.time_code_pictures/current_fps), fts_fc_offset); } gop_time = gtc; frames_since_last_gop=0; // Indicate that we read a gop header (since last frame number 0) saw_gop_header = 1; // If we use GOP timing, reconstruct the PTS from the GOP if (use_gop_as_pts) { current_pts = gtc.ms*(MPEG_CLOCK_FREQ/1000); if (pts_set==0) pts_set=1; current_tref = 0; frames_since_ref_time = 0; set_fts(); fts_at_gop_start = get_fts_max(); } else { // FIXME: Wrong when PTS are not increasing but are identical // troughout the GOP and then jump to the next time for the // next GOP. // This effect will also lead to captions being one GOP early // for DVD captions. fts_at_gop_start = get_fts_max() + LLONG(1000.0/current_fps); } if (debug_mask & DMT_TIME) { dbg_print(DMT_TIME, "\nNew GOP:\n"); dbg_print(DMT_TIME, "\nDrop frame flag: %u:\n", drop_frame_flag); print_debug_timing(); } } return 1; }
// Return TRUE if all was read. FALSE if a problem occured: // If a bitstream syntax problem occured the bitstream will // point to after the problem, in case we run out of data the bitstream // will point to where we want to restart after getting more. static int read_pic_info(struct bitstream *esstream) { dbg_print(DMT_VERBOSE, "Read PIC Info\n"); // We only get here after seeing that start code if (next_u32(esstream) != 0x00010000) // LSB first (0x00000100) fatal(EXIT_BUG_BUG, "Impossible!"); // If we get here esstream points to the start of a group_start_code // should we run out of data in esstream this is where we want to restart // after getting more. unsigned char *pic_info_start = esstream->pos; pic_header(esstream); pic_coding_ext(esstream); if (esstream->error) return 0; if (esstream->bitsleft < 0) { init_bitstream(esstream, pic_info_start, esstream->end); return 0; } // Analyse/use the picture information static int maxtref; // Use to remember the temporal reference number // A new anchor frame - flush buffered caption data. Might be flushed // in GOP header already. if (picture_coding_type==I_FRAME || picture_coding_type==P_FRAME) { // if (((picture_structure != 0x1) && (picture_structure != 0x2)) || // (temporal_reference != current_tref)) // { // NOTE: process_hdcc() needs to be called before set_fts() as it // uses fts_now to re-create the timeline !!!!! if (has_ccdata_buffered) { process_hdcc(); } anchor_hdcc(temporal_reference); // } } current_tref = temporal_reference; current_picture_coding_type = picture_coding_type; // We mostly use PTS, but when the GOP mode is enabled do not set // the FTS time here. if (!use_gop_as_pts) { set_fts(); // Initialize fts } dbg_print(DMT_VIDES, "PTS: %s (%8u) - tref: %2d - %s since tref0/GOP: %2u/%2u", print_mstime(current_pts/(MPEG_CLOCK_FREQ/1000)), unsigned(current_pts), temporal_reference, pict_types[picture_coding_type], unsigned(frames_since_ref_time), unsigned(frames_since_last_gop)); dbg_print(DMT_VIDES, " t:%d r:%d p:%d", top_field_first, repeat_first_field, progressive_frame); dbg_print(DMT_VIDES, " FTS: %s\n", print_mstime(get_fts())); // Set min_pts/sync_pts according to the current time stamp. // Use fts_at_gop_start as reference when a GOP header was seen // since the last frame 0. If not this is most probably a // TS without GOP headers but with USER DATA after each picture // header. Use the current FTS values as reference. // Note: If a GOP header was present the reference time is from // the beginning of the GOP, otherwise it is now. if(temporal_reference == 0) { last_gop_length = maxtref + 1; maxtref = temporal_reference; // frames_since_ref_time is used in set_fts() if( saw_gop_header ) { // This time (fts_at_gop_start) that was set in the // GOP header and it might be off by one GOP. See the comment there. frames_since_ref_time = frames_since_last_gop; // Should this be 0? } else { // No GOP header, use the current values fts_at_gop_start=get_fts(); frames_since_ref_time = 0; } if (debug_mask & DMT_TIME) { dbg_print(DMT_TIME, "\nNew temporal reference:\n"); print_debug_timing(); } saw_gop_header = 0; // Reset the value } if ( !saw_gop_header && picture_coding_type==I_FRAME ) { // A new GOP beginns with an I-frame. Lets hope there are // never more than one per GOP frames_since_last_gop = 0; } // Set maxtref if( temporal_reference > maxtref ) { maxtref = temporal_reference; if (maxtref+1 > max_gop_length) max_gop_length = maxtref+1; } unsigned extraframe = 0; if (repeat_first_field) { pulldownfields++; total_pulldownfields++; if ( current_progressive_sequence || !(total_pulldownfields%2) ) extraframe = 1; if ( current_progressive_sequence && top_field_first ) extraframe = 2; dbg_print(DMT_VIDES, "Pulldown: total pd fields: %d - %d extra frames\n", total_pulldownfields, extraframe); } total_pulldownframes += extraframe; total_frames_count += 1+extraframe; frames_since_last_gop += 1+extraframe; frames_since_ref_time += 1+extraframe; dbg_print(DMT_VERBOSE, "Read PIC Info - processed\n\n"); return 1; }
// Return TRUE if all was read. FALSE if a problem occured: // If a bitstream syntax problem occured the bitstream will // point to after the problem, in case we run out of data the bitstream // will point to where we want to restart after getting more. static int read_pic_info(struct lib_cc_decode *ctx, struct bitstream *esstream, struct cc_subtitle *sub) { debug("Read PIC Info\n"); // We only get here after seeing that start code if (next_u32(esstream) != 0x00010000) // LSB first (0x00000100) fatal(CCX_COMMON_EXIT_BUG_BUG, "In read_pic_info: next_u32(esstream) != 0x00010000. Please file a bug report in GitHub.\n"); // If we get here esstream points to the start of a group_start_code // should we run out of data in esstream this is where we want to restart // after getting more. unsigned char *pic_info_start = esstream->pos; pic_header(ctx, esstream); pic_coding_ext(ctx, esstream); if (esstream->error) return 0; if (esstream->bitsleft < 0) { init_bitstream(esstream, pic_info_start, esstream->end); return 0; } // A new anchor frame - flush buffered caption data. Might be flushed // in GOP header already. if (ctx->picture_coding_type==CCX_FRAME_TYPE_I_FRAME || ctx->picture_coding_type==CCX_FRAME_TYPE_P_FRAME) { if (((ctx->picture_structure != 0x1) && (ctx->picture_structure != 0x2)) || (ctx->temporal_reference != ctx->timing->current_tref)) { // NOTE: process_hdcc() needs to be called before set_fts() as it // uses fts_now to re-create the timeline !!!!! if (ctx->has_ccdata_buffered) { process_hdcc(ctx, sub); } anchor_hdcc(ctx, ctx->temporal_reference); } } ctx->timing->current_tref = ctx->temporal_reference; ctx->timing->current_picture_coding_type = ctx->picture_coding_type; // We mostly use PTS, but when the GOP mode is enabled do not set // the FTS time here. if (ccx_options.use_gop_as_pts!=1) { set_fts(ctx->timing); // Initialize fts } dbg_print(CCX_DMT_VIDES, " t:%d r:%d p:%d", ctx->top_field_first, ctx->repeat_first_field, ctx->progressive_frame); dbg_print(CCX_DMT_VIDES, " FTS: %s\n", print_mstime_static(get_fts(ctx->timing, ctx->current_field))); // Set min_pts/sync_pts according to the current time stamp. // Use fts_at_gop_start as reference when a GOP header was seen // since the last frame 0. If not this is most probably a // TS without GOP headers but with USER DATA after each picture // header. Use the current FTS values as reference. // Note: If a GOP header was present the reference time is from // the beginning of the GOP, otherwise it is now. if(ctx->temporal_reference == 0) { ctx->last_gop_length = ctx->maxtref + 1; ctx->maxtref = ctx->temporal_reference; // frames_since_ref_time is used in set_fts() if( ctx->saw_gop_header ) { // This time (fts_at_gop_start) that was set in the // GOP header and it might be off by one GOP. See the comment there. frames_since_ref_time = ctx->frames_since_last_gop; // Should this be 0? } else { // No GOP header, use the current values fts_at_gop_start = get_fts(ctx->timing, ctx->current_field); frames_since_ref_time = 0; } if (ccx_options.debug_mask & CCX_DMT_TIME) { dbg_print(CCX_DMT_TIME, "\nNew temporal reference:\n"); print_debug_timing(ctx->timing); } ctx->saw_gop_header = 0; // Reset the value } if ( !ctx->saw_gop_header && ctx->picture_coding_type==CCX_FRAME_TYPE_I_FRAME ) { // A new GOP beginns with an I-frame. Lets hope there are // never more than one per GOP ctx->frames_since_last_gop = 0; } // Set maxtref if( ctx->temporal_reference > ctx->maxtref ) { ctx->maxtref = ctx->temporal_reference; if (ctx->maxtref + 1 > ctx->max_gop_length) ctx->max_gop_length = ctx->maxtref + 1; } unsigned extraframe = 0; if (ctx->repeat_first_field) { ctx->pulldownfields++; ctx->total_pulldownfields++; if ( ctx->current_progressive_sequence || !(ctx->total_pulldownfields%2) ) extraframe = 1; if ( ctx->current_progressive_sequence && ctx->top_field_first ) extraframe = 2; dbg_print(CCX_DMT_VIDES, "Pulldown: total pd fields: %d - %d extra frames\n", ctx->total_pulldownfields, extraframe); } ctx->total_pulldownframes += extraframe; total_frames_count += 1+extraframe; ctx->frames_since_last_gop += 1+extraframe; frames_since_ref_time += 1+extraframe; debug("Read PIC Info - processed\n\n"); return 1; }