/* Helper function for to display debug timing info. */ void print_debug_timing(struct ccx_common_timing_ctx *ctx) { // Avoid wrong "Calc. difference" and "Asynchronous by" numbers // for uninitialized min_pts LLONG tempmin_pts = (ctx->min_pts==0x01FFFFFFFFLL ? ctx->sync_pts : ctx->min_pts); ccx_common_logging.log_ftn("Sync time stamps: PTS: %s ", print_mstime((ctx->sync_pts)/(MPEG_CLOCK_FREQ/1000)) ); ccx_common_logging.log_ftn("GOP: %s \n", print_mstime(gop_time.ms)); // Length first GOP to last GOP LLONG goplenms = (LLONG) (gop_time.ms - first_gop_time.ms); // Length at last sync point LLONG ptslenms = (unsigned)((ctx->sync_pts-tempmin_pts)/(MPEG_CLOCK_FREQ/1000) + ctx->fts_offset); ccx_common_logging.log_ftn("Last FTS: %s", print_mstime(get_fts_max(ctx))); ccx_common_logging.log_ftn(" GOP start FTS: %s\n", print_mstime(fts_at_gop_start)); // Times are based on last GOP and/or sync time ccx_common_logging.log_ftn("Max FTS diff. to PTS: %6lldms GOP: %6lldms\n\n", get_fts_max(ctx)+(LLONG)(1000.0/current_fps)-ptslenms, get_fts_max(ctx)+(LLONG)(1000.0/current_fps)-goplenms); }
/* Helper function for to display debug timing info. */ void print_debug_timing( void ) { // Avoid wrong "Calc. difference" and "Asynchronous by" numbers // for uninitialized min_pts LLONG tempmin_pts = (min_pts==0x01FFFFFFFFLL ? sync_pts : min_pts); mprint("Sync time stamps: PTS: %s ", print_mstime((sync_pts)/(MPEG_CLOCK_FREQ/1000)) ); mprint("GOP: %s \n", print_mstime(gop_time.ms)); // Length first GOP to last GOP LLONG goplenms = LLONG(gop_time.ms - first_gop_time.ms); // Length at last sync point LLONG ptslenms = unsigned((sync_pts-tempmin_pts)/(MPEG_CLOCK_FREQ/1000) + fts_offset); mprint("Last FTS: %s", print_mstime(get_fts_max())); mprint(" GOP start FTS: %s\n", print_mstime(fts_at_gop_start)); // Times are based on last GOP and/or sync time mprint("Max FTS diff. to PTS: %6lldms GOP: %6lldms\n\n", get_fts_max()+LLONG(1000.0/current_fps)-ptslenms, get_fts_max()+LLONG(1000.0/current_fps)-goplenms); }
void set_current_pts(struct ccx_common_timing_ctx *ctx, LLONG pts) { ctx->current_pts = pts; if(ctx->pts_set == 0) ctx->pts_set = 1; dbg_print(CCX_DMT_VIDES, "PTS: %s (%8u)", print_mstime(ctx->current_pts/(MPEG_CLOCK_FREQ/1000)), (unsigned) (ctx->current_pts)); dbg_print(CCX_DMT_VIDES, " FTS: %s \n",print_mstime(ctx->fts_now)); }
/* Buffer data with the same FTS and write when a new FTS or data==NULL * is encountered */ void writercwtdata (struct lib_cc_decode *ctx, const unsigned char *data, struct cc_subtitle *sub) { static LLONG prevfts = -1; LLONG currfts = fts_now + fts_global; static uint16_t cbcount = 0; static int cbempty=0; static unsigned char cbbuffer[0xFFFF*3]; // TODO: use malloc static unsigned char cbheader[8+2]; if ( (prevfts != currfts && prevfts != -1) || data == NULL || cbcount == 0xFFFF) { // Remove trailing empty or 608 padding caption blocks if ( cbcount != 0xFFFF) { unsigned char cc_valid; unsigned char cc_type; int storecbcount=cbcount; for( int cb = cbcount-1; cb >= 0 ; cb-- ) { cc_valid = (*(cbbuffer+3*cb) & 4) >>2; cc_type = *(cbbuffer+3*cb) & 3; // The -fullbin option disables pruning of 608 padding blocks if ( (cc_valid && cc_type <= 1 // Only skip NTSC padding packets && !ctx->fullbin // Unless we want to keep them && *(cbbuffer+3*cb+1)==0x80 && *(cbbuffer+3*cb+2)==0x80) || !(cc_valid || cc_type==3) ) // or unused packets { cbcount--; } else { cb = -1; } } dbg_print(CCX_DMT_CBRAW, "%s Write %d RCWT blocks - skipped %d padding / %d unused blocks.\n", print_mstime(prevfts), cbcount, storecbcount - cbcount, cbempty); } // New FTS, write data header // RCWT data header (10 bytes): //byte(s) value description //0-7 FTS int64_t number with current FTS //8-9 blocks Number of 3 byte data blocks with the same FTS that are // following this header memcpy(cbheader,&prevfts,8); memcpy(cbheader+8,&cbcount,2); if (cbcount > 0) { ctx->writedata(cbheader, 10, ctx->context_cc608_field_1, sub); ctx->writedata(cbbuffer, 3 * cbcount, ctx->context_cc608_field_1, sub); } cbcount = 0; cbempty = 0; }
/* This function returns the current FTS and saves it so it can be used by ctxget_visible_start */ LLONG get_visible_end (struct ccx_common_timing_ctx *ctx, int current_field) { LLONG fts = get_fts(ctx, current_field); if (fts > ctx->minimum_fts) ctx->minimum_fts = fts; ccx_common_logging.debug_ftn(CCX_DMT_DECODER_608, "Visible End time=%s\n", print_mstime(fts)); return fts; }
/* This function returns a FTS that is guaranteed to be at least 1 ms later than the end of the previous screen. It shouldn't be needed obviously but it guarantees there's no timing overlap */ LLONG get_visible_start (void) { LLONG fts = get_fts(); if (fts <= minimum_fts) fts = minimum_fts+1; ccx_common_logging.debug_ftn(CCX_DMT_DECODER_608, "Visible Start time=%s\n", print_mstime(fts)); return fts; }
/* This function returns the current FTS and saves it so it can be used by get_visible_start */ LLONG get_visible_end (void) { LLONG fts = get_fts(); if (fts>minimum_fts) minimum_fts=fts; ccx_common_logging.debug_ftn(CCX_DMT_DECODER_608, "Visible End time=%s\n", print_mstime(fts)); return fts; }
int do_cb (struct lib_cc_decode *ctx, unsigned char *cc_block, struct cc_subtitle *sub) { unsigned char cc_valid = (*cc_block & 4) >>2; unsigned char cc_type = *cc_block & 3; int timeok = 1; if ( ctx->fix_padding && cc_valid==0 && cc_type <= 1 // Only fix NTSC packets && cc_block[1]==0 && cc_block[2]==0 ) { /* Padding */ cc_valid=1; cc_block[1]=0x80; cc_block[2]=0x80; } if ( ctx->write_format!=CCX_OF_RAW && // In raw we cannot skip padding because timing depends on it ctx->write_format!=CCX_OF_DVDRAW && (cc_block[0]==0xFA || cc_block[0]==0xFC || cc_block[0]==0xFD ) && (cc_block[1]&0x7F)==0 && (cc_block[2]&0x7F)==0) // CFS: Skip non-data, makes debugging harder. return 1; // Print raw data with FTS. dbg_print(CCX_DMT_CBRAW, "%s %d %02X:%c%c:%02X", print_mstime(ctx->timing->fts_now + ctx->timing->fts_global),in_xds_mode, cc_block[0], cc_block[1]&0x7f,cc_block[2]&0x7f, cc_block[2]); /* In theory the writercwtdata() function could return early and not * go through the 608/708 cases below. We do that to get accurate * counts for cb_field1, cb_field2 and cb_708. * Note that printdata() and dtvcc_process_data() must not be called for * the CCX_OF_RCWT case. */ if (cc_valid || cc_type==3) { ctx->cc_stats[cc_type]++; switch (cc_type) { case 0: dbg_print(CCX_DMT_CBRAW, " %s .. ..\n", debug_608toASC( cc_block, 0)); ctx->current_field = 1; ctx->saw_caption_block = 1; if (ctx->extraction_start.set && get_fts(ctx->timing, ctx->current_field) < ctx->extraction_start.time_in_ms) timeok = 0; if (ctx->extraction_end.set && get_fts(ctx->timing, ctx->current_field) > ctx->extraction_end.time_in_ms) { timeok = 0; ctx->processed_enough=1; } if (timeok) { if(ctx->write_format!=CCX_OF_RCWT) printdata (ctx, cc_block+1,2,0,0, sub); else writercwtdata(ctx, cc_block, sub); } cb_field1++; break; case 1: dbg_print(CCX_DMT_CBRAW, " .. %s ..\n", debug_608toASC( cc_block, 1)); ctx->current_field = 2; ctx->saw_caption_block = 1; if (ctx->extraction_start.set && get_fts(ctx->timing, ctx->current_field) < ctx->extraction_start.time_in_ms) timeok = 0; if (ctx->extraction_end.set && get_fts(ctx->timing, ctx->current_field) > ctx->extraction_end.time_in_ms) { timeok = 0; ctx->processed_enough=1; } if (timeok) { if(ctx->write_format!=CCX_OF_RCWT) printdata (ctx, 0,0,cc_block+1,2, sub); else writercwtdata(ctx, cc_block, sub); } cb_field2++; break; case 2: //EIA-708 // DTVCC packet data // Fall through case 3: //EIA-708 dbg_print(CCX_DMT_CBRAW, " .. .. DD\n"); // DTVCC packet start ctx->current_field = 3; if (ctx->extraction_start.set && get_fts(ctx->timing, ctx->current_field) < ctx->extraction_start.time_in_ms) timeok = 0; if (ctx->extraction_end.set && get_fts(ctx->timing, ctx->current_field) > ctx->extraction_end.time_in_ms) { timeok = 0; ctx->processed_enough=1; } char temp[4]; temp[0]=cc_valid; temp[1]=cc_type; temp[2]=cc_block[1]; temp[3]=cc_block[2]; if (timeok) { if (ctx->write_format != CCX_OF_RCWT) ccx_dtvcc_process_data(ctx, (const unsigned char *) temp, 4); else writercwtdata(ctx, cc_block, sub); } cb_708++; // Check for bytes read // printf ("Warning: Losing EIA-708 data!\n"); break; default: fatal(CCX_COMMON_EXIT_BUG_BUG, "Cannot be reached!"); } // switch (cc_type) } // cc_valid else { dbg_print(CCX_DMT_CBRAW, " .. .. ..\n"); dbg_print(CCX_DMT_VERBOSE, "Found !(cc_valid || cc_type==3) - ignore this block\n"); } return 1; }
/* Read and evaluate the current video PES header. The function returns * the length of the payload if successful, otherwise -1 is returned * indicating a premature end of file / too small buffer. * If sbuflen is * 0 .. Read from file into nextheader * >0 .. Use data in nextheader with the length of sbuflen */ int read_video_pes_header (unsigned char *nextheader, int *headerlength, int sbuflen) { // Read the next video PES // ((nextheader[3]&0xf0)==0xe0) unsigned peslen=nextheader[4]<<8 | nextheader[5]; unsigned payloadlength = 0; // Length of packet data bytes if ( !sbuflen ) { // Extension present, get it buffered_read (nextheader+6,3); past=past+result; if (result!=3) { // Consider this the end of the show. return -1; } } else { // We need at least 9 bytes to continue if( sbuflen < 9 ) return -1; } *headerlength = 6+3; unsigned hskip=0; // Assume header[8] is right, but double check if ( !sbuflen ) { if (nextheader[8] > 0) { buffered_read (nextheader+9,nextheader[8]); past=past+result; if (result!=nextheader[8]) { return -1; } } } else { // See if the buffer is big enough if( sbuflen < *headerlength + (int)nextheader[8] ) return -1; } *headerlength += (int) nextheader[8]; int falsepes = 0; int pesext = 0; // Avoid false positives, check --- not really needed if ( (nextheader[7]&0xC0) == 0x80 ) { // PTS only hskip += 5; if( (nextheader[9]&0xF1) != 0x21 || (nextheader[11]&0x01) != 0x01 || (nextheader[13]&0x01) != 0x01 ) { falsepes = 1; printf("False PTS\n"); } } else if ( (nextheader[7]&0xC0) == 0xC0 ) { // PTS and DTS hskip += 10; if( (nextheader[9]&0xF1) != 0x31 || (nextheader[11]&0x01) != 0x01 || (nextheader[13]&0x01) != 0x01 || (nextheader[14]&0xF1) != 0x11 || (nextheader[16]&0x01) != 0x01 || (nextheader[18]&0x01) != 0x01 ) { falsepes = 1; printf("False PTS/DTS\n"); } } else if ( (nextheader[7]&0xC0) == 0x40 ) { // Forbidden falsepes = 1; printf("False PTS/DTS flag\n"); } if ( !falsepes && nextheader[7]&0x20 ) { // ESCR if ((nextheader[9+hskip]&0xC4) != 0x04 || !(nextheader[11+hskip]&0x04) || !(nextheader[13+hskip]&0x04) || !(nextheader[14+hskip]&0x01) ) { falsepes = 1; printf("False ESCR\n"); } hskip += 6; } if ( !falsepes && nextheader[7]&0x10 ) { // ES if ( !(nextheader[9+hskip]&0x80) || !(nextheader[11+hskip]&0x01) ) { printf("False ES\n"); falsepes = 1; } hskip += 3; } if ( !falsepes && nextheader[7]&0x04) { // add copy info if ( !(nextheader[9+hskip]&0x80) ) { printf("False add copy info\n"); falsepes = 1; } hskip += 1; } if ( !falsepes && nextheader[7]&0x02) { // PES CRC hskip += 2; } if ( !falsepes && nextheader[7]&0x01) { // PES extension if ( (nextheader[9+hskip]&0x0E)!=0x0E ) { printf("False PES ext\n"); falsepes = 1; } hskip += 1; pesext = 1; } if ( !falsepes ) { hskip = nextheader[8]; } if ( !falsepes && nextheader[7]&0x80 ) { // Read PTS from byte 9,10,11,12,13 unsigned bits_9 = (nextheader[9] & 0x0E) << 29; unsigned bits_10 = nextheader[10] << 22; unsigned bits_11 = (nextheader[11] & 0xFE) << 14; unsigned bits_12 = nextheader[12] << 7; unsigned bits_13 = nextheader[13] >> 1; current_pts = bits_9 | bits_10 | bits_11 | bits_12 | bits_13; if (pts_set==0) pts_set=1; if (debug_verbose) printf("Set PTS: %s (%u)\n", print_mstime((current_pts)/(MPEG_CLOCK_FREQ/1000)), unsigned(current_pts) ); /* The user data holding the captions seems to come between GOP and * the first frame. The sync PTS (sync_pts) (set at picture 0) * corresponds to the first frame of the current GOP. */ }
// 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 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; }
int handle_708_C1 (cc708_service_decoder *decoder, unsigned char *data, int data_length) { S_COMMANDS_C1 com=COMMANDS_C1[data[0]-0x80]; printf ("%s | C1: [%02X] [%s] [%s] (%d)\n", print_mstime(get_fts()), data[0],com.name,com.description, com.length); if (com.length>data_length) { printf ("C1: Warning: Not enough bytes for command.\n"); return -1; } switch (com.code) { case CW0: /* SetCurrentWindow */ case CW1: case CW2: case CW3: case CW4: case CW5: case CW6: case CW7: handle_708_CWx_SetCurrentWindow (decoder, com.code-CW0); /* Window 0 to 7 */ break; case CLW: handle_708_CLW_ClearWindows (decoder, data[1]); break; case DSW: handle_708_DSW_DisplayWindows (decoder, data[1]); break; case HDW: handle_708_HDW_HideWindows (decoder, data[1]); break; case TGW: handle_708_TGW_ToggleWindows (decoder, data[1]); break; case DLW: handle_708_DLW_DeleteWindows (decoder, data[1]); break; case DLY: handle_708_DLY_Delay (decoder, data[1]); break; case DLC: handle_708_DLC_DelayCancel (decoder); break; case RST: cc708_service_reset(decoder); break; case SPA: handle_708_SPA_SetPenAttributes (decoder, data); break; case SPC: handle_708_SPC_SetPenColor (decoder, data); break; case SPL: handle_708_SPL_SetPenLocation (decoder, data); break; case RSV93: case RSV94: case RSV95: case RSV96: printf ("Warning, found Reserved codes, ignored.\n"); break; case SWA: handle_708_SWA_SetWindowAttributes (decoder, data); break; case DF0: case DF1: case DF2: case DF3: case DF4: case DF5: case DF6: case DF7: handle_708_DFx_DefineWindow (decoder, com.code-DF0, data); /* Window 0 to 7 */ break; default: printf ("BUG: Unhandled code in handle_708_C1.\n"); break; } return com.length; }
// C1 Code Set - Captioning Commands Control Codes int _dtvcc_handle_C1(ccx_dtvcc_ctx *dtvcc, ccx_dtvcc_service_decoder *decoder, unsigned char *data, int data_length) { struct CCX_DTVCC_S_COMMANDS_C1 com = DTVCC_COMMANDS_C1[data[0] - 0x80]; ccx_common_logging.debug_ftn(CCX_DMT_708, "[CEA-708] C1: %s | [%02X] [%s] [%s] (%d)\n", print_mstime(get_fts(dtvcc->timing, 3)), data[0], com.name, com.description, com.length); if (com.length > data_length) { ccx_common_logging.debug_ftn(CCX_DMT_708, "[CEA-708] C1: Warning: Not enough bytes for command.\n"); return -1; } switch (com.code) { case CCX_DTVCC_C1_CW0: /* SetCurrentWindow */ case CCX_DTVCC_C1_CW1: case CCX_DTVCC_C1_CW2: case CCX_DTVCC_C1_CW3: case CCX_DTVCC_C1_CW4: case CCX_DTVCC_C1_CW5: case CCX_DTVCC_C1_CW6: case CCX_DTVCC_C1_CW7: dtvcc_handle_CWx_SetCurrentWindow(decoder, com.code - CCX_DTVCC_C1_CW0); /* Window 0 to 7 */ break; case CCX_DTVCC_C1_CLW: dtvcc_handle_CLW_ClearWindows(decoder, data[1]); break; case CCX_DTVCC_C1_DSW: dtvcc_handle_DSW_DisplayWindows(decoder, data[1], dtvcc->timing); break; case CCX_DTVCC_C1_HDW: dtvcc_handle_HDW_HideWindows(dtvcc, decoder, data[1]); break; case CCX_DTVCC_C1_TGW: dtvcc_handle_TGW_ToggleWindows(dtvcc, decoder, data[1]); break; case CCX_DTVCC_C1_DLW: dtvcc_handle_DLW_DeleteWindows(dtvcc, decoder, data[1]); break; case CCX_DTVCC_C1_DLY: dtvcc_handle_DLY_Delay(decoder, data[1]); break; case CCX_DTVCC_C1_DLC: dtvcc_handle_DLC_DelayCancel(decoder); break; case CCX_DTVCC_C1_RST: dtvcc_handle_RST_Reset(decoder); break; case CCX_DTVCC_C1_SPA: dtvcc_handle_SPA_SetPenAttributes(decoder, data); break; case CCX_DTVCC_C1_SPC: dtvcc_handle_SPC_SetPenColor(decoder, data); break; case CCX_DTVCC_C1_SPL: dtvcc_handle_SPL_SetPenLocation(decoder, data); break; case CCX_DTVCC_C1_RSV93: case CCX_DTVCC_C1_RSV94: case CCX_DTVCC_C1_RSV95: case CCX_DTVCC_C1_RSV96: ccx_common_logging.debug_ftn(CCX_DMT_708, "[CEA-708] Warning, found Reserved codes, ignored.\n"); break; case CCX_DTVCC_C1_SWA: dtvcc_handle_SWA_SetWindowAttributes(decoder, data); break; case CCX_DTVCC_C1_DF0: case CCX_DTVCC_C1_DF1: case CCX_DTVCC_C1_DF2: case CCX_DTVCC_C1_DF3: case CCX_DTVCC_C1_DF4: case CCX_DTVCC_C1_DF5: case CCX_DTVCC_C1_DF6: case CCX_DTVCC_C1_DF7: dtvcc_handle_DFx_DefineWindow(decoder, com.code - CCX_DTVCC_C1_DF0, data, dtvcc->timing); /* Window 0 to 7 */ break; default: ccx_common_logging.log_ftn ("[CEA-708] BUG: Unhandled code in _dtvcc_handle_C1.\n"); break; } return com.length; }
/* If wb is NULL, then only XDS will be processed */ int process608(const unsigned char *data, int length, void *private_data, struct cc_subtitle *sub) { struct ccx_decoder_608_report *report = NULL; ccx_decoder_608_context *context = private_data; static int textprinted = 0; int i; if (context) { report = &context->report; context->bytes_processed_608 += length; } if (!data) { return -1; } for (i=0; i < length; i=i+2) { unsigned char hi, lo; int wrote_to_screen=0; hi = data[i] & 0x7F; // Get rid of parity bit lo = data[i+1] & 0x7F; // Get rid of parity bit if (hi==0 && lo==0) // Just padding continue; // printf ("\r[%02X:%02X]\n",hi,lo); if (hi>=0x10 && hi<=0x1e) { int ch = (hi<=0x17)? 1 : 2; if (context == NULL || context->my_field == 2) // Originally: current_field from sequencing.c. Seems to be just to change channel, so context->my_field seems good. ch+=2; if(report) report->cc_channels[ch - 1] = 1; } if (hi >= 0x01 && hi <= 0x0E && (context == NULL || context->my_field == 2)) // XDS can only exist in field 2. { if (context) context->channel = 3; if (!in_xds_mode) { ts_start_of_xds=get_fts(); in_xds_mode=1; } if(report) report->xds=1; } if (hi == 0x0F && in_xds_mode && (context == NULL || context->my_field == 2)) // End of XDS block { in_xds_mode=0; do_end_of_xds (sub, lo); if (context) context->channel = context->new_channel; // Switch from channel 3 continue; } if (hi>=0x10 && hi<=0x1F) // Non-character code or special/extended char // http://www.geocities.com/mcpoodle43/SCC_TOOLS/DOCS/CC_CODES.HTML // http://www.geocities.com/mcpoodle43/SCC_TOOLS/DOCS/CC_CHARS.HTML { // We were writing characters before, start a new line for // diagnostic output from disCommand() if (textprinted == 1 ) { ccx_common_logging.debug_ftn(CCX_DMT_DECODER_608, "\n"); textprinted = 0; } if (!context || context->my_field == 2) in_xds_mode=0; // Back to normal (CEA 608-8.6.2) if (!context) // Not XDS and we don't have a writebuffer, nothing else would have an effect continue; if (context->last_c1 == hi && context->last_c2 == lo) { // Duplicate dual code, discard. Correct to do it only in // non-XDS, XDS codes shall not be repeated. ccx_common_logging.debug_ftn(CCX_DMT_DECODER_608, "Skipping command %02X,%02X Duplicate\n", hi, lo); // Ignore only the first repetition context->last_c1=-1; context->last_c2 = -1; continue; } context->last_c1 = hi; context->last_c2 = lo; wrote_to_screen = disCommand(hi, lo, context, sub); if(sub->got_output) break; } else { if (in_xds_mode && (context == NULL || context->my_field == 2)) { process_xds_bytes (hi,lo); continue; } if (!context) // No XDS code after this point, and user doesn't want captions. continue; context->last_c1 = -1; context->last_c2 = -1; if (hi>=0x20) // Standard characters (always in pairs) { // Only print if the channel is active if (context->channel != context->my_channel) continue; if( textprinted == 0 ) { ccx_common_logging.debug_ftn(CCX_DMT_DECODER_608, "\n"); textprinted = 1; } handle_single(hi, context); handle_single(lo, context); wrote_to_screen=1; context->last_c1 = 0; context->last_c2 = 0; } if (!textprinted && context->channel == context->my_channel) { // Current FTS information after the characters are shown ccx_common_logging.debug_ftn(CCX_DMT_DECODER_608, "Current FTS: %s\n", print_mstime(get_fts())); //printf(" N:%u", unsigned(fts_now) ); //printf(" G:%u", unsigned(fts_global) ); //printf(" F:%d %d %d %d\n", // current_field, cb_field1, cb_field2, cb_708 ); } if (wrote_to_screen && context->settings->direct_rollup && // If direct_rollup is enabled and (context->mode == MODE_FAKE_ROLLUP_1 || // we are in rollup mode, write now. context->mode == MODE_ROLLUP_2 || context->mode == MODE_ROLLUP_3 || context->mode == MODE_ROLLUP_4)) { // We don't increase screenfuls_counter here. write_cc_buffer(context, sub); context->current_visible_start_ms = get_visible_start(); } } if (wrote_to_screen && context->cc_to_stdout) fflush (stdout); } // for return i; }
int main(int argc, char *argv[]) { char *c; struct encoder_ctx enc_ctx[2]; struct cc_subtitle dec_sub; #ifdef ENABLE_FFMPEG void *ffmpeg_ctx = NULL; #endif struct lib_ccx_ctx *ctx; struct lib_cc_decode *dec_ctx = NULL; init_options (&ccx_options); parse_configuration(&ccx_options); parse_parameters (&ccx_options, argc, argv); // Initialize libraries ctx = init_libraries(&ccx_options); dec_ctx = ctx->dec_ctx; // Prepare write structures init_write(&ctx->wbout1,ccx_options.wbout1.filename); init_write(&ctx->wbout2,ccx_options.wbout2.filename); int show_myth_banner = 0; memset (&cea708services[0],0,CCX_DECODERS_708_MAX_SERVICES*sizeof (int)); // Cannot (yet) be moved because it's needed in parse_parameters. memset (&dec_sub, 0,sizeof(dec_sub)); if (ctx->num_input_files==0 && ccx_options.input_source==CCX_DS_FILE) { usage (); fatal (EXIT_NO_INPUT_FILES, "(This help screen was shown because there were no input files)\n"); } if (ctx->num_input_files>1 && ccx_options.live_stream) { fatal(EXIT_TOO_MANY_INPUT_FILES, "Live stream mode accepts only one input file.\n"); } if (ctx->num_input_files && ccx_options.input_source==CCX_DS_NETWORK) { fatal(EXIT_TOO_MANY_INPUT_FILES, "UDP mode is not compatible with input files.\n"); } if (ccx_options.input_source==CCX_DS_NETWORK || ccx_options.input_source==CCX_DS_TCP) { ccx_options.buffer_input=1; // Mandatory, because each datagram must be read complete. } if (ctx->num_input_files && ccx_options.input_source==CCX_DS_TCP) { fatal(EXIT_TOO_MANY_INPUT_FILES, "TCP mode is not compatible with input files.\n"); } if (ctx->num_input_files > 0) { ctx->wbout1.multiple_files = 1; ctx->wbout1.first_input_file = ctx->inputfile[0]; ctx->wbout2.multiple_files = 1; ctx->wbout2.first_input_file = ctx->inputfile[0]; } // teletext page number out of range if ((tlt_config.page != 0) && ((tlt_config.page < 100) || (tlt_config.page > 899))) { fatal (EXIT_NOT_CLASSIFIED, "Teletext page number could not be lower than 100 or higher than 899\n"); } if (ccx_options.output_filename!=NULL) { // Use the given output file name for the field specified by // the -1, -2 switch. If -12 is used, the filename is used for // field 1. if (ccx_options.extract==2) ctx->wbout2.filename=ccx_options.output_filename; else ctx->wbout1.filename=ccx_options.output_filename; } switch (ccx_options.write_format) { case CCX_OF_RAW: ctx->extension = ".raw"; break; case CCX_OF_SRT: ctx->extension = ".srt"; break; case CCX_OF_SAMI: ctx->extension = ".smi"; break; case CCX_OF_SMPTETT: ctx->extension = ".ttml"; break; case CCX_OF_TRANSCRIPT: ctx->extension = ".txt"; break; case CCX_OF_RCWT: ctx->extension = ".bin"; break; case CCX_OF_SPUPNG: ctx->extension = ".xml"; break; case CCX_OF_NULL: ctx->extension = ""; break; case CCX_OF_DVDRAW: ctx->extension = ".dvdraw"; break; default: fatal (CCX_COMMON_EXIT_BUG_BUG, "write_format doesn't have any legal value, this is a bug.\n"); } params_dump(ctx); // default teletext page if (tlt_config.page > 0) { // dec to BCD, magazine pages numbers are in BCD (ETSI 300 706) tlt_config.page = ((tlt_config.page / 100) << 8) | (((tlt_config.page / 10) % 10) << 4) | (tlt_config.page % 10); } if (ctx->auto_stream==CCX_SM_MCPOODLESRAW && ccx_options.write_format==CCX_OF_RAW) { fatal (EXIT_INCOMPATIBLE_PARAMETERS, "-in=raw can only be used if the output is a subtitle file.\n"); } if (ctx->auto_stream==CCX_SM_RCWT && ccx_options.write_format==CCX_OF_RCWT && ccx_options.output_filename==NULL) { fatal (EXIT_INCOMPATIBLE_PARAMETERS, "CCExtractor's binary format can only be used simultaneously for input and\noutput if the output file name is specified given with -o.\n"); } subline = (unsigned char *) malloc (SUBLINESIZE); switch (ccx_options.input_source) { case CCX_DS_FILE: ctx->basefilename = (char *) malloc (strlen (ctx->inputfile[0])+1); break; case CCX_DS_STDIN: ctx->basefilename = (char *) malloc (strlen (ctx->basefilename_for_stdin)+1); break; case CCX_DS_NETWORK: case CCX_DS_TCP: ctx->basefilename = (char *) malloc (strlen (ctx->basefilename_for_network)+1); break; } if (ctx->basefilename == NULL) fatal (EXIT_NOT_ENOUGH_MEMORY, "Not enough memory\n"); switch (ccx_options.input_source) { case CCX_DS_FILE: strcpy (ctx->basefilename, ctx->inputfile[0]); break; case CCX_DS_STDIN: strcpy (ctx->basefilename, ctx->basefilename_for_stdin); break; case CCX_DS_NETWORK: case CCX_DS_TCP: strcpy (ctx->basefilename, ctx->basefilename_for_network); break; } for (c=ctx->basefilename+strlen (ctx->basefilename)-1; ctx->basefilename && *c!='.'; c--) {;} // Get last . if (*c=='.') *c=0; if (ctx->wbout1.filename==NULL) { ctx->wbout1.filename = (char *) malloc (strlen (ctx->basefilename)+3+strlen (ctx->extension)); ctx->wbout1.filename[0]=0; } if (ctx->wbout2.filename==NULL) { ctx->wbout2.filename = (char *) malloc (strlen (ctx->basefilename)+3+strlen (ctx->extension)); ctx->wbout2.filename[0]=0; } if (ctx->buffer == NULL || ctx->pesheaderbuf==NULL || ctx->wbout1.filename == NULL || ctx->wbout2.filename == NULL || subline==NULL || init_file_buffer() ) { fatal (EXIT_NOT_ENOUGH_MEMORY, "Not enough memory\n"); } if (ccx_options.send_to_srv) { connect_to_srv(ccx_options.srv_addr, ccx_options.srv_port, ccx_options.tcp_desc); } if (ccx_options.write_format!=CCX_OF_NULL) { /* # DVD format uses one raw file for both fields, while Broadcast requires 2 */ if (ccx_options.write_format==CCX_OF_DVDRAW) { if (ctx->wbout1.filename[0]==0) { strcpy (ctx->wbout1.filename,ctx->basefilename); strcat (ctx->wbout1.filename,".raw"); } if (ctx->cc_to_stdout) { ctx->wbout1.fh=STDOUT_FILENO; mprint ("Sending captions to stdout.\n"); } else { mprint ("Creating %s\n", ctx->wbout1.filename); ctx->wbout1.fh=open (ctx->wbout1.filename, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IREAD | S_IWRITE); if (ctx->wbout1.fh==-1) { fatal(CCX_COMMON_EXIT_FILE_CREATION_FAILED, "Failed\n"); } } } else { if (ctx->cc_to_stdout && ccx_options.extract==12) fatal (EXIT_INCOMPATIBLE_PARAMETERS, "You can't extract both fields to stdout at the same time in broadcast mode."); if (ccx_options.write_format == CCX_OF_SPUPNG && ctx->cc_to_stdout) fatal (EXIT_INCOMPATIBLE_PARAMETERS, "You cannot use -out=spupng with -stdout."); if (ccx_options.extract!=2) { if (ctx->cc_to_stdout) { ctx->wbout1.fh=STDOUT_FILENO; mprint ("Sending captions to stdout.\n"); } else if (!ccx_options.send_to_srv) { if (ctx->wbout1.filename[0]==0) { strcpy (ctx->wbout1.filename,ctx->basefilename); if (ccx_options.extract==12) // _1 only added if there's two files strcat (ctx->wbout1.filename,"_1"); strcat (ctx->wbout1.filename,(const char *) ctx->extension); } mprint ("Creating %s\n", ctx->wbout1.filename); ctx->wbout1.fh=open (ctx->wbout1.filename, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IREAD | S_IWRITE); if (ctx->wbout1.fh==-1) { fatal(CCX_COMMON_EXIT_FILE_CREATION_FAILED, "Failed (errno=%d)\n", errno); } } switch (ccx_options.write_format) { case CCX_OF_RAW: writeraw(BROADCAST_HEADER, sizeof(BROADCAST_HEADER), &ctx->wbout1); break; case CCX_OF_DVDRAW: break; case CCX_OF_RCWT: if (init_encoder(enc_ctx, &ctx->wbout1)) fatal(EXIT_NOT_ENOUGH_MEMORY, "Not enough memory\n"); set_encoder_subs_delay(enc_ctx, ctx->subs_delay); set_encoder_last_displayed_subs_ms(enc_ctx, ctx->last_displayed_subs_ms); set_encoder_startcredits_displayed(enc_ctx, ctx->startcredits_displayed); break; default: if (!ccx_options.no_bom){ if (ccx_options.encoding == CCX_ENC_UTF_8){ // Write BOM writeraw(UTF8_BOM, sizeof(UTF8_BOM), &ctx->wbout1); } if (ccx_options.encoding == CCX_ENC_UNICODE){ // Write BOM writeraw(LITTLE_ENDIAN_BOM, sizeof(LITTLE_ENDIAN_BOM), &ctx->wbout1); } } if (init_encoder(enc_ctx, &ctx->wbout1)){ fatal(EXIT_NOT_ENOUGH_MEMORY, "Not enough memory\n"); } set_encoder_subs_delay(enc_ctx, ctx->subs_delay); set_encoder_last_displayed_subs_ms(enc_ctx, ctx->last_displayed_subs_ms); set_encoder_startcredits_displayed(enc_ctx, ctx->startcredits_displayed); } } if (ccx_options.extract == 12 && ccx_options.write_format != CCX_OF_RAW) mprint (" and \n"); if (ccx_options.extract!=1) { if (ctx->cc_to_stdout) { ctx->wbout1.fh=STDOUT_FILENO; mprint ("Sending captions to stdout.\n"); } else if(ccx_options.write_format == CCX_OF_RAW && ccx_options.extract == 12) { memcpy(&ctx->wbout2, &ctx->wbout1,sizeof(ctx->wbout1)); } else if (!ccx_options.send_to_srv) { if (ctx->wbout2.filename[0]==0) { strcpy (ctx->wbout2.filename,ctx->basefilename); if (ccx_options.extract==12) // _ only added if there's two files strcat (ctx->wbout2.filename,"_2"); strcat (ctx->wbout2.filename,(const char *) ctx->extension); } mprint ("Creating %s\n", ctx->wbout2.filename); ctx->wbout2.fh=open (ctx->wbout2.filename, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IREAD | S_IWRITE); if (ctx->wbout2.fh==-1) { fatal(CCX_COMMON_EXIT_FILE_CREATION_FAILED, "Failed\n"); } if(ccx_options.write_format == CCX_OF_RAW) writeraw (BROADCAST_HEADER,sizeof (BROADCAST_HEADER),&ctx->wbout2); } switch (ccx_options.write_format) { case CCX_OF_RAW: case CCX_OF_DVDRAW: break; case CCX_OF_RCWT: if( init_encoder(enc_ctx+1,&ctx->wbout2) ) fatal (EXIT_NOT_ENOUGH_MEMORY, "Not enough memory\n"); set_encoder_subs_delay(enc_ctx+1, ctx->subs_delay); set_encoder_last_displayed_subs_ms(enc_ctx+1, ctx->last_displayed_subs_ms); set_encoder_startcredits_displayed(enc_ctx+1, ctx->startcredits_displayed); break; default: if (!ccx_options.no_bom){ if (ccx_options.encoding == CCX_ENC_UTF_8){ // Write BOM writeraw(UTF8_BOM, sizeof(UTF8_BOM), &ctx->wbout2); } if (ccx_options.encoding == CCX_ENC_UNICODE){ // Write BOM writeraw(LITTLE_ENDIAN_BOM, sizeof(LITTLE_ENDIAN_BOM), &ctx->wbout2); } } if (init_encoder(enc_ctx + 1, &ctx->wbout2)){ fatal(EXIT_NOT_ENOUGH_MEMORY, "Not enough memory\n"); } set_encoder_subs_delay(enc_ctx+1, ctx->subs_delay); set_encoder_last_displayed_subs_ms(enc_ctx+1, ctx->last_displayed_subs_ms); set_encoder_startcredits_displayed(enc_ctx+1, ctx->startcredits_displayed); } } } } if (ccx_options.transcript_settings.xds) { if (ccx_options.write_format != CCX_OF_TRANSCRIPT) { ccx_options.transcript_settings.xds = 0; mprint ("Warning: -xds ignored, XDS can only be exported to transcripts at this time.\n"); } } if (ccx_options.teletext_mode == CCX_TXT_IN_USE) // Here, it would mean it was forced by user telxcc_init(ctx); ctx->fh_out_elementarystream = NULL; if (ccx_options.out_elementarystream_filename!=NULL) { if ((ctx->fh_out_elementarystream = fopen (ccx_options.out_elementarystream_filename,"wb"))==NULL) { fatal(CCX_COMMON_EXIT_FILE_CREATION_FAILED, "Unable to open clean file: %s\n", ccx_options.out_elementarystream_filename); } } // Initialize HDTV caption buffer init_hdcc(); if (ccx_options.line_terminator_lf) encoded_crlf_length = encode_line(encoded_crlf, (unsigned char *) "\n"); else encoded_crlf_length = encode_line(encoded_crlf, (unsigned char *) "\r\n"); encoded_br_length = encode_line(encoded_br, (unsigned char *) "<br>"); time_t start, final; time(&start); dec_ctx->processed_enough=0; if (ccx_options.binary_concat) { ctx->total_inputsize=gettotalfilessize(ctx); if (ctx->total_inputsize==-1) fatal (EXIT_UNABLE_TO_DETERMINE_FILE_SIZE, "Failed to determine total file size.\n"); } #ifndef _WIN32 signal_ctx = ctx; m_signal(SIGINT, sigint_handler); #endif while (switch_to_next_file(ctx, 0) && !dec_ctx->processed_enough) { prepare_for_new_file(ctx); #ifdef ENABLE_FFMPEG close_input_file(ctx); ffmpeg_ctx = init_ffmpeg(ctx->inputfile[0]); if(ffmpeg_ctx) { do { int ret = 0; unsigned char *bptr = ctx->buffer; int len = ff_get_ccframe(ffmpeg_ctx, bptr, 1024); int cc_count = 0; if(len == AVERROR(EAGAIN)) { continue; } else if(len == AVERROR_EOF) break; else if(len == 0) continue; else if(len < 0 ) { mprint("Error extracting Frame\n"); break; } else cc_count = len/3; ret = process_cc_data(dec_ctx, bptr, cc_count, &dec_sub); if(ret >= 0 && dec_sub.got_output) { encode_sub(enc_ctx, &dec_sub); dec_sub.got_output = 0; } }while(1); continue; } else { mprint ("\rFailed to initialized ffmpeg falling back to legacy\n"); } #endif if (ctx->auto_stream == CCX_SM_AUTODETECT) { detect_stream_type(ctx); switch (ctx->stream_mode) { case CCX_SM_ELEMENTARY_OR_NOT_FOUND: mprint ("\rFile seems to be an elementary stream, enabling ES mode\n"); break; case CCX_SM_TRANSPORT: mprint ("\rFile seems to be a transport stream, enabling TS mode\n"); break; case CCX_SM_PROGRAM: mprint ("\rFile seems to be a program stream, enabling PS mode\n"); break; case CCX_SM_ASF: mprint ("\rFile seems to be an ASF, enabling DVR-MS mode\n"); break; case CCX_SM_WTV: mprint ("\rFile seems to be a WTV, enabling WTV mode\n"); break; case CCX_SM_MCPOODLESRAW: mprint ("\rFile seems to be McPoodle raw data\n"); break; case CCX_SM_RCWT: mprint ("\rFile seems to be a raw caption with time data\n"); break; case CCX_SM_MP4: mprint ("\rFile seems to be a MP4\n"); break; #ifdef WTV_DEBUG case CCX_SM_HEX_DUMP: mprint ("\rFile seems to be an hexadecimal dump\n"); break; #endif case CCX_SM_MYTH: case CCX_SM_AUTODETECT: fatal(CCX_COMMON_EXIT_BUG_BUG, "Cannot be reached!"); break; } } else { ctx->stream_mode=ctx->auto_stream; } /* ----------------------------------------------------------------- MAIN LOOP ----------------------------------------------------------------- */ // The myth loop autodetect will only be used with ES or PS streams switch (ccx_options.auto_myth) { case 0: // Use whatever stream mode says break; case 1: // Force stream mode to myth ctx->stream_mode=CCX_SM_MYTH; break; case 2: // autodetect myth files, but only if it does not conflict with // the current stream mode switch (ctx->stream_mode) { case CCX_SM_ELEMENTARY_OR_NOT_FOUND: case CCX_SM_PROGRAM: if ( detect_myth(ctx) ) { ctx->stream_mode=CCX_SM_MYTH; } break; default: // Keep stream_mode break; } break; } // Disable sync check for raw formats - they have the right timeline. // Also true for bin formats, but -nosync might have created a // broken timeline for debug purposes. // Disable too in MP4, specs doesn't say that there can't be a jump switch (ctx->stream_mode) { case CCX_SM_MCPOODLESRAW: case CCX_SM_RCWT: case CCX_SM_MP4: #ifdef WTV_DEBUG case CCX_SM_HEX_DUMP: #endif ccx_common_timing_settings.disable_sync_check = 1; break; default: break; } switch (ctx->stream_mode) { case CCX_SM_ELEMENTARY_OR_NOT_FOUND: if (!ccx_options.use_gop_as_pts) // If !0 then the user selected something ccx_options.use_gop_as_pts = 1; // Force GOP timing for ES ccx_common_timing_settings.is_elementary_stream = 1; case CCX_SM_TRANSPORT: case CCX_SM_PROGRAM: case CCX_SM_ASF: case CCX_SM_WTV: if (!ccx_options.use_gop_as_pts) // If !0 then the user selected something ccx_options.use_gop_as_pts = 0; mprint ("\rAnalyzing data in general mode\n"); general_loop(ctx, &enc_ctx); break; case CCX_SM_MCPOODLESRAW: mprint ("\rAnalyzing data in McPoodle raw mode\n"); raw_loop(ctx, &enc_ctx); break; case CCX_SM_RCWT: mprint ("\rAnalyzing data in CCExtractor's binary format\n"); rcwt_loop(ctx, &enc_ctx); break; case CCX_SM_MYTH: mprint ("\rAnalyzing data in MythTV mode\n"); show_myth_banner = 1; myth_loop(ctx, &enc_ctx); break; case CCX_SM_MP4: mprint ("\rAnalyzing data with GPAC (MP4 library)\n"); close_input_file(ctx); // No need to have it open. GPAC will do it for us processmp4 (ctx, ctx->inputfile[0],&enc_ctx); break; #ifdef WTV_DEBUG case CCX_SM_HEX_DUMP: close_input_file(ctx); // processhex will open it in text mode processhex (ctx, ctx->inputfile[0]); break; #endif case CCX_SM_AUTODETECT: fatal(CCX_COMMON_EXIT_BUG_BUG, "Cannot be reached!"); break; } mprint("\n"); dbg_print(CCX_DMT_DECODER_608, "\nTime stamps after last caption block was written:\n"); dbg_print(CCX_DMT_DECODER_608, "Last time stamps: PTS: %s (%+2dF) ", print_mstime( (LLONG) (sync_pts/(MPEG_CLOCK_FREQ/1000) +frames_since_ref_time*1000.0/current_fps) ), frames_since_ref_time); dbg_print(CCX_DMT_DECODER_608, "GOP: %s \n", print_mstime(gop_time.ms) ); // Blocks since last PTS/GOP time stamp. dbg_print(CCX_DMT_DECODER_608, "Calc. difference: PTS: %s (%+3lldms incl.) ", print_mstime( (LLONG) ((sync_pts-min_pts)/(MPEG_CLOCK_FREQ/1000) + fts_offset + frames_since_ref_time*1000.0/current_fps)), fts_offset + (LLONG) (frames_since_ref_time*1000.0/current_fps) ); dbg_print(CCX_DMT_DECODER_608, "GOP: %s (%+3dms incl.)\n", print_mstime((LLONG)(gop_time.ms -first_gop_time.ms +get_fts_max()-fts_at_gop_start)), (int)(get_fts_max()-fts_at_gop_start)); // When padding is active the CC block time should be within // 1000/29.97 us of the differences. dbg_print(CCX_DMT_DECODER_608, "Max. FTS: %s (without caption blocks since then)\n", print_mstime(get_fts_max())); if (ctx->stat_hdtv) { mprint ("\rCC type 0: %d (%s)\n", dec_ctx->cc_stats[0], cc_types[0]); mprint ("CC type 1: %d (%s)\n", dec_ctx->cc_stats[1], cc_types[1]); mprint ("CC type 2: %d (%s)\n", dec_ctx->cc_stats[2], cc_types[2]); mprint ("CC type 3: %d (%s)\n", dec_ctx->cc_stats[3], cc_types[3]); } mprint ("\nTotal frames time: %s (%u frames at %.2ffps)\n", print_mstime( (LLONG)(total_frames_count*1000/current_fps) ), total_frames_count, current_fps); if (ctx->total_pulldownframes) mprint ("incl. pulldown frames: %s (%u frames at %.2ffps)\n", print_mstime( (LLONG)(ctx->total_pulldownframes*1000/current_fps) ), ctx->total_pulldownframes, current_fps); if (pts_set >= 1 && min_pts != 0x01FFFFFFFFLL) { LLONG postsyncms = (LLONG) (ctx->frames_since_last_gop*1000/current_fps); mprint ("\nMin PTS: %s\n", print_mstime( min_pts/(MPEG_CLOCK_FREQ/1000) - fts_offset)); if (pts_big_change) mprint ("(Reference clock was reset at some point, Min PTS is approximated)\n"); mprint ("Max PTS: %s\n", print_mstime( sync_pts/(MPEG_CLOCK_FREQ/1000) + postsyncms)); mprint ("Length: %s\n", print_mstime( sync_pts/(MPEG_CLOCK_FREQ/1000) + postsyncms - min_pts/(MPEG_CLOCK_FREQ/1000) + fts_offset )); } // dvr-ms files have invalid GOPs if (gop_time.inited && first_gop_time.inited && ctx->stream_mode != CCX_SM_ASF) { mprint ("\nInitial GOP time: %s\n", print_mstime(first_gop_time.ms)); mprint ("Final GOP time: %s%+3dF\n", print_mstime(gop_time.ms), ctx->frames_since_last_gop); mprint ("Diff. GOP length: %s%+3dF", print_mstime(gop_time.ms - first_gop_time.ms), ctx->frames_since_last_gop); mprint (" (%s)\n", print_mstime(gop_time.ms - first_gop_time.ms +(LLONG) ((ctx->frames_since_last_gop)*1000/29.97)) ); } if (ctx->false_pict_header) mprint ("\nNumber of likely false picture headers (discarded): %d\n",ctx->false_pict_header); if (ctx->stat_numuserheaders) mprint("\nTotal user data fields: %d\n", ctx->stat_numuserheaders); if (ctx->stat_dvdccheaders) mprint("DVD-type user data fields: %d\n", ctx->stat_dvdccheaders); if (ctx->stat_scte20ccheaders) mprint("SCTE-20 type user data fields: %d\n", ctx->stat_scte20ccheaders); if (ctx->stat_replay4000headers) mprint("ReplayTV 4000 user data fields: %d\n", ctx->stat_replay4000headers); if (ctx->stat_replay5000headers) mprint("ReplayTV 5000 user data fields: %d\n", ctx->stat_replay5000headers); if (ctx->stat_hdtv) mprint("HDTV type user data fields: %d\n", ctx->stat_hdtv); if (ctx->stat_dishheaders) mprint("Dish Network user data fields: %d\n", ctx->stat_dishheaders); if (ctx->stat_divicom) { mprint("CEA608/Divicom user data fields: %d\n", ctx->stat_divicom); mprint("\n\nNOTE! The CEA 608 / Divicom standard encoding for closed\n"); mprint("caption is not well understood!\n\n"); mprint("Please submit samples to the developers.\n\n\n"); } // Add one frame as fts_max marks the beginning of the last frame, // but we need the end. fts_global += fts_max + (LLONG) (1000.0/current_fps); // CFS: At least in Hauppage mode, cb_field can be responsible for ALL the // timing (cb_fields having a huge number and fts_now and fts_global being 0 all // the time), so we need to take that into account in fts_global before resetting // counters. if (cb_field1!=0) fts_global += cb_field1*1001/3; else fts_global += cb_field2*1001/3; // Reset counters - This is needed if some captions are still buffered // and need to be written after the last file is processed. cb_field1 = 0; cb_field2 = 0; cb_708 = 0; fts_now = 0; fts_max = 0; } // file loop close_input_file(ctx); if (ctx->fh_out_elementarystream!=NULL) fclose (ctx->fh_out_elementarystream); flushbuffer (ctx, &ctx->wbout1, false); flushbuffer (ctx, &ctx->wbout2, false); prepare_for_new_file (ctx); // To reset counters used by handle_end_of_data() telxcc_close(ctx); if (ctx->wbout1.fh!=-1) { if (ccx_options.write_format==CCX_OF_SMPTETT || ccx_options.write_format==CCX_OF_SAMI || ccx_options.write_format==CCX_OF_SRT || ccx_options.write_format==CCX_OF_TRANSCRIPT || ccx_options.write_format==CCX_OF_SPUPNG ) { handle_end_of_data(dec_ctx->context_cc608_field_1, &dec_sub); if (dec_sub.got_output) { encode_sub(enc_ctx,&dec_sub); dec_sub.got_output = 0; } } else if(ccx_options.write_format==CCX_OF_RCWT) { // Write last header and data writercwtdata (dec_ctx, NULL); } dinit_encoder(enc_ctx); } if (ctx->wbout2.fh!=-1) { if (ccx_options.write_format==CCX_OF_SMPTETT || ccx_options.write_format==CCX_OF_SAMI || ccx_options.write_format==CCX_OF_SRT || ccx_options.write_format==CCX_OF_TRANSCRIPT || ccx_options.write_format==CCX_OF_SPUPNG ) { handle_end_of_data(dec_ctx->context_cc608_field_2, &dec_sub); if (dec_sub.got_output) { encode_sub(enc_ctx,&dec_sub); dec_sub.got_output = 0; } } dinit_encoder(enc_ctx+1); } flushbuffer (ctx, &ctx->wbout1,true); flushbuffer (ctx, &ctx->wbout2,true); time (&final); long proc_time=(long) (final-start); mprint ("\rDone, processing time = %ld seconds\n", proc_time); if (proc_time>0) { LLONG ratio=(get_fts_max()/10)/proc_time; unsigned s1=(unsigned) (ratio/100); unsigned s2=(unsigned) (ratio%100); mprint ("Performance (real length/process time) = %u.%02u\n", s1, s2); } dbg_print(CCX_DMT_708, "The 708 decoder was reset [%d] times.\n",resets_708); if (ccx_options.teletext_mode == CCX_TXT_IN_USE) mprint ( "Teletext decoder: %"PRIu32" packets processed, %"PRIu32" SRT frames written.\n", tlt_packet_counter, tlt_frames_produced); if (dec_ctx->processed_enough) { mprint ("\rNote: Processing was cancelled before all data was processed because\n"); mprint ("\rone or more user-defined limits were reached.\n"); } if (ccblocks_in_avc_lost>0) { mprint ("Total caption blocks received: %d\n", ccblocks_in_avc_total); mprint ("Total caption blocks lost: %d\n", ccblocks_in_avc_lost); } mprint ("This is beta software. Report issues to carlos at ccextractor org...\n"); if (show_myth_banner) { mprint ("NOTICE: Due to the major rework in 0.49, we needed to change part of the timing\n"); mprint ("code in the MythTV's branch. Please report results to the address above. If\n"); mprint ("something is broken it will be fixed. Thanks\n"); } dinit_libraries(&ctx); return EXIT_OK; }
int set_fts(struct ccx_common_timing_ctx *ctx) { int pts_jump = 0; // ES don't have PTS unless GOP timing is used if (!ctx->pts_set && ccx_common_timing_settings.is_elementary_stream) return CCX_OK; // First check for timeline jump (only when min_pts was set (implies sync_pts)). int dif = 0; if (ctx->pts_set == 2) { dif=(int) (ctx->current_pts - ctx->sync_pts)/MPEG_CLOCK_FREQ; if (ccx_common_timing_settings.disable_sync_check){ // Disables sync check. Used for several input formats. dif = 0; } if (dif < -0.2 || dif >= max_dif ) { // ATSC specs: More than 3501 ms means missing component ccx_common_logging.log_ftn ("\nWarning: Reference clock has changed abruptly (%d seconds filepos=%lld), attempting to synchronize\n", (int) dif, *ccx_common_timing_settings.file_position); ccx_common_logging.log_ftn ("Last sync PTS value: %lld\n",ctx->sync_pts); ccx_common_logging.log_ftn ("Current PTS value: %lld\n",ctx->current_pts); pts_jump = 1; pts_big_change = 1; // Discard the gap if it is not on an I-frame or temporal reference zero. if(ctx->current_tref != 0 && ctx->current_picture_coding_type != CCX_FRAME_TYPE_I_FRAME) { ctx->fts_now = ctx->fts_max; ccx_common_logging.log_ftn ("Change did not occur on first frame - probably a broken GOP\n"); return CCX_OK; } } } // Set min_pts, fts_offset if (ctx->pts_set != 0) { ctx->pts_set = 2; // Use this part only the first time min_pts is set. Later treat // it as a reference clock change if (ctx->current_pts < ctx->min_pts && !pts_jump) { // If this is the first GOP, and seq 0 was not encountered yet // we might reset min_pts/fts_offset again ctx->min_pts = ctx->current_pts; // Avoid next async test ctx->sync_pts = (LLONG)(ctx->current_pts -ctx->current_tref*1000.0/current_fps *(MPEG_CLOCK_FREQ/1000)); if(ctx->current_tref == 0) { // Earliest time in GOP. ctx->fts_offset = 0; } else if ( total_frames_count-frames_since_ref_time == 0 ) { // If this is the first frame (PES) there cannot be an offset. // This part is also reached for dvr-ms/NTSC (RAW) as // total_frames_count = frames_since_ref_time = 0 when // this is called for the first time. ctx->fts_offset = 0; } else { // It needs to be "+1" because the current frame is // not yet counted. ctx->fts_offset = (LLONG)((total_frames_count -frames_since_ref_time+1) *1000.0/current_fps); } ccx_common_logging.debug_ftn(CCX_DMT_TIME, "\nFirst sync time PTS: %s %+lldms (time before this PTS)\n", print_mstime(ctx->min_pts/(MPEG_CLOCK_FREQ/1000)), ctx->fts_offset ); ccx_common_logging.debug_ftn(CCX_DMT_TIME, "Total_frames_count %u frames_since_ref_time %u\n", total_frames_count, frames_since_ref_time); } // -nosync disables syncing if (pts_jump && !ccx_common_timing_settings.no_sync) { // The current time in the old time base is calculated using // sync_pts (set at the beginning of the last GOP) plus the // time of the frames since then. ctx->fts_offset = ctx->fts_offset + (ctx->sync_pts - ctx->min_pts)/(MPEG_CLOCK_FREQ/1000) + (LLONG) (frames_since_ref_time*1000/current_fps); ctx->fts_max = ctx->fts_offset; // Start counting again from here ctx->pts_set = 1; // Force min to be set again ctx->sync_pts2fts_set = 0; // Make note of the new conversion values // Avoid next async test - the gap might have occured on // current_tref != 0. ctx->sync_pts = (LLONG) (ctx->current_pts -ctx->current_tref*1000.0/current_fps *(MPEG_CLOCK_FREQ/1000)); // Set min_pts = sync_pts as this is used for fts_now ctx->min_pts = ctx->sync_pts; ccx_common_logging.debug_ftn(CCX_DMT_TIME, "\nNew min PTS time: %s %+lldms (time before this PTS)\n", print_mstime(ctx->min_pts/(MPEG_CLOCK_FREQ/1000)), ctx->fts_offset ); } } // Set sync_pts, fts_offset if(ctx->current_tref == 0) ctx->sync_pts = ctx->current_pts; // Reset counters cb_field1 = 0; cb_field2 = 0; cb_708 = 0; // Avoid wrong "Calc. difference" and "Asynchronous by" numbers // for uninitialized min_pts if (1) // CFS: Remove or think decent condition { if ( ctx->pts_set ) { // If pts_set is TRUE we have min_pts ctx->fts_now = (LLONG)((ctx->current_pts - ctx->min_pts)/(MPEG_CLOCK_FREQ/1000) + ctx->fts_offset); if (!ctx->sync_pts2fts_set) { ctx->sync_pts2fts_pts = ctx->current_pts; ctx->sync_pts2fts_fts = ctx->fts_now; ctx->sync_pts2fts_set = 1; } } else { // No PTS info at all!! ccx_common_logging.log_ftn("Set PTS called without any global timestamp set\n"); return CCX_EINVAL; } } if ( ctx->fts_now > ctx->fts_max ) { ctx->fts_max = ctx->fts_now; } return CCX_OK; }
int main(int argc, char *argv[]) { struct lib_ccx_ctx *ctx; struct lib_cc_decode *dec_ctx = NULL; int ret = 0; enum ccx_stream_mode_enum stream_mode; init_options (&ccx_options); parse_configuration(&ccx_options); ret = parse_parameters (&ccx_options, argc, argv); if (ret == EXIT_NO_INPUT_FILES) { usage (); fatal (EXIT_NO_INPUT_FILES, "(This help screen was shown because there were no input files)\n"); } else if (ret == EXIT_WITH_HELP) { return EXIT_OK; } else if (ret != EXIT_OK) { exit(ret); } // Initialize libraries ctx = init_libraries(&ccx_options); if (!ctx && errno == ENOMEM) fatal (EXIT_NOT_ENOUGH_MEMORY, "Not enough memory\n"); else if (!ctx && errno == EINVAL) fatal (CCX_COMMON_EXIT_BUG_BUG, "Invalid option to CCextractor Library\n"); else if (!ctx && errno == EPERM) fatal (CCX_COMMON_EXIT_FILE_CREATION_FAILED, "Unable to create Output File\n"); else if (!ctx && errno == EACCES) fatal (CCX_COMMON_EXIT_FILE_CREATION_FAILED, "Unable to create Output File\n"); else if (!ctx) fatal (EXIT_NOT_CLASSIFIED, "Unable to create Library Context %d\n",errno); int show_myth_banner = 0; params_dump(ctx); // default teletext page if (tlt_config.page > 0) { // dec to BCD, magazine pages numbers are in BCD (ETSI 300 706) tlt_config.page = ((tlt_config.page / 100) << 8) | (((tlt_config.page / 10) % 10) << 4) | (tlt_config.page % 10); } if (ccx_options.transcript_settings.xds) { if (ccx_options.write_format != CCX_OF_TRANSCRIPT) { ccx_options.transcript_settings.xds = 0; mprint ("Warning: -xds ignored, XDS can only be exported to transcripts at this time.\n"); } } time_t start, final; time(&start); if (ccx_options.binary_concat) { ctx->total_inputsize=gettotalfilessize(ctx); if (ctx->total_inputsize==-1) fatal (EXIT_UNABLE_TO_DETERMINE_FILE_SIZE, "Failed to determine total file size.\n"); } #ifndef _WIN32 signal_ctx = ctx; m_signal(SIGINT, sigint_handler); #endif while (switch_to_next_file(ctx, 0)) { prepare_for_new_file(ctx); stream_mode = ctx->demux_ctx->get_stream_mode(ctx->demux_ctx); // Disable sync check for raw formats - they have the right timeline. // Also true for bin formats, but -nosync might have created a // broken timeline for debug purposes. // Disable too in MP4, specs doesn't say that there can't be a jump switch (stream_mode) { case CCX_SM_MCPOODLESRAW: case CCX_SM_RCWT: case CCX_SM_MP4: #ifdef WTV_DEBUG case CCX_SM_HEX_DUMP: #endif ccx_common_timing_settings.disable_sync_check = 1; break; default: break; } /* ----------------------------------------------------------------- MAIN LOOP ----------------------------------------------------------------- */ switch (stream_mode) { case CCX_SM_ELEMENTARY_OR_NOT_FOUND: if (!ccx_options.use_gop_as_pts) // If !0 then the user selected something ccx_options.use_gop_as_pts = 1; // Force GOP timing for ES ccx_common_timing_settings.is_elementary_stream = 1; case CCX_SM_TRANSPORT: case CCX_SM_PROGRAM: case CCX_SM_ASF: case CCX_SM_WTV: case CCX_SM_GXF: #ifdef ENABLE_FFMPEG case CCX_SM_FFMPEG: #endif if (!ccx_options.use_gop_as_pts) // If !0 then the user selected something ccx_options.use_gop_as_pts = 0; mprint ("\rAnalyzing data in general mode\n"); general_loop(ctx); break; case CCX_SM_MCPOODLESRAW: mprint ("\rAnalyzing data in McPoodle raw mode\n"); raw_loop(ctx); break; case CCX_SM_RCWT: mprint ("\rAnalyzing data in CCExtractor's binary format\n"); rcwt_loop(ctx); break; case CCX_SM_MYTH: mprint ("\rAnalyzing data in MythTV mode\n"); show_myth_banner = 1; myth_loop(ctx); break; case CCX_SM_MP4: mprint ("\rAnalyzing data with GPAC (MP4 library)\n"); close_input_file(ctx); // No need to have it open. GPAC will do it for us processmp4 (ctx, &ctx->mp4_cfg, ctx->inputfile[0]); if (ccx_options.print_file_reports) print_file_report(ctx); break; #ifdef WTV_DEBUG case CCX_SM_HEX_DUMP: close_input_file(ctx); // processhex will open it in text mode processhex (ctx, ctx->inputfile[0]); break; #endif case CCX_SM_AUTODETECT: fatal(CCX_COMMON_EXIT_BUG_BUG, "Cannot be reached!"); break; } #if 0 if (ctx->total_pulldownframes) mprint ("incl. pulldown frames: %s (%u frames at %.2ffps)\n", print_mstime( (LLONG)(ctx->total_pulldownframes*1000/current_fps) ), ctx->total_pulldownframes, current_fps); if (pts_set >= 1 && min_pts != 0x01FFFFFFFFLL) { LLONG postsyncms = (LLONG) (ctx->frames_since_last_gop*1000/current_fps); mprint ("\nMin PTS: %s\n", print_mstime( min_pts/(MPEG_CLOCK_FREQ/1000) - fts_offset)); if (pts_big_change) mprint ("(Reference clock was reset at some point, Min PTS is approximated)\n"); mprint ("Max PTS: %s\n", print_mstime( sync_pts/(MPEG_CLOCK_FREQ/1000) + postsyncms)); mprint ("Length: %s\n", print_mstime( sync_pts/(MPEG_CLOCK_FREQ/1000) + postsyncms - min_pts/(MPEG_CLOCK_FREQ/1000) + fts_offset )); } // dvr-ms files have invalid GOPs if (gop_time.inited && first_gop_time.inited && stream_mode != CCX_SM_ASF) { mprint ("\nInitial GOP time: %s\n", print_mstime(first_gop_time.ms)); mprint ("Final GOP time: %s%+3dF\n", print_mstime(gop_time.ms), ctx->frames_since_last_gop); mprint ("Diff. GOP length: %s%+3dF", print_mstime(gop_time.ms - first_gop_time.ms), ctx->frames_since_last_gop); mprint (" (%s)\n", print_mstime(gop_time.ms - first_gop_time.ms +(LLONG) ((ctx->frames_since_last_gop)*1000/29.97)) ); } if (ctx->false_pict_header) mprint ("\nNumber of likely false picture headers (discarded): %d\n",ctx->false_pict_header); if (ctx->stat_numuserheaders) mprint("\nTotal user data fields: %d\n", ctx->stat_numuserheaders); if (ctx->stat_dvdccheaders) mprint("DVD-type user data fields: %d\n", ctx->stat_dvdccheaders); if (ctx->stat_scte20ccheaders) mprint("SCTE-20 type user data fields: %d\n", ctx->stat_scte20ccheaders); if (ctx->stat_replay4000headers) mprint("ReplayTV 4000 user data fields: %d\n", ctx->stat_replay4000headers); if (ctx->stat_replay5000headers) mprint("ReplayTV 5000 user data fields: %d\n", ctx->stat_replay5000headers); if (ctx->stat_hdtv) mprint("HDTV type user data fields: %d\n", ctx->stat_hdtv); if (ctx->stat_dishheaders) mprint("Dish Network user data fields: %d\n", ctx->stat_dishheaders); if (ctx->stat_divicom) { mprint("CEA608/Divicom user data fields: %d\n", ctx->stat_divicom); mprint("\n\nNOTE! The CEA 608 / Divicom standard encoding for closed\n"); mprint("caption is not well understood!\n\n"); mprint("Please submit samples to the developers.\n\n\n"); } #endif list_for_each_entry(dec_ctx, &ctx->dec_ctx_head, list, struct lib_cc_decode) { mprint("\n"); dbg_print(CCX_DMT_DECODER_608, "\nTime stamps after last caption block was written:\n"); dbg_print(CCX_DMT_DECODER_608, "GOP: %s \n", print_mstime(gop_time.ms) ); dbg_print(CCX_DMT_DECODER_608, "GOP: %s (%+3dms incl.)\n", print_mstime((LLONG)(gop_time.ms -first_gop_time.ms +get_fts_max(dec_ctx->timing)-fts_at_gop_start)), (int)(get_fts_max(dec_ctx->timing)-fts_at_gop_start)); // When padding is active the CC block time should be within // 1000/29.97 us of the differences. dbg_print(CCX_DMT_DECODER_608, "Max. FTS: %s (without caption blocks since then)\n", print_mstime(get_fts_max(dec_ctx->timing))); if (dec_ctx->codec == CCX_CODEC_ATSC_CC) { mprint ("\nTotal frames time: %s (%u frames at %.2ffps)\n", print_mstime( (LLONG)(total_frames_count*1000/current_fps) ), total_frames_count, current_fps); } if (ctx->stat_hdtv) { mprint ("\rCC type 0: %d (%s)\n", dec_ctx->cc_stats[0], cc_types[0]); mprint ("CC type 1: %d (%s)\n", dec_ctx->cc_stats[1], cc_types[1]); mprint ("CC type 2: %d (%s)\n", dec_ctx->cc_stats[2], cc_types[2]); mprint ("CC type 3: %d (%s)\n", dec_ctx->cc_stats[3], cc_types[3]); } // Add one frame as fts_max marks the beginning of the last frame, // but we need the end. dec_ctx->timing->fts_global += dec_ctx->timing->fts_max + (LLONG) (1000.0/current_fps); // CFS: At least in Hauppage mode, cb_field can be responsible for ALL the // timing (cb_fields having a huge number and fts_now and fts_global being 0 all // the time), so we need to take that into account in fts_global before resetting // counters. if (cb_field1!=0) dec_ctx->timing->fts_global += cb_field1*1001/3; else if (cb_field2!=0) dec_ctx->timing->fts_global += cb_field2*1001/3; else dec_ctx->timing->fts_global += cb_708*1001/3; // Reset counters - This is needed if some captions are still buffered // and need to be written after the last file is processed. cb_field1 = 0; cb_field2 = 0; cb_708 = 0; dec_ctx->timing->fts_now = 0; dec_ctx->timing->fts_max = 0; } if(is_decoder_processed_enough(ctx) == CCX_TRUE) break; } // file loop close_input_file(ctx); prepare_for_new_file (ctx); // To reset counters used by handle_end_of_data() time (&final); long proc_time=(long) (final-start); mprint ("\rDone, processing time = %ld seconds\n", proc_time); #if 0 if (proc_time>0) { LLONG ratio=(get_fts_max()/10)/proc_time; unsigned s1=(unsigned) (ratio/100); unsigned s2=(unsigned) (ratio%100); mprint ("Performance (real length/process time) = %u.%02u\n", s1, s2); } #endif dbg_print(CCX_DMT_708, "[CEA-708] The 708 decoder was reset [%d] times.\n", ctx->freport.data_from_708->reset_count); if (is_decoder_processed_enough(ctx) == CCX_TRUE) { mprint ("\rNote: Processing was cancelled before all data was processed because\n"); mprint ("\rone or more user-defined limits were reached.\n"); } mprint ("This is beta software. Report issues to carlos at ccextractor org...\n"); if (show_myth_banner) { mprint ("NOTICE: Due to the major rework in 0.49, we needed to change part of the timing\n"); mprint ("code in the MythTV's branch. Please report results to the address above. If\n"); mprint ("something is broken it will be fixed. Thanks\n"); } dinit_libraries(&ctx); return EXIT_OK; }
void set_fts(void) { int pts_jump = 0; // ES don't have PTS unless GOP timing is used if (!pts_set && stream_mode==SM_ELEMENTARY_OR_NOT_FOUND) return; // First check for timeline jump (only when min_pts was // set (implies sync_pts). int dif = 0; if (pts_set == 2) { dif=(int) (current_pts-sync_pts)/MPEG_CLOCK_FREQ; // Used to distinguish gaps with missing caption information from // jumps in the timeline. (Currently only used for dvr-ms/NTSC // recordings) if ( CaptionGap ) dif = 0; // Disable sync check for raw formats - they have the right timeline. // Also true for bin formats, but -nosync might have created a // broken timeline for debug purposes. // Disable too in MP4, specs doesn't say that there can't be a jump switch (stream_mode) { case SM_MCPOODLESRAW: case SM_RCWT: case SM_MP4: dif = 0; break; default: break; } if (dif < -0.2 || dif >=5 ) { // ATSC specs: More than 3501 ms means missing component mprint ("\nWarning: Reference clock has changed abruptly (%d seconds), attempting to synchronize\n", (int) dif); mprint ("Last sync PTS value: %lld\n",sync_pts); mprint ("Current PTS value: %lld\n",current_pts); pts_jump = 1; pts_big_change=1; // Discard the gap if it is not on an I-frame or temporal reference // zero. if(current_tref != 0 && current_picture_coding_type != I_FRAME) { fts_now = fts_max; mprint ("Change did not occur on first frame - probably a broken GOP\n"); return; } } } // Set min_pts, fts_offset if (pts_set!=0) { pts_set=2; // Use this part only the first time min_pts is set. Later treat // it as a reference clock change if (current_pts<min_pts && !pts_jump) { // If this is the first GOP, and seq 0 was not encountered yet // we might reset min_pts/fts_offset again min_pts=current_pts; // Avoid next async test sync_pts = LLONG(current_pts -current_tref*1000.0/current_fps *(MPEG_CLOCK_FREQ/1000)); if(current_tref == 0) { // Earliest time in GOP. fts_offset = 0; } else if ( total_frames_count-frames_since_ref_time == 0 ) { // If this is the first frame (PES) there cannot be an offset. // This part is also reached for dvr-ms/NTSC (RAW) as // total_frames_count = frames_since_ref_time = 0 when // this is called for the first time. fts_offset = 0; } else { // It needs to be "+1" because the current frame is // not yet counted. fts_offset = LLONG((total_frames_count -frames_since_ref_time+1) *1000.0/current_fps); } dbg_print(DMT_TIME, "\nFirst sync time PTS: %s %+lldms (time before this PTS)\n", print_mstime(min_pts/(MPEG_CLOCK_FREQ/1000)), fts_offset ); dbg_print(DMT_TIME, "Total_frames_count %u frames_since_ref_time %u\n", total_frames_count, frames_since_ref_time); } // -nosync diasbles syncing if (pts_jump && !nosync) { // The current time in the old time base is calculated using // sync_pts (set at the beginning of the last GOP) plus the // time of the frames since then. fts_offset = fts_offset + (sync_pts-min_pts)/(MPEG_CLOCK_FREQ/1000) + LLONG(frames_since_ref_time*1000/current_fps); fts_max = fts_offset; // Start counting again from here pts_set=1; // Force min to be set again // Avoid next async test - the gap might have occured on // current_tref != 0. sync_pts = LLONG(current_pts -current_tref*1000.0/current_fps *(MPEG_CLOCK_FREQ/1000)); // Set min_pts = sync_pts as this is used for fts_now min_pts = sync_pts; dbg_print(DMT_TIME, "\nNew min PTS time: %s %+lldms (time before this PTS)\n", print_mstime(min_pts/(MPEG_CLOCK_FREQ/1000)), fts_offset ); } } // Set sync_pts, fts_offset if(current_tref == 0) sync_pts = current_pts; // Reset counters cb_field1 = 0; cb_field2 = 0; cb_708 = 0; // Avoid wrong "Calc. difference" and "Asynchronous by" numbers // for uninitialized min_pts if (1) // CFS: Remove or think decent condition { if ( pts_set ) { // If pts_set is TRUE we have min_pts fts_now = LLONG((current_pts-min_pts)/(MPEG_CLOCK_FREQ/1000) + fts_offset); } else { // No PTS info at all!! fatal(EXIT_BUG_BUG, "No PTS info. Please write bug report."); } } if ( fts_now > fts_max ) { fts_max = fts_now; } }
int do_cb (unsigned char *cc_block) { unsigned char cc_valid = (*cc_block & 4) >>2; unsigned char cc_type = *cc_block & 3; int timeok = 1; if ( fix_padding && cc_valid==0 && cc_type <= 1 // Only fix NTSC packets && cc_block[1]==0 && cc_block[2]==0 ) { /* Padding */ cc_valid=1; cc_block[1]=0x80; cc_block[2]=0x80; } // Print raw data with FTS. if (debug_cbraw) printf("%s %02X:%02X:%02X", print_mstime(fts_now + fts_global), cc_block[0], cc_block[1], cc_block[2]); /* In theory the writercwtdata() function could return early and not * go through the 608/708 cases below. We do that to get accurate * counts for cb_field1, cb_field2 and cb_708. * Note that printdata() and do_708() must not be called for * the OF_RCWT case. */ if (cc_valid || cc_type==3) { cc_stats[cc_type]++; switch (cc_type) { case 0: if (debug_cbraw) printf(" %s .. ..\n", debug_608toASC( cc_block, 0)); current_field=1; saw_caption_block = 1; if (extraction_start.set && get_fts() < extraction_start.time_in_ms) timeok = 0; if (extraction_end.set && get_fts() > extraction_end.time_in_ms) { timeok = 0; processed_enough=1; } if (timeok) { if(write_format!=OF_RCWT) printdata (cc_block+1,2,0,0); else writercwtdata(cc_block); } cb_field1++; break; case 1: if (debug_cbraw) printf(" .. %s ..\n", debug_608toASC( cc_block, 1)); current_field=2; saw_caption_block = 1; if (extraction_start.set && get_fts() < extraction_start.time_in_ms) timeok = 0; if (extraction_end.set && get_fts() > extraction_end.time_in_ms) { timeok = 0; processed_enough=1; } if (timeok) { if(write_format!=OF_RCWT) printdata (0,0,cc_block+1,2); else writercwtdata(cc_block); } cb_field2++; break; case 2: //EIA-708 // DTVCC packet data // Fall through case 3: //EIA-708 if (debug_cbraw) printf(" .. .. DD\n"); // DTVCC packet start current_field=3; if (extraction_start.set && get_fts() < extraction_start.time_in_ms) timeok = 0; if (extraction_end.set && get_fts() > extraction_end.time_in_ms) { timeok = 0; processed_enough=1; } char temp[4]; temp[0]=cc_valid; temp[1]=cc_type; temp[2]=cc_block[1]; temp[3]=cc_block[2]; if (timeok) { if(write_format!=OF_RCWT) do_708 ((const unsigned char *) temp, 4); else writercwtdata(cc_block); } cb_708++; // Check for bytes read // printf ("Warning: Losing EIA-708 data!\n"); break; default: fatal(EXIT_BUG_BUG, "Cannot be reached!"); } // switch (cc_type) } // cc_valid else { if (debug_cbraw) printf(" .. .. ..\n"); if (debug_verbose) printf("Found !(cc_valid || cc_type==3) - ignore this block\n"); } return 1; }