/* * Output a reversed representation of an H.264 (MPEG-4/AVC) or H.262 (MPEG-2) * elementary stream. * * Note that the input stream must be seekable, which means that an option * to read from standard input is not provided. * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the MPEG TS, PS and ES tools. * * The Initial Developer of the Original Code is Amino Communications Ltd. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Amino Communications Ltd, Swavesey, Cambridge UK * * ***** END LICENSE BLOCK ***** */ #include #include #include #include #include #ifdef _WIN32 #include #else // _WIN32 #include #endif // _WIN32 #include "compat.h" #include "es_fns.h" #include "nalunit_fns.h" #include "accessunit_fns.h" #include "h262_fns.h" #include "ts_fns.h" #include "tswrite_fns.h" #include "pes_fns.h" #include "reverse_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "version.h" #define DEBUG 0 #define SHOW_REVERSE_DATA 1 #if SHOW_REVERSE_DATA static int show_reverse_data = FALSE; #endif /* * Write out packet data as ES or TS. This is defined in reverse.c, but * otherwise unadvertised. */ extern int write_packet_data(WRITER output, int as_TS, byte data[], int data_len, uint32_t pid, byte stream_id); /* * Find the I slices in our input stream, and output them in reverse order. * * - `es` is the input elementary stream * - `output` is the stream to write to * - if `max` is non-zero, then reporting will stop after `max` MPEG items * - if `frequency` is non-zero, then attempt to produce the effect of * keeping every th picture (similar to reversing at a * multiplication factor of `frequency`) If 0, just retain all I pictures. * - if `as_TS` is true, then output as TS packets, not ES * - if `verbose` is true, then extra information will be output * - if `quiet` is true, then only errors will be reported * * Returns 0 if all went well, 1 if something went wrong. */ static int reverse_h262(ES_p es, WRITER output, int max, int frequency, int as_TS, int verbose, int quiet) { int err = 0; reverse_data_p reverse_data = NULL; h262_context_p hcontext = NULL; err = build_h262_context(es,&hcontext); if (err) return 1; err = build_reverse_data(&reverse_data,FALSE); if (err) { free_h262_context(&hcontext); return 1; } if (!quiet) print_msg("\nScanning forwards\n"); add_h262_reverse_context(hcontext,reverse_data); err = collect_reverse_h262(hcontext,max,verbose,quiet); if (err && err != EOF) { if (reverse_data->length > 0) { fprint_err("!!! Collected %d pictures and sequence headers," " continuing to reverse\n",reverse_data->length); } else { free_reverse_data(&reverse_data); free_h262_context(&hcontext); return 1; } } #if SHOW_REVERSE_DATA if (show_reverse_data) { int ii; for (ii=0; iilength; ii++) if (reverse_data->seq_offset[ii]) fprint_msg("%3d: %4d at " OFFSET_T_FORMAT "/%d for %d\n", ii,reverse_data->index[ii], reverse_data->start_file[ii], reverse_data->start_pkt[ii], reverse_data->data_len[ii]); else fprint_msg("%3d: seqh at " OFFSET_T_FORMAT "/%d for %d\n", ii, reverse_data->start_file[ii], reverse_data->start_pkt[ii], reverse_data->data_len[ii]); } if (!es->reading_ES) write_program_data(es->reader,output.ts_output); #endif if (!es->reading_ES) { // Just in case (it can't hurt) stop_server_output(es->reader); // But this is important set_PES_reader_video_only(es->reader,TRUE); } if (!quiet) print_msg("\nOutputting in reverse order\n"); if (as_TS) err = output_in_reverse_as_TS(es,output.ts_output,frequency,verbose,quiet, -1,0,reverse_data); else err = output_in_reverse_as_ES(es,output.es_output,frequency,verbose,quiet, -1,0,reverse_data); if (!err && !quiet) { uint32_t final_index = reverse_data->index[reverse_data->first_written]; print_msg("\n"); print_msg("Summary\n"); print_msg("=======\n"); print_msg(" Considered Used Written\n"); fprint_msg("Pictures %10d %10d (%4.1f%%) %10d (%4.1f%%)\n", final_index,reverse_data->pictures_kept, 100*(((double)reverse_data->pictures_kept)/final_index), reverse_data->pictures_written, 100*(((double)reverse_data->pictures_written)/final_index)); if (frequency != 0) fprint_msg("Target (pictures) . %10d (%4.1f%%) at requested" " frequency %d\n",final_index/frequency,100.0/frequency, frequency); } free_reverse_data(&reverse_data); free_h262_context(&hcontext); return err; } /* * Output any sequence and picture parameter sets * * Returns 0 if all went well, 1 if something went wrong. */ static int output_parameter_sets(WRITER output, access_unit_context_p context, int as_TS, int quiet) { nal_unit_context_p nac = context->nac; param_dict_p seq_param_dict = nac->seq_param_dict; param_dict_p pic_param_dict = nac->pic_param_dict; int ii; int err; for (ii = 0; ii < seq_param_dict->length; ii++) { ES_offset posn = seq_param_dict->posns[ii]; uint32_t length = seq_param_dict->data_lens[ii]; byte *data = NULL; if (!quiet) fprint_msg("Writing out sequence parameter set %d\n", seq_param_dict->ids[ii]); err = read_ES_data(nac->es,posn,length,NULL,&data); if (err) { fprint_err("### Error reading (sequence parameter set %d) data" " from " OFFSET_T_FORMAT "/%d for %d\n", seq_param_dict->ids[ii],posn.infile,posn.inpacket,length); return 1; } err = write_packet_data(output,as_TS,data,length,DEFAULT_VIDEO_PID, DEFAULT_VIDEO_STREAM_ID); free(data); if (err) { fprint_err("### Error writing out (sequence parameter set %d)" "data\n",seq_param_dict->ids[ii]); return 1; } } for (ii = 0; ii < pic_param_dict->length; ii++) { ES_offset posn = pic_param_dict->posns[ii]; uint32_t length = pic_param_dict->data_lens[ii]; byte *data = NULL; if (!quiet) fprint_msg("Writing out picture parameter set %d\n", pic_param_dict->ids[ii]); err = read_ES_data(nac->es,posn,length,NULL,&data); if (err) { fprint_err("### Error reading (picture parameter set %d) data" " from " OFFSET_T_FORMAT "/%d for %d\n", pic_param_dict->ids[ii],posn.infile,posn.inpacket,length); return 1; } err = write_packet_data(output,as_TS,data,length,DEFAULT_VIDEO_PID, DEFAULT_VIDEO_STREAM_ID); free(data); if (err) { fprint_err("### Error writing out (picture parameter set %d)" "data\n",pic_param_dict->ids[ii]); return 1; } } return 0; } /* * Find IDR and I access units, and output them in reverse order. * * Returns 0 if all went well, 1 if something went wrong. */ static int reverse_access_units(ES_p es, WRITER output, int max, int frequency, int as_TS, int verbose, int quiet) { int err = 0; reverse_data_p reverse_data = NULL; access_unit_context_p acontext = NULL; err = build_access_unit_context(es,&acontext); if (err) return 1; err = build_reverse_data(&reverse_data,TRUE); if (err) { free_access_unit_context(&acontext); return 1; } if (!quiet) print_msg("\nScanning forwards\n"); add_access_unit_reverse_context(acontext,reverse_data); err = collect_reverse_access_units(acontext,max,verbose,quiet); if (err && err != EOF) { if (reverse_data->length > 0) { fprint_err("!!! Collected %d access units," " continuing to reverse\n",reverse_data->length); } else { free_reverse_data(&reverse_data); free_access_unit_context(&acontext); return 1; } } #if SHOW_REVERSE_DATA if (show_reverse_data) { int ii; for (ii=0; iilength; ii++) fprint_msg("%3d: %4d at " OFFSET_T_FORMAT "/%d for %d\n", ii,reverse_data->index[ii], reverse_data->start_file[ii], reverse_data->start_pkt[ii], reverse_data->data_len[ii]); } //if (!es->reading_ES) // write_program_data(es->reader,output.ts_output); #endif if (!es->reading_ES) { // Just in case (it can't hurt) stop_server_output(es->reader); // But this is important set_PES_reader_video_only(es->reader,TRUE); } // Before outputting any reverse data, it's a good idea to write out the // picture parameter set(s) and sequence parameter set(s) if (!quiet) print_msg("\nPreparing to output reverse data\n"); err = output_parameter_sets(output,acontext,as_TS,quiet); if (err) { free_reverse_data(&reverse_data); free_access_unit_context(&acontext); return 1; } if (!quiet) print_msg("\nOutputting in reverse order\n"); if (as_TS) err = output_in_reverse_as_TS(es,output.ts_output,frequency,verbose,quiet, -1,0,reverse_data); else err = output_in_reverse_as_ES(es,output.es_output,frequency,verbose,quiet, -1,0,reverse_data); if (!err && !quiet) { uint32_t final_index = reverse_data->index[reverse_data->first_written]; print_msg("\n"); print_msg("Summary\n"); print_msg("=======\n"); print_msg(" Considered Used Written\n"); fprint_msg("Access units %10d %10d (%4.1f%%) %10d (%4.1f%%)\n", final_index,reverse_data->pictures_kept, 100*(((double)reverse_data->pictures_kept)/final_index), reverse_data->pictures_written, 100*(((double)reverse_data->pictures_written)/final_index)); if (frequency != 0) fprint_msg("Target (access units) . %10d (%4.1f%%) at requested" " frequency %d\n",final_index/frequency,100.0/frequency, frequency); } free_reverse_data(&reverse_data); free_access_unit_context(&acontext); return err; } static void print_usage() { print_msg( "Usage: esreverse [switches] [] []\n" "\n" ); REPORT_VERSION("esreverse"); print_msg( "\n" " Output a reversed stream derived from the input H.264 (MPEG-4/AVC)\n" " or H.262 (MPEG-2) elementary stream.\n" "\n" " If output is to an H.222 Transport Stream, then fixed values for\n" " the PMT PID (0x66) and video PID (0x68) are used.\n" "\n" "Files:\n" " is the input elementary stream.\n" " is the output stream, either an equivalent elementary\n" " stream, or an H.222 Transport Stream (but see -stdout\n" " and -host below).\n" "\n" "Switches:\n" " -verbose, -v Output additional (debugging) messages\n" " -err stdout Write error messages to standard output (the default)\n" " -err stderr Write error messages to standard error (Unix traditional)\n" " -quiet, -q Only output error messages\n" " -stdout Write output to , instead of a named file\n" " Forces -quiet and -err stderr.\n" " -host , -host :\n" " Writes output (over TCP/IP) to the named ,\n" " instead of to a named file. If is not\n" " specified, it defaults to 88. Implies -tsout.\n" " -max , -m Maximum number of frames to read\n" " -freq Specify the frequency of frames to try to keep\n" " when reversing. Defaults to 8.\n" " -tsout Output H.222 Transport Stream\n" "\n" " -pes, -ts The input file is TS or PS, to be read via the\n" " PES->ES reading mechanisms\n" " -server Also output as normal forward video as reversal\n" " data is being collected. Implies -pes and -tsout.\n" #if SHOW_REVERSE_DATA "\n" " -x Temporary extra debugging information\n" #endif "\n" "Stream type:\n" " If input is from a file, then the program will look at the start of\n" " the file to determine if the stream is H.264 or H.262 data. This\n" " process may occasionally come to the wrong conclusion, in which case\n" " the user can override the choice using the following switches.\n" "\n" " -h264, -avc Force the program to treat the input as MPEG-4/AVC.\n" " -h262 Force the program to treat the input as MPEG-2.\n" ); } int main(int argc, char **argv) { char *input_name = NULL; char *output_name = NULL; int had_input_name = FALSE; int had_output_name = FALSE; int use_stdout = FALSE; int use_tcpip = FALSE; int port = 88; // Useful default port number int err = 0; ES_p es = NULL; WRITER output; int max = 0; int as_TS = FALSE; int frequency = 8; // The default as stated in the usage int quiet = FALSE; int verbose = FALSE; int ii = 1; int use_pes = FALSE; int use_server = FALSE; int want_data = VIDEO_H262; int is_data; int force_stream_type = FALSE; byte stream_type; if (argc < 2) { print_usage(); return 0; } output.es_output = NULL; while (ii < argc) { if (argv[ii][0] == '-') { if (!strcmp("--help",argv[ii]) || !strcmp("-help",argv[ii]) || !strcmp("-h",argv[ii])) { print_usage(); return 0; } #if SHOW_REVERSE_DATA else if (!strcmp("-x",argv[ii])) show_reverse_data = TRUE; #endif else if (!strcmp("-avc",argv[ii]) || !strcmp("-h264",argv[ii])) { force_stream_type = TRUE; want_data = VIDEO_H264; } else if (!strcmp("-h262",argv[ii])) { force_stream_type = TRUE; want_data = VIDEO_H262; } else if (!strcmp("-pes",argv[ii]) || !strcmp("-ts",argv[ii])) use_pes = TRUE; else if (!strcmp("-server",argv[ii])) { use_server = TRUE; use_pes = TRUE; as_TS = TRUE; } else if (!strcmp("-tsout",argv[ii])) as_TS = TRUE; else if (!strcmp("-stdout",argv[ii])) { had_output_name = TRUE; // more or less use_stdout = TRUE; redirect_output_stderr(); } else if (!strcmp("-err",argv[ii])) { CHECKARG("esreverse",ii); if (!strcmp(argv[ii+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[ii+1],"stdout")) redirect_output_stdout(); else { fprint_err("### esreverse: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[ii+1]); return 1; } ii++; } else if (!strcmp("-host",argv[ii])) { CHECKARG("esreverse",ii); err = host_value("esreverse",argv[ii],argv[ii+1],&output_name,&port); if (err) return 1; had_output_name = TRUE; // more or less use_tcpip = TRUE; as_TS = TRUE; ii++; } else if (!strcmp("-verbose",argv[ii]) || !strcmp("-v",argv[ii])) { verbose = TRUE; quiet = FALSE; } else if (!strcmp("-quiet",argv[ii]) || !strcmp("-q",argv[ii])) { verbose = FALSE; quiet = TRUE; } else if (!strcmp("-max",argv[ii]) || !strcmp("-m",argv[ii])) { CHECKARG("esreverse",ii); err = int_value("esreverse",argv[ii],argv[ii+1],TRUE,10,&max); if (err) return 1; ii++; } else if (!strcmp("-freq",argv[ii])) { CHECKARG("esreverse",ii); err = int_value("esreverse",argv[ii],argv[ii+1],TRUE,10,&frequency); if (err) return 1; ii++; } else { fprint_err("### esreverse: " "Unrecognised command line switch '%s'\n",argv[ii]); return 1; } } else { if (had_input_name && had_output_name) { fprint_err("### esreverse: Unexpected '%s'\n",argv[ii]); return 1; } else if (had_input_name) { output_name = argv[ii]; had_output_name = TRUE; } else { input_name = argv[ii]; had_input_name = TRUE; } } ii++; } if (!had_input_name) { print_err("### esreverse: No input file specified\n"); return 1; } if (!had_output_name) { print_err("### esreverse: No output file specified\n"); return 1; } // Try to stop extraneous data ending up in our output stream if (use_stdout) { verbose = FALSE; quiet = TRUE; } err = open_input_as_ES(input_name,use_pes,quiet, force_stream_type,want_data,&is_data,&es); if (err) { print_err("### esreverse: Error opening input file\n"); return 1; } if (is_data == VIDEO_H262) stream_type = MPEG2_VIDEO_STREAM_TYPE; else if (is_data == VIDEO_H264) stream_type = AVC_VIDEO_STREAM_TYPE; else { print_err("### esreverse: Unexpected type of video data\n"); return 1; } if (as_TS) { if (use_stdout) err = tswrite_open(TS_W_STDOUT,NULL,NULL,0,quiet,&(output.ts_output)); else if (use_tcpip) err = tswrite_open(TS_W_TCP,output_name,NULL,port,quiet,&(output.ts_output)); else err = tswrite_open(TS_W_FILE,output_name,NULL,0,quiet,&(output.ts_output)); if (err) { fprint_err("### esreverse: Unable to open %s\n",output_name); (void) close_input_as_ES(input_name,&es); return 1; } } else { output.es_output = fopen(output_name,"wb"); if (output.es_output == NULL) { fprint_err("### esreverse: Unable to open output file %s: %s\n", output_name,strerror(errno)); (void) close_input_as_ES(input_name,&es); return 1; } if (!quiet) fprint_msg("Writing to %s\n",output_name); } if (!quiet) { if (as_TS) print_msg("Writing as Transport Stream\n"); fprint_msg("Filtering freqency %d\n",frequency); if (max) fprint_msg("Stopping as soon after %d %s as possible\n",max, (is_data == VIDEO_H262?"MPEG2 items":"NAL units")); } if (use_pes) { #if SHOW_REVERSE_DATA if (show_reverse_data) es->reader->debug_read_packets = TRUE; #endif if (use_server) { // For testing purposes, let's try outputting video as we collect data set_server_output(es->reader,output.ts_output,FALSE,100); es->reader->debug_read_packets = TRUE; } } // If we're writing out TS data, start it off now // (we mustn't do it after our forwards-processing function, // because that itself may output some data...) if (as_TS) { if (use_pes) { if (!quiet) fprint_msg("Using transport stream id 1, PMT PID %#x, program 1 =" " PID %#x\n",DEFAULT_PMT_PID,DEFAULT_VIDEO_PID); set_PES_reader_program_data(es->reader,1,DEFAULT_PMT_PID, DEFAULT_VIDEO_PID, DEFAULT_AUDIO_PID, // not actually used DEFAULT_VIDEO_PID); // video as PCR // Note that (a) the server output will write program data for us, // and (b) for the moment, the TS writer does not allow us to set the // stream_type } else { if (!quiet) fprint_msg("Using transport stream id 1, PMT PID %#x, program 1 =" " PID %#x, stream type %#x\n",DEFAULT_PMT_PID,DEFAULT_VIDEO_PID, stream_type); err = write_TS_program_data(output.ts_output, 1,1,DEFAULT_PMT_PID,DEFAULT_VIDEO_PID, stream_type); if (err) { print_err("### esreverse: Error writing out TS program data\n"); (void) close_input_as_ES(input_name,&es); if (as_TS) (void) tswrite_close(output.ts_output,TRUE); else if (had_output_name && !use_stdout) { err = fclose(output.es_output); if (err) fprint_err("### esreverse: (Error closing output file %s: %s)\n", output_name,strerror(errno)); } return 1; } } } if (is_data == VIDEO_H262) err = reverse_h262(es,output,max,frequency,as_TS,verbose,quiet); else err = reverse_access_units(es,output,max,frequency,as_TS,verbose,quiet); if (err) { print_err("### esreverse: Error reversing input\n"); (void) close_input_as_ES(input_name,&es); if (as_TS) (void) tswrite_close(output.ts_output,TRUE); else if (had_output_name && !use_stdout) { err = fclose(output.es_output); if (err) fprint_err("### esreverse: (Error closing output file %s: %s)\n", output_name,strerror(errno)); } return 1; } // And tidy up when we're finished if (as_TS) { err = tswrite_close(output.ts_output,quiet); if (err) { fprint_err("### esreverse: Error closing output file %s", output_name); (void) close_input_as_ES(input_name,&es); return 1; } } else if (!use_stdout) { errno = 0; err = fclose(output.es_output); if (err) { fprint_err("### esreverse: Error closing output file %s: %s\n", output_name,strerror(errno)); (void) close_input_as_ES(input_name,&es); return 1; } } err = close_input_as_ES(input_name,&es); if (err) { print_err("### esreverse: Error closing input file\n"); return 1; } return 0; } // Local Variables: // tab-width: 8 // indent-tabs-mode: nil // c-basic-offset: 2 // End: // vim: set tabstop=8 shiftwidth=2 expandtab: