/* * Serve TS packets from TS or PS data, supporting playing forwards * at normal and accelerated speeds, and reverse play. * * ***** 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 #include #ifdef _WIN32 #include #include #include #include #else // _WIN32 #include #include #include #include // WNOHANG #include // sockaddr_in #include // sigaction, etc. #endif // _WIN32 #include // Sleeping and timing #include "compat.h" #include "ts_fns.h" #include "ps_fns.h" #include "pes_fns.h" #include "accessunit_fns.h" #include "nalunit_fns.h" #include "misc_fns.h" #include "printing_fns.h" #include "tswrite_fns.h" #include "es_fns.h" #include "h262_fns.h" #include "filter_fns.h" #include "reverse_fns.h" #include "version.h" //#define DEBUG #define SHOW_REVERSE_DATA 1 #define DEBUG_COMMANDS 1 #define TIME_SKIPPING 1 #if TIME_SKIPPING #include #endif #define DEFAULT_REVERSE_FREQUENCY 8 #define DEFAULT_FORWARD_FREQUENCY 8 #define FRAMES_FOR_ONE_SECOND 25 #define SMALL_SKIP_DISTANCE 10*FRAMES_FOR_ONE_SECOND // 10 seconds #define BIG_SKIP_DISTANCE 3*60*FRAMES_FOR_ONE_SECOND // 3 minutes static int extra_info = 0; // What we are to do enum ACTION { ACTION_SERVER, // The default action is to be a server ACTION_CMD, // An alternative is to connect and read commands ACTION_TEST, // One of the testing modes }; #define MAX_INPUT_FILES 10 // i.e., 0..9 // Command line data // There's a lot of data from the command line that needs passing down // from the top level to the main processing functions, so let's package // it up neatly struct tsserve_context { char *input_names[MAX_INPUT_FILES]; // The files to read from int default_file_index; // Which one is the default int video_only; // As it says - no audio? int pad_start; // Number of filler packets to output at start int ffrequency; // Fast forward frequency when filtering int rfrequency; // Base reverse frequency int with_seq_hdrs;// For H.262, output sequence headers when not // doing normal play? int pes_padding; // Number of dummy PES packets to output per real packet int drop_packets; // 0 or drop TS packets every on output int drop_number; // how many packets to drop // Program Stream specific options uint32_t pmt_pid; uint32_t audio_pid; uint32_t video_pid; uint32_t pcr_pid; int want_h262; int dolby_is_dvb; int force_stream_type; int repeat_program_every; // Transport Stream specific options int tsdirect; }; typedef struct tsserve_context *tsserve_context_p; // ============================================================ // Unions to give us a single view of the two forms of data stream // ============================================================ // The form of this single view is limited solely to what is needed // in this program - it is not intended to be a general unification // of the two types of data. // Accessing the data stream union u_stream_context { h262_context_p h262; access_unit_context_p h264; }; struct _stream_context { int is_h262; int program_number; union u_stream_context u; }; typedef struct _stream_context stream_context; typedef struct _stream_context *stream_context_p; // Filtering union u_filter_context { h262_filter_context_p h262; h264_filter_context_p h264; }; struct _filter_context { int is_h262; union u_filter_context u; }; typedef struct _filter_context filter_context; typedef struct _filter_context *filter_context_p; // Pictures union u_picture { int is_h262; h262_picture_p h262; access_unit_p h264; }; struct _picture { int is_h262; union u_picture u; int type; // For H.262, the picture coding type. 0xFF means seq hdr }; typedef struct _picture picture; typedef struct _picture *picture_p; // ============================================================ // Utilities to hide the difference between the two data stream types // ============================================================ // A macro to avoid my mistyping this on the several occasions I need it #define EXTRACT_ES_FROM_STREAM(stream) \ ((stream).is_h262?(stream).u.h262->es:(stream).u.h264->nac->es) // A related macro for reverse data #define EXTRACT_REVERSE_FROM_STREAM(stream) \ ((stream).is_h262?(stream).u.h262->reverse_data:(stream).u.h264->reverse_data) /* * Note that `program_number` should be 1 or more. */ static int build_stream(ES_p es, int is_h262, int program_number, stream_context *stream) { int err; stream->is_h262 = is_h262; stream->program_number = program_number; if (is_h262) { err = build_h262_context(es,&(stream->u.h262)); if (err) { print_err("### Error building H.262 context\n"); return 1; } } else { err = build_access_unit_context(es,&(stream->u.h264)); if (err) { print_err("### Error building H.264 access unit context\n"); return 1; } } return 0; } static void close_stream(stream_context stream) { if (stream.is_h262) free_h262_context(&(stream.u.h262)); else free_access_unit_context(&(stream.u.h264)); } static int build_and_attach_reverse(stream_context stream, reverse_data_p *reverse_data) { int err; err = build_reverse_data(reverse_data,!stream.is_h262); if (err) { print_err("### Unable to build reverse memory\n"); return 1; } if (stream.is_h262) add_h262_reverse_context(stream.u.h262,*reverse_data); else add_access_unit_reverse_context(stream.u.h264,*reverse_data); return 0; } static int build_filter_context(stream_context stream, int is_strip, int frequency, filter_context *fcontext) { int err; fcontext->is_h262 = stream.is_h262; if (stream.is_h262) { if (is_strip) err = build_h262_filter_context_strip(&(fcontext->u.h262), stream.u.h262,TRUE); else err = build_h262_filter_context(&(fcontext->u.h262), stream.u.h262,frequency); } else { if (is_strip) err = build_h264_filter_context_strip(&(fcontext->u.h264), stream.u.h264,TRUE); else err = build_h264_filter_context(&(fcontext->u.h264), stream.u.h264,frequency); } return err; } static void free_filter_context(filter_context fcontext) { if (fcontext.is_h262) free_h262_filter_context(&(fcontext.u.h262)); else free_h264_filter_context(&(fcontext.u.h264)); } /* * "Reset" a stream, such that the picture reading contexts do not contain * any past memory. */ static inline void reset_stream(stream_context stream) { if (stream.is_h262) { if (stream.u.h262->last_item) free_h262_item(&stream.u.h262->last_item); } else { reset_access_unit_context(stream.u.h264); } } /* * Retrieve the next picture. Doesn't distinguish H.262 sequence headers * and pictures. */ static inline int get_next_picture(stream_context stream, int verbose, int quiet, picture *pic) { int err; if (stream.is_h262) { h262_picture_p picture; err = get_next_h262_frame(stream.u.h262,verbose,quiet,&picture); if (err) return err; pic->is_h262 = TRUE; pic->u.h262 = picture; if (picture->is_picture) pic->type = picture->picture_coding_type; else pic->type = 0xff; } else { access_unit_p unit; err = get_next_h264_frame(stream.u.h264,quiet,verbose,&unit); if (err) return err; pic->is_h262 = FALSE; pic->u.h264 = unit; } return 0; } static inline void free_picture(picture *pic) { if (pic->is_h262) free_h262_picture(&pic->u.h262); else free_access_unit(&pic->u.h264); pic->type = 0; } /* * Needs to be told if the picture is H.262 or not, because this may be * called on an unused instance of the picture data structure. */ static inline void unset_picture(int is_h262, picture *pic) { if (is_h262) pic->u.h262 = NULL; else pic->u.h264 = NULL; pic->is_h262 = is_h262; } static inline int is_null_picture(picture pic) { if (pic.is_h262) return pic.u.h262 == NULL; else return pic.u.h264 == NULL; } // NB: there is already a macro called "is_seq_header" static inline int is_non_frame(picture pic) { return (pic.is_h262 && pic.type == 0xff); } static inline int is_reference_picture(picture pic) { if (pic.is_h262) return (pic.type == 1 || pic.type == 2); else return (pic.u.h264->primary_start != NULL && pic.u.h264->primary_start->nal_ref_idc != 0); } static inline int is_I_or_IDR_picture(picture pic) { if (pic.is_h262) return (pic.type == 1); else return (pic.u.h264->primary_start != NULL && pic.u.h264->primary_start->nal_ref_idc != 0 && (pic.u.h264->primary_start->nal_unit_type == NAL_IDR || all_slices_I(pic.u.h264))); } static void print_picture(picture pic) { if (pic.is_h262) { if (pic.type == 0xff) print_msg("sequence header"); else fprint_msg("%s picture",H262_PICTURE_CODING_STR(pic.type)); } else { if (pic.u.h264->primary_start == NULL) print_msg(""); else fprint_msg("idc %d/type %d (%s)", pic.u.h264->primary_start->nal_ref_idc, pic.u.h264->primary_start->nal_unit_type, NAL_UNIT_TYPE_STR(pic.u.h264->primary_start->nal_unit_type)); } } static inline int write_picture_as_TS(stream_context stream, TS_writer_p output, picture pic) { ES_p es = EXTRACT_ES_FROM_STREAM(stream); if (stream.is_h262) return write_h262_picture_as_TS(output,pic.u.h262, es->reader->output_video_pid); else return write_access_unit_as_TS(pic.u.h264,stream.u.h264, output,es->reader->output_video_pid); } static inline void reset_filter_context(filter_context fcontext, int frequency) { if (fcontext.is_h262) { reset_h262_filter_context(fcontext.u.h262); fcontext.u.h262->freq = frequency; } else { reset_h264_filter_context(fcontext.u.h264); fcontext.u.h264->freq = frequency; } } static inline int get_next_stripped(filter_context fcontext, int verbose, int quiet, picture *seq_hdr, picture *this_picture, int *delta_pictures_seen) { int err; unset_picture(fcontext.is_h262,seq_hdr); unset_picture(fcontext.is_h262,this_picture); if (fcontext.is_h262) { h262_picture_p _this_picture = NULL; h262_picture_p _seq_hdr = NULL; err = get_next_stripped_h262_frame(fcontext.u.h262,verbose,quiet,&_seq_hdr, &_this_picture,delta_pictures_seen); seq_hdr->u.h262 = _seq_hdr; this_picture->u.h262 = _this_picture; } else { access_unit_p this_unit = NULL; err = get_next_stripped_h264_frame(fcontext.u.h264,verbose,quiet, &this_unit,delta_pictures_seen); this_picture->u.h264 = this_unit; } return err; } static inline int get_next_filtered(filter_context fcontext, int verbose, int quiet, picture *seq_hdr, picture *this_picture, int *delta_pictures_seen) { int err; unset_picture(fcontext.is_h262,seq_hdr); unset_picture(fcontext.is_h262,this_picture); if (fcontext.is_h262) { h262_picture_p _this_picture = NULL; h262_picture_p _seq_hdr = NULL; err = get_next_filtered_h262_frame(fcontext.u.h262,verbose,quiet,&_seq_hdr, &_this_picture,delta_pictures_seen); seq_hdr->u.h262 = _seq_hdr; this_picture->u.h262 = _this_picture; } else { access_unit_p this_unit = NULL; err = get_next_filtered_h264_frame(fcontext.u.h264,verbose,quiet, &this_unit,delta_pictures_seen); this_picture->u.h264 = this_unit; } return err; } // ============================================================ // A common view of handling the two types of data stream // ============================================================ /* * Playing at normal speed happens as a "side effect" of gathering * information to allow us to reverse. Basically, each time a PES packet * is read in, it gets automatically written out for us, whilst we * analyse its contents. * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int play_normal(stream_context stream, TS_writer_p output, int verbose, int quiet, int num_normal, int tsdirect, reverse_data_p reverse_data) { int err; ES_p es = EXTRACT_ES_FROM_STREAM(stream); PES_reader_p reader = es->reader; if (extra_info) print_msg("Playing at normal speed\n"); /* Do not write program data if we're in tsdirect mode - * it'll change and some programs can't cope */ if (!tsdirect) { err = write_program_data(reader,output); if (err) return err; } start_server_output(reader); if (stream.is_h262) { err = collect_reverse_h262(stream.u.h262,num_normal,verbose,quiet); if (err) return err; } else { err = collect_reverse_access_units(stream.u.h264,num_normal,verbose,quiet); if (err) return err; } return 0; } /* * Flush our PES packet after normal play * * Call this with server output still on. * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int flush_after_normal(stream_context stream, TS_writer_p output, int verbose, int quiet) { int err; ES_p es = EXTRACT_ES_FROM_STREAM(stream); PES_reader_p reader = es->reader; ES_offset item_start; if (extra_info) print_msg("Flushing PES data after normal play\n"); if (reader->packet == NULL) { // We're apparently at the end of file, so there's not much we can do return 0; } // When playing forwards at normal speed, each PES packet is read in, // processed to extract information, and then (automatically) written // out again when the next PES packet is read in. // // However, when we start doing fast forward or reverse, that automatic // output of PES packets is switched off. Thus it is up to us to ensure // that any outstanding data gets output before that. // // A command character is received when a write (of a PES packet) is made, // and such a write is (as said above) triggered when a new PES packet has // to be read in. *That* happens when reading the next ES item requires // reading a byte from the next PES packet. Thus we know that the current // picture started in the last PES packet, and that the ES item that // comes after it ends in the next packet. // // To terminate the data for that picture neatly in the output, we thus // need to output the ES data from this PES packet up to the end of the // current picture. if (stream.is_h262) { if (stream.u.h262->last_item == NULL) { if (extra_info) print_msg(".. no H.262 last item\n"); return 0; // not much else we can do } // The ES item that comes after (and thus marks the end of) the // last picture *starts* at: item_start = stream.u.h262->last_item->unit.start_posn; } else { if (stream.u.h264->pending_nal == NULL) { // We ended the previous access unit for some reason that didn't // need to read the next NAL unit, or we've not read anything in yet item_start = es->posn_of_next_byte; } else { // The previous access unit was ended by this "pending" NAL unit, // so the "next" item presumably starts with that... item_start = stream.u.h264->pending_nal->unit.start_posn; } } if (extra_info) fprint_msg(".. last item starts at " OFFSET_T_FORMAT "/%d\n", item_start.infile,item_start.inpacket); // We know we haven't written out any data for the current PES packet // - do we need to? if (item_start.infile < reader->packet->posn) { // The terminating item started in the previous packet, which we've // already output. We should read in the next picture, and output // that part of it which hasn't already been output. picture picture; if (extra_info) print_msg(".. which is in the previous packet - " "reading spanning picture into next packet\n"); err = get_next_picture(stream,verbose,quiet,&picture); if (err == EOF) { // Clearly there is no next picture if (extra_info) print_msg("End of file\n"); return err; } else if (err) { print_err("### Error trying to read into next packet whilst" " flushing after normal play\n"); return 1; } free_picture(&picture); // We now know that we want to output from the current PES packet to the // end of this picture, which is one byte before the new terminating // item if (stream.is_h262) item_start = stream.u.h262->last_item->unit.start_posn; else { if (stream.u.h264->pending_nal == NULL) item_start = es->posn_of_next_byte; else item_start = stream.u.h264->pending_nal->unit.start_posn; } if (extra_info) fprint_msg(".. new last item starts at " OFFSET_T_FORMAT "/%d\n",item_start.infile,item_start.inpacket); } if (item_start.inpacket == 0) { // The terminating item started at the beginning of this packet, // so we don't have any outstanding data to output. if (extra_info) print_msg(".. so there's no need to output any of this" " packet\n"); return 0; } // We need to output whatever came before the terminating item if (extra_info) fprint_msg(".. so need to output %d bytes of this" " packet\n",item_start.inpacket); err = write_ES_as_TS_PES_packet(output, reader->packet->es_data, item_start.inpacket, reader->output_video_pid, DEFAULT_VIDEO_STREAM_ID); if (err) { print_err("### Error flushing start of PES packet after normal play\n"); return 1; } return 0; } /* * Output the next reference picture. If `I_only` then only output the next * I (or IDR) picture. * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. */ static int output_next_reference_picture(stream_context stream, TS_writer_p output, int verbose, int quiet, int I_only) { int err; picture picture; if (extra_info) print_msg(".. outputting next reference picture\n"); for (;;) { err = get_next_picture(stream,verbose,quiet,&picture); if (err == EOF) { // Clearly there is no next picture - so we can't output it if (extra_info) print_msg("End of file\n"); return err; } else if (err) { print_err("### Error trying to resynchronise after fast forward\n"); return 1; } if (extra_info) { print_msg(".. read next picture: "); print_picture(picture); print_msg("\n"); } if (is_non_frame(picture)) { // A sequence header doesn't help us directly, but we can output // it as it will in practise be followed by an I picture // A sequence end will be followed by a sequence header, so we can // treat it similarly if (extra_info) print_msg(".. writing it out\n"); err = write_picture_as_TS(stream,output,picture); if (err) { print_err("### Error writing out picture list\n"); free_picture(&picture); return 1; } } else if (( I_only && is_I_or_IDR_picture(picture)) || (!I_only && is_reference_picture(picture))) { if (extra_info) print_msg(".. picture acceptable\n"); break; } free_picture(&picture); } // So we've got something sensible to continue with // - don't forget to write it out! if (extra_info) print_msg(".. writing it out\n"); err = write_picture_as_TS(stream,output,picture); if (err) { print_err("### Error writing out picture list\n"); free_picture(&picture); return 1; } free_picture(&picture); return 0; } /* * Resynchronise after reverse, ready for forwards playing (at whatever speed) * * Always call this immediately after reversing. * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int resync_after_reverse(stream_context stream, TS_writer_p output, int verbose, int quiet) { int err; ES_p es = EXTRACT_ES_FROM_STREAM(stream); if (extra_info) print_msg(" \nResynchronising PES packets after reverse\n"); // When reversing, data is read directly from the required locations // in the input file, without using the normal "get next picture" // mechanisms. // // When we *stop* reversing, we need to "pretend" to have read to the // (end of the) last picture output by the normal mechanisms // Undo any memory of previous pictures/context reset_stream(stream); if (extra_info) fprint_msg(" triple byte = %02x,%02x,%02x, next byte to be from " OFFSET_T_FORMAT "/%d\n", es->prev2_byte,es->prev1_byte,es->cur_byte, es->posn_of_next_byte.infile, es->posn_of_next_byte.inpacket); // @@@ The following is not true, methinks, as we've been outputting IDR // and I frames (since there are not enough I frames, in hp-trail // at least). On the other hand, there's not much we can do about it. // If we've just been reversing H.264 data, we know we've just output an // IDR, and we also know that IDRs act as "backstops" for B pictures - they // can't refer "through" them. Thus we don't need to worry about outputting // anything extra. // @@@ Even for H.264, it may be safer to output another reference picture, // and it does help get the internal datastructures back in synch. //if (!stream.is_h262) // return 0; // However, if it's H.262 data, we know we've just output a reference // picture (specifically, an I picture), but that we can't safely output // a B picture until we've output the *next* reference picture, since B // pictures need to refer "back" (in decoding order - back and forwards // in "play" order) to two reference pictures. err = output_next_reference_picture(stream,output,verbose,quiet,FALSE); if (err == EOF) return EOF; else if (err) { print_err("### Error outputting next reference picture," " after reversing\n"); return 1; } return 0; } /* * "Rewind" to the start of our stream, ready to start again from the * beginning. * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int rewind_stream(stream_context stream) { if (extra_info) print_msg(" \nRewinding\n"); if (stream.is_h262) return rewind_h262_context(stream.u.h262); else return rewind_access_unit_context(stream.u.h264); } /* * Resynchronise playing after fast fast forwarding. Always call this when * changing from fast forward back to normal speed playing. We want to * be in video only mode for this function. * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int resync_after_filter(stream_context stream, TS_writer_p output, int verbose, int quiet) { int err; if (extra_info) print_msg(" \nResynchronising after fast fast forward\n"); // Fast forwarding with "filter" drops reference frames. // B pictures refer "back" (in decoding order) to two the last two // reference frames (although, in H.264 an IDR acts as a "stop" to this). // // If we've just been filtering, we know we just output *some* reference // picture, but we probably (almost certainly) hadn't output the preceding // reference picture. // // Specifically, typical data might be laid out as (using the output // of esdots, slightly massaged):: // // [E>iE bE bE pE bE bE pE bE bE ... // // get_next_filtered_picture() returns us the "i" picture, and if we // then get the 'obvious' next data, we'll end up with a "b" picture, // which is not what we want. Thus we need to read forwards until // we reach the next "i" or "p" picture (in this case, it would be the // next "p" picture). // @@@ For H.264, it might perhaps make more sense to "reverse" to the // last IDR, output *that*, and then just continue playing. We know that // B pictures can't refer backwards "through" an IDR. This might also // *look* better when we've finished fast forwarding... // So... err = output_next_reference_picture(stream,output,verbose,quiet,FALSE); if (err == EOF) return EOF; else if (err) { print_err("### Error outputting next reference picture," " after fast forwarding\n"); return 1; } return 0; } /* * Resynchronise playing ready for normal playing again. * * Call this with server output off, but turn it on again immediately * after this call. * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int back_to_normal(stream_context stream, TS_writer_p output, int tsdirect) { int err; ES_p es = EXTRACT_ES_FROM_STREAM(stream); PES_reader_p reader = es->reader; ES_offset item_start; if (extra_info) print_msg(" \nResynchronising PES packets for normal play\n"); if (reader->packet == NULL) { // We appear to have reached the end of file - there's not much // we can do about this, nor (probably) should we return 0; } if (!tsdirect) { // It can't hurt to reiterate the program data, and if we were just // playing a different program stream, it's a good idea err = write_program_data(reader,output); if (err) return err; } // When playing forwards at normal speed, each PES packet is read in, // processed to extract information, and then (automatically) written // out again when the next PES packet is read in. // // However, if we have been fast forwarding (at whatever speed), then we // will have been outputting only some pictures, and not outputting PES // packets automatically. // // In this case, we need to make it appear as if the (rest of the) // current PES packet had been output by the automatic mechanisms. // // In this context, "the rest of the PES packet" means all the data // (in this PES packet) from the start of the H.262 item that stopped us // reading the last picture // // However, if we have instead been reversing, then we do not have a last // item (since reversing just outputs uninterpreted chunks of data). We do, // however, still know the first byte of the next piece of information after // that chunk of data, and that should be enough. if (stream.is_h262) { if (stream.u.h262->last_item == NULL) { if (extra_info) print_msg(".. no H.262 last item, presumably been" " reversing\n"); item_start = es->posn_of_next_byte; // In which case, we've already output the data for our "last" item // and only some of the following cases can occur... } else { // The ES item that comes after (and thus marks the end of) the last // picture *starts* at: item_start = stream.u.h262->last_item->unit.start_posn; } } else { if (stream.u.h264->pending_nal == NULL) { // Either we ended the previous access unit for some reason that // didn't need to read the next NAL unit, or we've been reversing // (or we just started and there was no previous access unit) item_start = es->posn_of_next_byte; } else { // The previous access unit was ended by this "pending" NAL unit, // so the "next" item presumably starts with that... item_start = stream.u.h264->pending_nal->unit.start_posn; } } if (extra_info) { fprint_msg(".. posn_of_next_byte is " OFFSET_T_FORMAT "/%d\n", es->posn_of_next_byte.infile,es->posn_of_next_byte.inpacket); if (stream.is_h262) { if (stream.u.h262->last_item) { fprint_msg(" last item starts at " OFFSET_T_FORMAT "/%d,\n", stream.u.h262->last_item->unit.start_posn.infile, stream.u.h262->last_item->unit.start_posn.inpacket); print_data(TRUE," last item", stream.u.h262->last_item->unit.data, stream.u.h262->last_item->unit.data_len,20); } } else { if (stream.u.h264->pending_nal) { fprint_msg(" last item starts at " OFFSET_T_FORMAT "/%d,\n", stream.u.h264->pending_nal->unit.start_posn.infile, stream.u.h264->pending_nal->unit.start_posn.inpacket); print_data(TRUE," pending NAL unit", stream.u.h264->pending_nal->unit.data, stream.u.h264->pending_nal->unit.data_len,20); } } fprint_msg(".. i.e., last item starts at " OFFSET_T_FORMAT "/%d\n", item_start.infile,item_start.inpacket); fprint_msg(" PES ES data length is %d\n" " difference is %d\n", reader->packet->es_data_len, reader->packet->es_data_len-item_start.inpacket); fprint_msg(" reader->packet->posn is " OFFSET_T_FORMAT "\n", reader->packet->posn); } if (item_start.infile < reader->packet->posn) { // Said last item started in the previous PES packet // - we need to output the part of it that is in that previous packet // Given the next byte to be read (from this PES packet) int32_t curposn = es->posn_of_next_byte.inpacket; // we can work out how much of the item was in the previous packet // (sanity check - if the next byte to read was 1, then we've read one // byte from the current packet, and the following should indeed be right // - look at pes.c:read_PES_ES_byte and es.c:next_triple_byte for details) int32_t length_wanted; if (stream.is_h262) { length_wanted = stream.u.h262->last_item->unit.data_len - curposn; if (extra_info) fprint_msg(".. next byte is %d, so length wanted is %d" " - outputting it\n",curposn,length_wanted); err = write_ES_as_TS_PES_packet(output, stream.u.h262->last_item->unit.data, length_wanted, reader->output_video_pid, DEFAULT_VIDEO_STREAM_ID); } else { // @@@ For H.264, do we know, when we get here, that we always // have a pending NAL unit? length_wanted = stream.u.h264->pending_nal->unit.data_len - curposn; if (extra_info) fprint_msg(".. next byte is %d, so length wanted is %d" " - outputting it\n",curposn,length_wanted); err = write_ES_as_TS_PES_packet(output, stream.u.h264->pending_nal->unit.data, length_wanted, reader->output_video_pid, DEFAULT_VIDEO_STREAM_ID); } if (err) { print_err("### Error flushing (start of) last item after fast forward\n"); return 1; } // That leaves us with the whole of this packet still to output, // and we can leave that to the automated mechanism next time it // reads in a new PES packet } else if (item_start.inpacket == 0) { // Said last item started at the start of this PES packet // so there's nothing to flush, and we can leave the automated // mechanism to sort out this packet, as above if (extra_info) print_msg(".. i.e., at start of packet, nothing to do\n"); } else { // Said last item starts part way through this PES packet int32_t start_offset = item_start.inpacket; int32_t length_wanted = reader->packet->es_data_len - start_offset; if (extra_info) { fprint_msg(".. so output %d bytes at end of PES packet\n",length_wanted); print_data(TRUE,".. end bytes",&reader->packet->es_data[start_offset], length_wanted,20); } err = write_ES_as_TS_PES_packet(output, &reader->packet->es_data[start_offset], length_wanted, reader->output_video_pid, DEFAULT_VIDEO_STREAM_ID); if (err) { print_err("### Error flushing rest of PES packet after fast forward\n"); return 1; } // That's all very well, but when the server restarts, and a call is made // to read (the next) PES packet in, it will attempt to write *this* PES // packet out again. So tell it not to do that... reader->dont_write_current_packet = TRUE; } return 0; } /* * Read PES packets and write them out to the target, fast forward. * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int play_stripped(stream_context stream, filter_context fcontext, TS_writer_p output, int verbose, int quiet, int tsdirect, int num_fast, int with_seq_hdrs) { int err; ES_p es = EXTRACT_ES_FROM_STREAM(stream); PES_reader_p reader = es->reader; int total_pictures = 0; // stop_server_output(reader); // And then reset our filter context so that we start filtering without // remembering anything about last time we filtered reset_filter_context(fcontext,0); if (!tsdirect) { // Ensure we've got program data available (probably not necessary, // but unlikely to hurt) err = write_program_data(reader,output); if (err) return err; } if (extra_info) print_msg("Fast forwarding (strip)\n"); for (;;) { picture this_picture; picture seq_hdr; // H.262 only - *we* mustn't free this one int delta_pictures_seen; if (tswrite_command_changed(output)) return COMMAND_RETURN_CODE; err = get_next_stripped(fcontext,verbose,quiet, &seq_hdr,&this_picture,&delta_pictures_seen); if (err == EOF || err == COMMAND_RETURN_CODE) { return err; } else if (err) { print_err("### Error getting next stripped picture\n"); return 1; } if (with_seq_hdrs && !is_null_picture(seq_hdr)) { err = write_picture_as_TS(stream,output,seq_hdr); if (err) { print_err("### Error writing out sequence header\n"); free_picture(&this_picture); return 1; } } err = write_picture_as_TS(stream,output,this_picture); if (err) { print_err("### Error writing out picture list\n"); free_picture(&this_picture); return 1; } free_picture(&this_picture); total_pictures += delta_pictures_seen; if (num_fast > 0 && total_pictures > num_fast) break; } return 0; } /* * Read PES packets and write them out to the target, fast fast forward. * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int play_filtered(stream_context stream, filter_context fcontext, TS_writer_p output, int verbose, int quiet, int tsdirect, int num_faster, int frequency, int with_seq_hdrs) { int err; ES_p es = EXTRACT_ES_FROM_STREAM(stream); PES_reader_p reader = es->reader; picture this_picture; picture last_picture; picture seq_hdr; // H.262 only - *we* mustn't free this one int total_pictures = 0; // stop_server_output(reader); // Reset our filter context so that we start filtering without remembering // anything about last time we filtered reset_filter_context(fcontext,frequency); if (!tsdirect) { // Ensure we've got program data available (probably not necessary, // but unlikely to hurt) err = write_program_data(reader,output); if (err) return err; } if (extra_info) print_msg("Fast forwarding (filter)\n"); unset_picture(stream.is_h262,&this_picture); unset_picture(stream.is_h262,&last_picture); for (;;) { int delta_pictures_seen; if (tswrite_command_changed(output)) { free_picture(&last_picture); err = COMMAND_RETURN_CODE; break; } err = get_next_filtered(fcontext,verbose,quiet, &seq_hdr,&this_picture,&delta_pictures_seen); if (err == EOF || err == COMMAND_RETURN_CODE) { free_picture(&last_picture); break; } else if (err) { print_err("### Error getting next filtered picture\n"); free_picture(&last_picture); return 1; } if (is_null_picture(this_picture)) { // We need to repeat the last picture this_picture = last_picture; unset_picture(stream.is_h262,&last_picture); } if (!is_null_picture(this_picture)) { if (with_seq_hdrs && !is_null_picture(seq_hdr)) { err = write_picture_as_TS(stream,output,seq_hdr); if (err) { print_err("### Error writing out sequence header\n"); free_picture(&this_picture); free_picture(&last_picture); return 1; } } err = write_picture_as_TS(stream,output,this_picture); if (err) { print_err("### Error writing out picture\n"); free_picture(&this_picture); free_picture(&last_picture); return 1; } } free_picture(&last_picture); last_picture = this_picture; total_pictures += delta_pictures_seen; if (num_faster > 0 && total_pictures > num_faster) break; } // We *do* end up here if we run out of for loop... free_picture(&last_picture); if (err == EOF) { // If we reached the end of the file, then back up to the final // picture in the reversing arrays, and output that (so that the // user *sees* that final picture). // // (The last picture in the reversing arrays will be the last I or IDR // frame. We know that we are only outputting I or IDR frames, so we // know that this would also be the last frame we'd have considered // outputting. It's possible we've already output it, but on the whole // that shouldn't be terribly obvious to the user, I think.) reverse_data_p reverse_data = EXTRACT_REVERSE_FROM_STREAM(stream); // Try going back 2 I/IDR pictures... err = output_from_reverse_data_as_TS(es,output,verbose,quiet,2, reverse_data); if (err && err != COMMAND_RETURN_CODE) { print_err("### Error outputting 'last' picture at EOF\n"); return err; } // Which means we need to adjust back to normal playing *this* way err = resync_after_reverse(stream,output,verbose,quiet); if (err) return err; // Let the caller know what we did/where we are return EOF; } else { // Adjust back to normal playing err = resync_after_filter(stream,output,verbose,quiet); if (err) return err; } return 0; } /* * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * - `num_to_skip` is the number of frames to skip * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int skip_forwards(stream_context stream, TS_writer_p output, filter_context fcontext, int with_seq_hdrs, int num_to_skip, int verbose, int quiet, int tsdirect) { int err; ES_p es = EXTRACT_ES_FROM_STREAM(stream); PES_reader_p reader = es->reader; picture this_picture; picture seq_hdr; // H.262 only - *we* mustn't free this one int delta_pictures_seen; #if TIME_SKIPPING time_t start_time,end_time; clock_t start_clock,end_clock; start_time = time(NULL); start_clock = clock(); #endif // Reset our filter context so that we start filtering without remembering // anything about last time we filtered reset_filter_context(fcontext,num_to_skip); if (!tsdirect) { // Ensure we've got program data available (probably not necessary, // but unlikely to hurt) err = write_program_data(reader,output); if (err) return err; } if (extra_info) fprint_msg("Skipping forwards (%d frames)\n",num_to_skip); unset_picture(stream.is_h262,&this_picture); // Say that we don't want our skipping to be interrupted by the next command tswrite_set_command_atomic(output,TRUE); err = get_next_filtered(fcontext,verbose,quiet, &seq_hdr,&this_picture,&delta_pictures_seen); if (err && err != EOF) { tswrite_set_command_atomic(output,FALSE); if (err == COMMAND_RETURN_CODE) return err; else { print_err("### Error skipping pictures\n"); return 1; } } if (err == EOF) { // We hit the end of file before finding anything - so we should make // sure to display the "last" picture (actually, the last I/IDR picture) // Luckily, we can do that by "reversing" to it... reverse_data_p reverse_data = EXTRACT_REVERSE_FROM_STREAM(stream); // Try going back 2 I/IDR pictures... err = output_from_reverse_data_as_TS(es,output,verbose,quiet,2, reverse_data); if (err) { print_err("### Error outputting 'last' picture at EOF\n"); tswrite_set_command_atomic(output,FALSE); return err; } // Which means we need to adjust back to normal playing *this* way err = resync_after_reverse(stream,output,verbose,quiet); if (err) { tswrite_set_command_atomic(output,FALSE); return err; } // Let the caller know what we did/where we are return EOF; } else { // Since we're only skipping once, we shouldn't get a NULL (repeat) // picture back if (is_null_picture(this_picture)) { print_err("### Skipping returned a NULL picture\n"); free_picture(&this_picture); tswrite_set_command_atomic(output,FALSE); return 1; } if (with_seq_hdrs && !is_null_picture(seq_hdr)) { err = write_picture_as_TS(stream,output,seq_hdr); if (err) { print_err("### Error writing out sequence header\n"); free_picture(&this_picture); tswrite_set_command_atomic(output,FALSE); return 1; } } err = write_picture_as_TS(stream,output,this_picture); if (err) { print_err("### Error writing out picture\n"); free_picture(&this_picture); tswrite_set_command_atomic(output,FALSE); return 1; } free_picture(&this_picture); // And remember to adjust back to normal playing err = resync_after_filter(stream,output,verbose,quiet); if (err) { tswrite_set_command_atomic(output,FALSE); return 1; } } #if TIME_SKIPPING end_clock = clock(); end_time = time(NULL); fprint_msg("Started skipping at %s",ctime(&start_time)); fprint_msg("Finished skipping at %s",ctime(&end_time)); fprint_msg("Elapsed time %.3fs\n",difftime(end_time,start_time)); fprint_msg("Process time %.3fs\n", ((double)(end_clock-start_clock)/CLOCKS_PER_SEC)); #endif // Remember to allow future commands to be interrupted tswrite_set_command_atomic(output,FALSE); return 0; } /* * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * - `num_to_skip` is the number of frames to skip * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int skip_backwards(stream_context stream, TS_writer_p output, int num_to_skip, int verbose, int quiet, int tsdirect, reverse_data_p reverse_data) { int err; ES_p es = EXTRACT_ES_FROM_STREAM(stream); PES_reader_p reader = es->reader; if (!tsdirect) { // Ensure we've got program data available (probably not necessary, // but unlikely to hurt) err = write_program_data(reader,output); if (err) return err; } if (extra_info) fprint_msg("Skipping backwards (%d frames)\n",num_to_skip); // Say that we don't want our skipping to be interrupted by the next command tswrite_set_command_atomic(output,TRUE); err = output_in_reverse_as_TS(es,output,num_to_skip,verbose,quiet, -1,num_to_skip,reverse_data); if (err && err != COMMAND_RETURN_CODE) { print_err("### Error skipping backwards\n"); tswrite_set_command_atomic(output,FALSE); return err; } err = resync_after_reverse(stream,output,verbose,quiet); if (err) { tswrite_set_command_atomic(output,FALSE); return err; } // Remember to allow future commands to be interrupted tswrite_set_command_atomic(output,FALSE); return 0; } /* * Write pictures out to the target, in reverse * * Returns 0 if all went well, EOF if the end of file is reached, * otherwise 1 if an error occurred. * * If command input is enabled, then it can also return COMMAND_RETURN_CODE * if the current command has changed. */ static int play_reverse(stream_context stream, TS_writer_p output, int verbose, int quiet, int tsdirect, int frequency, int num_reverse, reverse_data_p reverse_data) { int err; ES_p es = EXTRACT_ES_FROM_STREAM(stream); PES_reader_p reader = es->reader; if (extra_info) print_msg("Reversing\n"); if (tsdirect) { // Ensure we've got program data available (probably not necessary, // but unlikely to hurt) err = write_program_data(reader,output); if (err) return err; } #if SHOW_REVERSE_DATA if (extra_info) { int ii; for (ii=0; iilength; ii++) if (stream.is_h262 && reverse_data->seq_offset[ii] == 0) 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]); else 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]); } #endif err = output_in_reverse_as_TS(es,output,frequency,verbose,quiet, -1,num_reverse,reverse_data); if (err && err != COMMAND_RETURN_CODE) { print_err("### Error outputting reversed data\n"); return err; } // Adjust back to normal playing err = resync_after_reverse(stream,output,verbose,quiet); if (err) return err; return err; } /* * Read PES packets and write them out to the target, obeying user * commands as to what to do. * * Returns 0 if all went well, EOF if the 'q'uit command has been given, * 1 if an error occurred. */ static int obey_command(char this_command, char last_command, int *index, int started[MAX_INPUT_FILES], PES_reader_p reader[MAX_INPUT_FILES], stream_context stream[MAX_INPUT_FILES], filter_context fcontext[MAX_INPUT_FILES], filter_context scontext[MAX_INPUT_FILES], reverse_data_p reverse_data[MAX_INPUT_FILES], TS_writer_p tswriter, int video_only, int verbose, int quiet, int tsdirect, int with_seq_hdrs, int ffrequency, int rfrequency) { int err = 0; int new_stream; int which = *index; // which stream we're reading // Loop obeying our given command and any "imaginary" commands that // result therefrom for (;;) { #ifdef DEBUG_COMMANDS fprint_msg("__ obeying command '%c'\n",this_command); #endif switch (this_command) { case COMMAND_NORMAL: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Forwards, normal speed\n", tswriter->where.socket,which); if (last_command != COMMAND_NORMAL && started[which]) { err = back_to_normal(stream[which],tswriter,tsdirect); if (err) return 1; } started[which] = TRUE; set_PES_reader_video_only(reader[which],video_only); err = play_normal(stream[which],tswriter,verbose,quiet,0, tsdirect, reverse_data[which]); // If we've had a new command, and it's not 'n' again... if (err == COMMAND_RETURN_CODE && tswriter->command != COMMAND_NORMAL) err = flush_after_normal(stream[which],tswriter,verbose,quiet); break; case COMMAND_PAUSE: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Pause\n", tswriter->where.socket,which); stop_server_output(reader[which]); err = wait_for_command(tswriter); break; case COMMAND_FAST: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Fast forwards\n", tswriter->where.socket,which); stop_server_output(reader[which]); set_PES_reader_video_only(reader[which],TRUE); err = play_stripped(stream[which],scontext[which],tswriter, verbose,quiet,tsdirect,0,with_seq_hdrs); break; case COMMAND_FAST_FAST: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Fast fast forwards\n", tswriter->where.socket,which); stop_server_output(reader[which]); set_PES_reader_video_only(reader[which],TRUE); err = play_filtered(stream[which],fcontext[which],tswriter, verbose,quiet,tsdirect,0,ffrequency,with_seq_hdrs); break; case COMMAND_REVERSE: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Reverse\n", tswriter->where.socket,which); stop_server_output(reader[which]); set_PES_reader_video_only(reader[which],TRUE); err = play_reverse(stream[which],tswriter,verbose,quiet, tsdirect, rfrequency,0,reverse_data[which]); if (err == 0) { if (!quiet) fprint_msg("Start of file %d\n",which); this_command = COMMAND_PAUSE; break; } break; case COMMAND_FAST_REVERSE: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Reverse (faster)\n", tswriter->where.socket,which); stop_server_output(reader[which]); set_PES_reader_video_only(reader[which],TRUE); err = play_reverse(stream[which],tswriter,verbose,quiet, tsdirect, 2*rfrequency,0,reverse_data[which]); if (err == 0) { if (!quiet) fprint_msg("Start of file %d\n",which); this_command = COMMAND_PAUSE; break; } break; case COMMAND_SKIP_FORWARD: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Skip forwards 10 seconds\n", tswriter->where.socket,which); stop_server_output(reader[which]); set_PES_reader_video_only(reader[which],TRUE); err = skip_forwards(stream[which],tswriter, fcontext[which],with_seq_hdrs, SMALL_SKIP_DISTANCE,verbose,quiet,tsdirect); this_command = COMMAND_NORMAL; // aim to continue with normal play break; case COMMAND_SKIP_BACKWARD: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Skip backwards 10 seconds\n", tswriter->where.socket,which); stop_server_output(reader[which]); set_PES_reader_video_only(reader[which],TRUE); err = skip_backwards(stream[which],tswriter,SMALL_SKIP_DISTANCE, verbose,quiet,tsdirect,reverse_data[which]); this_command = COMMAND_NORMAL; // aim to continue with normal play break; case COMMAND_SKIP_FORWARD_LOTS: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Skip forwards 3 minutes\n", tswriter->where.socket,which); stop_server_output(reader[which]); set_PES_reader_video_only(reader[which],TRUE); err = skip_forwards(stream[which],tswriter, fcontext[which],with_seq_hdrs, BIG_SKIP_DISTANCE,verbose,quiet,tsdirect); this_command = COMMAND_NORMAL; // aim to continue with normal play break; case COMMAND_SKIP_BACKWARD_LOTS: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Skip backwards 3 minutes\n", tswriter->where.socket,which); stop_server_output(reader[which]); set_PES_reader_video_only(reader[which],TRUE); err = skip_backwards(stream[which],tswriter,BIG_SKIP_DISTANCE, verbose,quiet,tsdirect,reverse_data[which]); this_command = COMMAND_NORMAL; // aim to continue with normal play break; case COMMAND_SELECT_FILE_0: new_stream = 0; goto change_stream; case COMMAND_SELECT_FILE_1: new_stream = 1; goto change_stream; case COMMAND_SELECT_FILE_2: new_stream = 2; goto change_stream; case COMMAND_SELECT_FILE_3: new_stream = 3; goto change_stream; case COMMAND_SELECT_FILE_4: new_stream = 4; goto change_stream; case COMMAND_SELECT_FILE_5: new_stream = 5; goto change_stream; case COMMAND_SELECT_FILE_6: new_stream = 6; goto change_stream; case COMMAND_SELECT_FILE_7: new_stream = 7; goto change_stream; case COMMAND_SELECT_FILE_8: new_stream = 8; goto change_stream; case COMMAND_SELECT_FILE_9: new_stream = 9; goto change_stream; change_stream: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Select file\n", tswriter->where.socket,new_stream); if (reader[new_stream] == NULL) { fprint_msg(".. No input file defined for stream %d - ignored\n", new_stream); } else { #if 0 // The following would only make sense if we *knew* we'd just been doing 'n'ormal play... // Try to ensure we finish at the end of a picture... err = flush_after_normal(stream[which],tswriter,verbose,quiet); if (err && err != COMMAND_RETURN_CODE && err != EOF) return err; #endif // Pause the current stream stop_server_output(reader[which]); // Change to the new stream *index = which = new_stream; // @@@ For the moment, changing channel also means rewinding // the "new" channel. This can become a "pure" channel change // when we have the ability to "go back one(ish) reverse item(s)" // and guarantee to end up at a sensible place to continue from // (an IDR for H.264, or a GOP for H.262) // Rewind it err = rewind_stream(stream[which]); if (err) return 1; // And note that we *are* starting from the beginning again started[which] = FALSE; } // And return to normal playing this_command = COMMAND_NORMAL; break; case COMMAND_QUIT: if (!quiet) fprint_msg("****************************************\n" "** [%3d] File %d: Quitting\n", which,tswriter->where.socket); return EOF; default: fprint_err("!!! Command '%c' ignored\n",this_command); this_command = COMMAND_NORMAL; break; } // Work out what to do next switch (err) { case 0: case COMMAND_RETURN_CODE: break; case EOF: if (!quiet) fprint_msg("End of file %d\n",which); this_command = COMMAND_PAUSE; break; default: fprint_err("!!! Error playing file %d - pausing\n",which); this_command = COMMAND_PAUSE; break; // return 1; } if (tswriter->command_changed) return 0; } } /* * Read PES packets and write them out to the target, obeying user * commands as to what to do. * * Returns 0 if all went well, 1 if an error occurred. */ static int play(int default_index, PES_reader_p reader[MAX_INPUT_FILES], stream_context stream[MAX_INPUT_FILES], filter_context fcontext[MAX_INPUT_FILES], filter_context scontext[MAX_INPUT_FILES], reverse_data_p reverse_data[MAX_INPUT_FILES], TS_writer_p tswriter, int video_only, int verbose, int quiet, int tsdirect, int with_seq_hdrs, int ffrequency, int rfrequency) { int err; int ii; int started[MAX_INPUT_FILES]; int which = default_index; // which stream we're reading // Any function which writes to the output may read a new command character, // but only if tswriter->command_changed is FALSE. Such a function will then // return COMMAND_RETURN_CODE. // When a new command character is read, tswriter->command_changed is set to // TRUE. It is up to us to set it back to FALSE when we have finished // dealing with the new command letter. byte this_command = tswriter->command; byte last_command = COMMAND_NOT_A_COMMAND; for (ii=0; iicommand_changed = FALSE; this_command = tswriter->command; #ifdef DEBUG_COMMANDS fprint_msg("xx Command is '%c', last command '%c'\n", this_command,last_command); #endif err = obey_command(this_command,last_command,&which, started,reader,stream,fcontext,scontext,reverse_data, tswriter,video_only,verbose,quiet,tsdirect,with_seq_hdrs, ffrequency,rfrequency); if (err == EOF) return 0; // The user gave the 'q'uit command else if (err) { print_err("### Error terminated play\n"); return 1; } last_command = this_command; } } /* * Read PES packets and write them out to the target, obeying user * commands as to what to do. * * Returns 0 if all went well, 1 if an error occurred. */ static int play_pes_packets(PES_reader_p reader[MAX_INPUT_FILES], TS_writer_p tswriter, tsserve_context_p context, int verbose, int quiet) { int err; int ii; ES_p es[MAX_INPUT_FILES]; // A view of our PES packets as ES units reverse_data_p reverse_data[MAX_INPUT_FILES]; stream_context stream[MAX_INPUT_FILES]; filter_context fcontext[MAX_INPUT_FILES]; filter_context scontext[MAX_INPUT_FILES]; if (!quiet) print_msg("\nSetting up environment\n"); // Request that packets be written out to the TS writer as a "side effect" of // reading them in. The default is to write PES packets (just for the video // and audio data), but the alternative is to write all TS packets (if the // data *is* TS) for (ii = 0; ii < MAX_INPUT_FILES; ii++) { if (reader[ii] != NULL) { set_server_output(reader[ii],tswriter,!context->tsdirect, context->repeat_program_every); set_server_padding(reader[ii],context->pes_padding); } } for (ii = 0; ii < MAX_INPUT_FILES; ii++) { es[ii] = NULL; reverse_data[ii] = NULL; // Closing uninitialised things is a bit dodgy if we don't indicate // what *type* of unset value is being used. However, in practice // it doesn't matter much, as both the H.262 and H.264 "destroy" // functions for streams and filter contexts sensibly do nothing // with a NULL value - so we might as well just say the same for all... stream[ii].is_h262 = fcontext[ii].is_h262 = scontext[ii].is_h262 = FALSE; stream[ii].u.h262 = NULL; fcontext[ii].u.h262 = scontext[ii].u.h262 = NULL; } // Start off our output with some null packets - this is in case the // reader needs some time to work out its byte alignment before it starts // looking for 0x47 bytes for (ii=0; iipad_start; ii++) { err = write_TS_null_packet(tswriter); if (err) return 1; } // And sort out our stack-of-streams atop each input file for (ii = 0; ii < MAX_INPUT_FILES; ii++) { if (reader[ii] == NULL) continue; if (!quiet) fprint_msg("Setting up stream %d\n",ii); // Wrap our PES stream up as an ES stream // Note that this has the side-effect of reading the first packet // from the file (so that the ES reader can prime its 3-byte buffer). // This means that we will have read in the first PES packet, and // thus (for TS data) potentially quite a few TS packets, which // may also have included PAT/PMT. Luckily, we rely upon our caller // to have aleady set up PES or TS mirroring. err = build_elementary_stream_PES(reader[ii],&es[ii]); if (err) { fprint_err("### Error trying to build ES reader for PES reader %d\n",ii); goto tidy_up; } // Put an access unit or H.262 unit context around that err = build_stream(es[ii],!(reader[ii]->is_h264),ii+1,&stream[ii]); if (err) { fprint_err("### Unable to build input stream %d\n",ii); goto tidy_up; } // Build our reverse memory datastructure err = build_and_attach_reverse(stream[ii],&reverse_data[ii]); if (err) { fprint_err("### Unable to build reverse memory for stream %d\n",ii); goto tidy_up; } // Tell it what PID and stream id to use when outputting reversed data set_reverse_pid(reverse_data[ii],reader[ii]->output_video_pid, DEFAULT_VIDEO_STREAM_ID); if (!context->with_seq_hdrs) reverse_data[ii]->output_sequence_headers = FALSE; // Build our fast forwards filter contexts err = build_filter_context(stream[ii],FALSE,context->ffrequency,&fcontext[ii]); if (err) { fprint_err("### Unable to build filter context for stream %d\n",ii); goto tidy_up; } err = build_filter_context(stream[ii],TRUE,0,&scontext[ii]); if (err) { fprint_err("### Unable to build strip context for stream %d\n",ii); goto tidy_up; } } // And, at last, do what we came for err = play(context->default_file_index,reader,stream,fcontext,scontext,reverse_data, tswriter,context->video_only,verbose,quiet,context->tsdirect, context->with_seq_hdrs,context->ffrequency,context->rfrequency); tidy_up: for (ii = 0; ii < MAX_INPUT_FILES; ii++) { close_elementary_stream(&es[ii]); free_reverse_data(&reverse_data[ii]); close_stream(stream[ii]); free_filter_context(fcontext[ii]); free_filter_context(scontext[ii]); } return err; } /* * Read PES packets and write them out to the target. Alternate normal * speed, fast forward and reverse (in some sequence). * * Returns 0 if all went well, 1 if an error occurred. */ static int test_play(PES_reader_p reader, stream_context stream, filter_context fcontext, filter_context scontext, reverse_data_p reverse_data, TS_writer_p tswriter, int video_only, int verbose, int quiet, int tsdirect, int num_normal, int num_fast, int num_faster, int num_reverse, int ffrequency, int rfrequency, int with_seq_hdrs) { int err = 0; int started = FALSE; int ii; if (num_fast == 0 && num_faster == 0 && num_reverse == 0) { // Special case -- just play through print_msg(">> Just playing at normal speed\n"); set_PES_reader_video_only(reader,video_only); err = play_normal(stream,tswriter,verbose,quiet,tsdirect,0,reverse_data); if (err == EOF) return 0; else return err; } print_msg(">> Going through sequence twice\n"); for (ii=0; ii<2; ii++) { // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Normal speed for %d\n",num_normal); if (started) { err = back_to_normal(stream,tswriter,tsdirect); if (err) return 1; } started = TRUE; set_PES_reader_video_only(reader,video_only); err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data); if (err == EOF) break; else if (err) return 1; err = flush_after_normal(stream,tswriter,verbose,quiet); if (err == EOF) break; else if (err) return 1; stop_server_output(reader); // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Fast forward for %d\n",num_fast); set_PES_reader_video_only(reader,TRUE); err = play_stripped(stream,scontext,tswriter,verbose,quiet,tsdirect, num_fast, with_seq_hdrs); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Normal speed for %d\n",num_normal); err = back_to_normal(stream,tswriter,tsdirect); if (err) return 1; set_PES_reader_video_only(reader,video_only); err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data); if (err == EOF) break; else if (err) return 1; err = flush_after_normal(stream,tswriter,verbose,quiet); if (err == EOF) break; else if (err) return 1; stop_server_output(reader); // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Faster forward for %d\n",num_faster); set_PES_reader_video_only(reader,TRUE); err = play_filtered(stream,fcontext,tswriter,verbose,quiet,tsdirect, num_faster, ffrequency,with_seq_hdrs); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Normal speed for %d\n",num_normal); err = back_to_normal(stream,tswriter,tsdirect); if (err) return 1; set_PES_reader_video_only(reader,video_only); err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data); if (err == EOF) break; else if (err) return 1; err = flush_after_normal(stream,tswriter,verbose,quiet); if (err == EOF) break; else if (err) return 1; stop_server_output(reader); // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Reverse for %d\n",num_reverse); set_PES_reader_video_only(reader,TRUE); err = play_reverse(stream,tswriter,verbose,quiet,rfrequency, tsdirect, num_reverse,reverse_data); if (err == EOF) break; else if (err) return 1; } if (verbose || extra_info) print_msg("\n \n"); if (err == EOF) print_msg("** End of file\n"); else print_msg(">> End of sequences\n"); return 0; } /* * Read PES packets and write them out to the target. Test skipping forwards * and back. * * Returns 0 if all went well, 1 if an error occurred. */ static int test_skip(PES_reader_p reader, stream_context stream, filter_context fcontext, filter_context scontext, reverse_data_p reverse_data, TS_writer_p tswriter, int video_only, int verbose, int quiet, int tsdirect, int with_seq_hdrs) { int err = 0; int num_normal = 100; int started = FALSE; int ii; print_msg(">> Going through sequence once\n"); for (ii=0; ii<1; ii++) { fprint_msg("\n>> Iteration %d\n\n",ii); // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Normal speed for %d\n",num_normal); if (started) { err = back_to_normal(stream,tswriter,tsdirect); if (err) return 1; } started = TRUE; set_PES_reader_video_only(reader,video_only); err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data); if (err == EOF) break; else if (err) return 1; err = flush_after_normal(stream,tswriter,verbose,quiet); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); print_msg("** Skip forwards\n"); stop_server_output(reader); set_PES_reader_video_only(reader,TRUE); err = skip_forwards(stream,tswriter,fcontext,with_seq_hdrs, SMALL_SKIP_DISTANCE,verbose,quiet,tsdirect); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); print_msg("** Skip forwards\n"); stop_server_output(reader); set_PES_reader_video_only(reader,TRUE); err = skip_forwards(stream,tswriter,fcontext,with_seq_hdrs, SMALL_SKIP_DISTANCE,verbose,quiet,tsdirect); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Normal speed for %d\n",num_normal); err = back_to_normal(stream,tswriter,tsdirect); if (err) return 1; set_PES_reader_video_only(reader,video_only); err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data); if (err == EOF) break; else if (err) return 1; err = flush_after_normal(stream,tswriter,verbose,quiet); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); print_msg("** Skip backwards\n"); stop_server_output(reader); set_PES_reader_video_only(reader,TRUE); err = skip_backwards(stream,tswriter,1,verbose,quiet,tsdirect,reverse_data); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); print_msg("** Skip backwards\n"); stop_server_output(reader); set_PES_reader_video_only(reader,TRUE); err = skip_backwards(stream,tswriter,1,verbose,quiet,tsdirect,reverse_data); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Normal speed for %d\n",num_normal); err = back_to_normal(stream,tswriter,tsdirect); if (err) return 1; set_PES_reader_video_only(reader,video_only); err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data); if (err == EOF) break; else if (err) return 1; err = flush_after_normal(stream,tswriter,verbose,quiet); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); print_msg("** Skip forwards\n"); stop_server_output(reader); set_PES_reader_video_only(reader,TRUE); err = skip_forwards(stream,tswriter,fcontext,with_seq_hdrs, SMALL_SKIP_DISTANCE,verbose,quiet,tsdirect); if (err == EOF) break; else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); print_msg("** Skip backwards\n"); stop_server_output(reader); set_PES_reader_video_only(reader,TRUE); err = skip_backwards(stream,tswriter,1,verbose,quiet,tsdirect,reverse_data); if (err == EOF) break; else if (err) return 1; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ num_normal = 100; if (verbose || extra_info) print_msg("\n \n"); fprint_msg("** Normal speed for %d\n",num_normal); err = back_to_normal(stream,tswriter,tsdirect); if (err) return 1; set_PES_reader_video_only(reader,video_only); err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data); if (err == EOF) { print_msg("** End of file\n"); return 0; } else if (err) return 1; err = flush_after_normal(stream,tswriter,verbose,quiet); if (err == EOF) { print_msg("** End of file\n"); return 0; } else if (err) return 1; // ------------------------------------------------------------ if (verbose || extra_info) print_msg("\n \n"); if (err == EOF) print_msg("** End of file\n"); else print_msg(">> End of sequences\n"); return 0; } /* * Read PES packets and write them out to the target. Alternate normal * speed, fast forward and reverse (in some sequence). * * Returns 0 if all went well, 1 if an error occurred. */ static int test_play_pes_packets(PES_reader_p reader, TS_writer_p tswriter, tsserve_context_p context, int pad_start, int video_only, int verbose, int quiet, int tsdirect, int num_normal, int num_fast, int num_faster, int num_reverse, int ffrequency, int rfrequency, int skiptest, int with_seq_hdrs) { int err; int ii; ES_p es; // A view of our PES packets as ES units reverse_data_p reverse_data = NULL; stream_context stream; filter_context fcontext; filter_context scontext; // Start off our output with some null packets - this is in case the // reader needs some time to work out its byte alignment before it starts // looking for 0x47 bytes for (ii=0; iitsdirect, context->repeat_program_every); set_server_padding(reader,context->pes_padding); // Wrap our PES stream up as an ES stream err = build_elementary_stream_PES(reader,&es); if (err) { print_err("### Error trying to build ES reader from PES reader\n"); return 1; } // Build our reverse memory datastructure err = build_reverse_data(&reverse_data,reader->is_h264); if (err) { print_err("### Unable to build reverse memory\n"); close_elementary_stream(&es); return 1; } stream.is_h262 = fcontext.is_h262 = scontext.is_h262 = !(reader->is_h264); if (reader->is_h264) { access_unit_context_p acontext; // Our ES data as access units h264_filter_context_p fcontext4 = NULL; // And a filter over that h264_filter_context_p scontext4 = NULL; // And another err = build_access_unit_context(es,&acontext); if (err) { print_err("### Error trying to build access unit reader from ES reader\n"); close_elementary_stream(&es); free_reverse_data(&reverse_data); return 1; } add_access_unit_reverse_context(acontext,reverse_data); err = build_h264_filter_context(&fcontext4,acontext,ffrequency); if (err) { print_err("### Unable to build filter context\n"); close_elementary_stream(&es); free_reverse_data(&reverse_data); free_access_unit_context(&acontext); return 1; } err = build_h264_filter_context_strip(&scontext4,acontext,TRUE); if (err) { print_err("### Unable to build strip context\n"); close_elementary_stream(&es); free_reverse_data(&reverse_data); free_access_unit_context(&acontext); free_h264_filter_context(&fcontext4); return 1; } stream.u.h264 = acontext; fcontext.u.h264 = fcontext4; scontext.u.h264 = scontext4; if (skiptest) err = test_skip(reader,stream,fcontext,scontext,reverse_data,tswriter, video_only,verbose,quiet,tsdirect,FALSE); else err = test_play(reader,stream,fcontext,scontext,reverse_data,tswriter, video_only,verbose,quiet,tsdirect, num_normal,num_fast,num_faster,num_reverse, ffrequency,rfrequency,FALSE); free_access_unit_context(&acontext); free_h264_filter_context(&fcontext4); free_h264_filter_context(&scontext4); } else { h262_context_p h262; // Our ES data as H.262 items h262_filter_context_p fcontext2 = NULL; // And a filter over that h262_filter_context_p scontext2 = NULL; // And another if (!with_seq_hdrs) reverse_data->output_sequence_headers = FALSE; err = build_h262_context(es,&h262); if (err) { print_err("### Error trying to build H.262 reader from ES reader\n"); close_elementary_stream(&es); free_reverse_data(&reverse_data); return 1; } add_h262_reverse_context(h262,reverse_data); err = build_h262_filter_context(&fcontext2,h262,ffrequency); if (err) { print_err("### Unable to build filter context\n"); close_elementary_stream(&es); free_reverse_data(&reverse_data); free_h262_context(&h262); return 1; } err = build_h262_filter_context_strip(&scontext2,h262,TRUE); if (err) { print_err("### Unable to build strip context\n"); close_elementary_stream(&es); free_reverse_data(&reverse_data); free_h262_context(&h262); free_h262_filter_context(&fcontext2); return 1; } stream.u.h262 = h262; fcontext.u.h262 = fcontext2; scontext.u.h262 = scontext2; if (skiptest) err = test_skip(reader,stream,fcontext,scontext,reverse_data,tswriter, video_only,verbose,quiet,tsdirect,with_seq_hdrs); else err = test_play(reader,stream,fcontext,scontext,reverse_data,tswriter, video_only,verbose,quiet,tsdirect, num_normal,num_fast,num_faster,num_reverse, ffrequency,rfrequency,with_seq_hdrs); free_h262_context(&h262); free_h262_filter_context(&fcontext2); free_h262_filter_context(&scontext2); } close_elementary_stream(&es); free_reverse_data(&reverse_data); return err; } static int open_input_file(tsserve_context_p context, int quiet, int verbose, PES_reader_p *reader) { int err = open_PES_reader(context->input_names[context->default_file_index], !quiet,verbose,reader); if (err) { fprint_err("### Error opening file %s\n", context->input_names[context->default_file_index]); return 1; } if (!quiet) fprint_msg("Opened input file %s (as %s)\n", context->input_names[context->default_file_index], ((*reader)->is_TS?"TS":"PS")); // If it's PS data, check if we're overriding its stream type if (!(*reader)->is_TS && context->force_stream_type && (*reader)->is_h264 == context->want_h262) { if (!quiet) fprint_msg("File appeared to contain %s, forcing %s\n", (*reader)->is_h264?"MPEG-4/AVC (H.264)":"MPEG-2 (H.272)", context->want_h262?"MPEG-2":"MPEG-4/AVC"); set_PES_reader_h264(*reader); } // If it's PS data, pretend to have read in a PAT and PMT if (!(*reader)->is_TS) { set_PES_reader_program_data(*reader,1, context->pmt_pid, context->video_pid, context->audio_pid,context->pcr_pid); set_PES_reader_dolby_stream_type(*reader,context->dolby_is_dvb); } // If we're wanting extra information, also ask to be told about // the reading and writing of underlying PES packets. (*reader)->debug_read_packets = extra_info; return 0; } static int open_input_files(tsserve_context_p context, int quiet, int verbose, PES_reader_p reader[MAX_INPUT_FILES]) { int ii; for (ii = 0; ii < MAX_INPUT_FILES; ii++) { int err; if (context->input_names[ii] == NULL) { reader[ii] = NULL; continue; } if (!quiet) fprint_msg("\nLooking at input file %d, %s\n",ii,context->input_names[ii]); err = open_PES_reader(context->input_names[ii],!quiet,verbose,&reader[ii]); if (err) { fprint_err("!!! Error opening file %d (%s)\n", ii,context->input_names[ii]); // return 1; reader[ii] = NULL; continue; } if (!quiet) fprint_msg("Opened input file %2d, %s, as %s\n",ii,context->input_names[ii], (reader[ii]->is_TS?"TS":"PS")); // If it's PS data, check if we're overriding its stream type // (for the moment, we only allow overriding of *all* files, // which is clumsy, but may be sufficient for our needs) if (!reader[ii]->is_TS && context->force_stream_type && reader[ii]->is_h264 == context->want_h262) { if (!quiet) fprint_msg("File appeared to contain %s, forcing %s\n", reader[ii]->is_h264?"MPEG-4/AVC (H.264)":"MPEG-2 (H.272)", context->want_h262?"MPEG-2":"MPEG-4/AVC"); set_PES_reader_h264(reader[ii]); } // Ensure that different input files get written out as different // programs (with differing PIDs) set_PES_reader_program_data(reader[ii], ii+1, // program number: 1 upwards DEFAULT_VIDEO_PID+ii+20, // PMT DEFAULT_VIDEO_PID+ii, // video DEFAULT_VIDEO_PID+ii+10, // audio DEFAULT_VIDEO_PID+ii); // PCR==video // If we're wanting extra information, also ask to be told about // the reading and writing of underlying PES packets. reader[ii]->debug_read_packets = extra_info; } return 0; } // ============================================================ // Serving multiple clients // ============================================================ // Arguments for passing to the child server process struct server_args { tsserve_context_p context; // Various arguments we might need TS_writer_p tswriter; // Where we're writing to int verbose; int quiet; }; static int tsserve_child_process(struct server_args *args) { int ii, err; int had_err; tsserve_context_p context = args->context; TS_writer_p tswriter = args->tswriter; int verbose = args->verbose; int quiet = args->quiet; PES_reader_p reader[MAX_INPUT_FILES]; if (!quiet) fprint_msg("Establishing connection with client on socket %d\n", tswriter->where.socket); err = tswrite_start_input(tswriter,tswriter->where.socket); if (err) { print_err("### Unable to start command input from client\n"); (void) tswrite_close(tswriter,TRUE); return 1; } err = open_input_files(context,quiet,verbose,reader); if (err) { print_err("### Unable to open input file\n"); (void) tswrite_close(tswriter,TRUE); return 1; } // And play... if (!quiet) fprint_msg("Playing to client via socket %d\n", tswriter->where.socket); err = play_pes_packets(reader,tswriter,context,verbose,quiet); if (err) { print_err("!!! Error playing PES packets to client\n"); (void) tswrite_close(tswriter,TRUE); for (ii=0;iiinput_names[ii]); had_err = TRUE; } } #ifdef _WIN32 // The original ("parent") thread does not know when we have finished, // so it cannot free the resources we are using. Of course, *we* know // we've now finished, so we can... free(args); #endif return (had_err?1:0); } #ifdef _WIN32 // ============================================================ // Windows threading ("fork" alternative) // ============================================================ /* * Wrapper for tsserve_child_process, used to coerce args, etc. */ static void child_thread_fn(void_p varg) { struct server_args *args = (struct server_args *)varg; (void) tsserve_child_process(args); } /* * Start up the child thread, to serve a single client */ static int start_child(tsserve_context_p context, TS_writer_p tswriter, int verbose, int quiet) { HANDLE child_thread; struct server_args *args; args = malloc(sizeof(struct server_args)); if (args == NULL) { print_err("### Unable to allocate memory for child datastructure\n"); return 1; } args->context = context; args->tswriter = tswriter; args->verbose = verbose; args->quiet = quiet; child_thread = (HANDLE) _beginthread(child_thread_fn,0,(void_p)args); if (child_thread == (HANDLE) -1) { fprint_err("Error creating child process: %s\n",strerror(errno)); return 1; } return 0; } #else // _WIN32 // ============================================================ // Unix forking ("thread" alternative) // ============================================================ /* * Start up the child fork, to handle the circular buffering */ static int start_child(tsserve_context_p context, TS_writer_p tswriter, int verbose, int quiet) { pid_t pid; struct server_args args = {context,tswriter,verbose,quiet}; pid = fork(); if (pid == -1) { fprint_err("Error forking: %s\n",strerror(errno)); return 1; } else if (pid == 0) { // Aha - we're the child _exit(tsserve_child_process(&args)); } tswriter->child = pid; return 0; } static void set_child_exit_handler(); /* * Signal handler - catch children and stop them becoming zombies */ static void on_child_exit() { #if 0 print_msg("sighandler: starting\n"); #endif for (;;) { int status; int pid = waitpid(-1, &status, WNOHANG); #if 0 if (pid > 0) fprint_msg("sighandler: finished with child %08x\n",pid); else fprint_msg("sighandler: finished with %d\n",pid); #endif if (pid <= 0) break; } } /* * Setup the "on child exit" signal handler */ static void set_child_exit_handler() { int ret; struct sigaction action; action.sa_handler = on_child_exit; action.sa_flags = SA_NOCLDSTOP; // we only want terminated children, not stopped children #ifdef SA_RESTART action.sa_flags |= SA_RESTART; #endif sigemptyset(&action.sa_mask); // If it goes wrong, there's not much we can do apart from grumble... #if 0 print_msg("sighandler: Setting up signal handler to reap child processes\n"); #endif ret = sigaction(SIGCHLD,&action,0); if (ret < 0) print_err("!!! tsserve: Error starting signal handler to reap child processes\n"); } #endif // _WIN32 /* * Run as a server */ static int run_server(tsserve_context_p context, int listen_port, int verbose, int quiet) { int err; SOCKET server_socket; struct sockaddr_in ipaddr; #ifdef _WIN32 err = winsock_startup(); if (err) return 1; #else set_child_exit_handler(); #endif // Create a socket. server_socket = socket(AF_INET, SOCK_STREAM, 0); if (server_socket == -1) { #ifdef _WIN32 err = WSAGetLastError(); print_err("### Unable to create socket: "); print_winsock_err(err); print_err("\n"); #else // _WIN32 fprint_err("### Unable to create socket: %s\n",strerror(errno)); #endif // _WIN32 return 1; } // Bind it to port `listen_port` on this machine memset(&ipaddr,0,sizeof(ipaddr)); #if !defined(__linux__) && !defined(_WIN32) // On BSD, the length is defined in the datastructure ipaddr.sin_len = sizeof(struct sockaddr_in); #endif ipaddr.sin_family = AF_INET; ipaddr.sin_port = htons(listen_port); ipaddr.sin_addr.s_addr = INADDR_ANY; // any interface err = bind(server_socket,(struct sockaddr*)&ipaddr,sizeof(ipaddr)); if (err == -1) { #ifdef _WIN32 err = WSAGetLastError(); fprint_err("### Unable to bind to port %d: ",listen_port); print_winsock_err(err); print_err("\n"); #else // _WIN32 fprint_err("### Unable to bind to port %d: %s\n", listen_port,strerror(errno)); #endif // _WIN32 return 1; } for (;;) { TS_writer_p tswriter = NULL; if (!quiet) fprint_msg("\nListening for a connection on port %d" " with socket %d\n",listen_port,server_socket); #ifdef _WIN32 // tswrite_close calls winsock_cleanup(), so we need to make sure that // we call an *extra* winsock_startup to match that (and leave the // call made before this loop "in scope") err = winsock_startup(); if (err) { print_err("### Error calling winsock_startup before listening\n"); return 1; } #endif // _WIN32 err = tswrite_wait_for_client(server_socket,quiet,&tswriter); if (err) { fprint_err("### Error listening for client on port %d\n", listen_port); return 1; } if (context->drop_packets) { tswriter->drop_packets = context->drop_packets; tswriter->drop_number = context->drop_number; } err = start_child(context,tswriter,verbose,quiet); if (err) { print_err("### Error spawning child server\n"); return 1; } #if 0 // The following was a temporary fix to stop zombies without a signal handler #ifndef _WIN32 // If we've forked, then we need to free our "copy" of the tswriter err = tswrite_close(tswriter,TRUE); if (err) { print_err("### Error closing socket in parent process\n"); return 1; } #endif #endif } return 0; } /* * Run tests */ static int test_reader(tsserve_context_p context, int output_to_file, char *output_name, int port, int num_normal, int num_fast, int num_faster, int num_reverse, int skiptest, int verbose, int quiet, int tsdirect) { int err; TS_writer_p tswriter = NULL; PES_reader_p reader = NULL; err = tswrite_open((output_to_file?TS_W_FILE:TS_W_TCP), output_name,NULL,port,quiet,&tswriter); if (err) { fprint_err("### Unable to connect to %s\n",output_name); return 1; } if (context->drop_packets) { tswriter->drop_packets = context->drop_packets; tswriter->drop_number = context->drop_number; } err = open_input_file(context,quiet,verbose,&reader); if (err) { print_err("### Unable to open input file\n"); (void) tswrite_close(tswriter,TRUE); return 1; } // And play... err = test_play_pes_packets(reader,tswriter,context, context->pad_start,context->video_only, verbose,quiet,tsdirect, num_normal,num_fast,num_faster,num_reverse, context->ffrequency,context->rfrequency, skiptest,context->with_seq_hdrs); if (err) { print_err("### Error playing PES packets\n"); (void) tswrite_close(tswriter,TRUE); (void) close_PES_reader(&reader); return 1; } err = tswrite_close(tswriter,quiet); if (err) { fprint_err("### Error closing output %s: %s\n",output_name, strerror(errno)); (void) close_PES_reader(&reader); return 1; } err = close_PES_reader(&reader); if (err) { fprint_err("### Error closing input file %s\n", context->input_names[context->default_file_index]); return 1; } return 0; } /* * Run as a player, possibly reading commands via a socket */ static int command_reader(tsserve_context_p context, char *output_name, int port, int use_stdin, int verbose, int quiet) { int err; int ii, had_err; TS_writer_p tswriter = NULL; PES_reader_p reader[MAX_INPUT_FILES]; err = tswrite_open(TS_W_TCP,output_name,NULL,port,quiet,&tswriter); if (err) { fprint_err("### Unable to connect to %s\n",output_name); return 1; } if (context->drop_packets) { tswriter->drop_packets = context->drop_packets; tswriter->drop_number = context->drop_number; } #ifndef _WIN32 // Maybe enable command input from stdin if (use_stdin) { if (!quiet) print_msg("Commands from standard input:\n" " q = quit\n" " n = normal speed\n" " p = pause (the initial state)\n" " f = fast forward\n" " F = fast fast forward\n" " r = reverse\n" " R = fast reverse\n" " > < = skip forwards, back by 10 seconds\n" " ] [ = skip forwards, back by 3 minutes\n" " 0..9 = select file 0 through 9 (if defined),\n" " rewind it and play at normal speed\n" "Use newline to 'send' a command or sequence of commands.\n"); err= tswrite_start_input(tswriter,STDIN_FILENO); if (err) { print_err("### Unable to start command input from stdin\n"); (void) tswrite_close(tswriter,TRUE); return 1; } } else #endif // _WIN32 { err= tswrite_start_input(tswriter,tswriter->where.socket); if (err) { fprint_err("### Unable to start command input from %s\n", output_name); (void) tswrite_close(tswriter,TRUE); return 1; } } err = open_input_files(context,quiet,verbose,reader); if (err) { print_err("### Unable to open input file\n"); (void) tswrite_close(tswriter,TRUE); return 1; } // And play... err = play_pes_packets(reader,tswriter,context,verbose,quiet); if (err) { print_err("### Error playing PES packets\n"); (void) tswrite_close(tswriter,TRUE); for (ii=0;iiinput_names[ii]); had_err = TRUE; } } return (had_err?1:0); } static void print_usage() { print_msg( "Usage:\n" " tsserve \n" " tsserve -port \n" " tsserve [switches] [switches]\n" "\n" ); REPORT_VERSION("tsserve"); print_msg( "\n" " Act as a server which plays the given file (containing Transport\n" " Stream or Program Stream data). The output is always Transport\n" " Stream.\n" "\n" "Input:\n" " An H.222.0 TS or PS file to serve to the client.\n" " This will be treated as file 0 (see below).\n" "\n" " -0 .. -9 \n" " Specify files 0 through 9, selectable with command\n" " characters 0 through 9. The lowest numbered file\n" " will be the default for display.\n" "\n" "General Switches:\n" " -details Print out more detailed help information,\n" " including some less common options.\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 Suppress informational and warning messages.\n" " -verbose, -v Output additional diagnostic messages\n" " -port Listen for a client on port (default 88)\n" " -noaudio Ignore any audio data\n" " -pad Pad the start of the output with filler TS\n" " packets, to allow the client to synchronize with\n" " the datastream. Defaults to 8.\n" "\n" " -noseqhdr Do not output sequence headers for fast forward/reverse\n" " data. Only relevant to H.262 data.\n" "\n" "Program Stream Switches:\n" "\n" " -prepeat Output the program data (PAT/PMT) after every \n" " PS packs. Defaults to 100.\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" " Both of these affect the stream type of the output data.\n" "\n" " If the audio stream being output is Dolby (AC-3), then the stream type\n" " used to output it differs for DVB (European) and ATSC (USA) data. It\n" " may be specified as follows:\n" "\n" " -dolby dvb Use stream type 0x06 (the default)\n" " -dolby atsc Use stream type 0x81\n" "\n" " For information on using the program in other modes, see -details.\n" ); } static void print_detailed_usage() { print_msg( "Usage: tsserve [switches] \n" "\n" " Copyright (c) 2004 SJ Consulting Ltd.\n" "\n" " Reads from a file containing H.222.0 (ISO/IEC 13818-1) Transport\n" " Stream or Program Stream data (converting PS to TS as it goes),\n" " and 'plays' the Transport Stream 'at' a client.\n" "\n" " Assumes a single program in the file, and for PS assumes that the\n" " program stream is well formed - i.e., that it starts with a pack\n" " header. A PS stream that ends after a PES packet, but without an\n" " MPEG_program_end_code will cause a warning message, but will not\n" " be treated as an error.\n" "\n" " In the default mode, the program acts as a server, listening for\n" " clients on port 88 (or the port specified with -port). When a\n" " client connects to the port, the program starts listening for\n" " commands from the client, and acting appropriately. When the\n" " client sends the 'q'uit command, the program disconnects from\n" " the client, and listens for another.\n" "\n" " Alternative modes may be specified with -cmd, -cmdstdin and\n" " -test.\n" "\n" "Input:\n" " An H.222.0 TS or PS file.\n" " If given before any of -0..-9, this will be treated\n" " as a specification of file 0. If given after -0..-9,\n" " it will be treated as an error.\n" "\n" " -0 .. -9 \n" " Specify files 0 through 9, selectable with command\n" " characters 0 through 9. The lowest numbered name\n" " will be selected as the default.\n" "\n" "General Switches:\n" " -details Present this text.\n" " -quiet, -q Only output error messages.\n" " -verbose, -v Output progress messages.\n" "\n" " Normal operation outputs some messages summarising the command line\n" " choices, information about data from the input file, confirmation\n" " when the program is ending, etc.\n" " Quiet operation endeavours only to output error messages.\n" " Verbose operation outputs diagnostic information, not intended for\n" " normal use.\n" "\n" " -x Output *extra* information." "\n" " The extra information output gives more details about what the\n" " server is doing in reaction to the commands given by the client.\n" " It is intended as a diagnostic aid during development.\n" "\n" " -port Listen for a client on port (default 88)\n" " Ignored if -cmd, -cmdstdin or -test is\n" " specified\n" "\n" " -noaudio Don't output audio data\n" "\n" " -pad Pad the start of the output with filler TS\n" " packets, to allow the client to synchronize with\n" " the datastream. Defaults to 8.\n" "\n" " -noseqhdr Do not output sequence headers for fast forward/reverse\n" " data. Only relevant to H.262 data.\n" "\n" "Program Stream Switches:\n" "\n" " The following switches are only applicable if the input data is PS.\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" "\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 switches above.\n" "\n" " If the audio stream being output is Dolby (AC-3), then the stream type\n" " used to output it differs for DVB (European) and ATSC (USA) data. It\n" " may be specified as follows:\n" "\n" " -dolby dvb Use stream type 0x06 (the default)\n" " -dolby atsc Use stream type 0x81\n" "\n" "Transport Stream Switches:\n" "\n" " The following switches are only applicable if the input data is TS.\n" "\n" " -tsdirect In normal play, copy all TS packets to the client,\n" " instead of just sending the PES packets for the video\n" " and audio streams'\n" "\n" " Note that when -tsdirect is specified, PES packets are still inspected\n" " to allow building up the fast forward/reverse indices.\n" " Also, -prepeat, -pes_padding and -drop will have no effect with this switch.\n" "\n" "Other stuff:\n" "\n" " -prepeat Output the program data (PAT/PMT) after every \n" " PES packets, to allow a TS reader to resynchronise\n" " if it starts reading part way through the stream.\n" " PAT/PMT pairs are also output before 'significant'\n" " events (changing speed/direction/etc.).\n" " Defaults to 100.\n" "\n" " -ffreq Frequency for faster forward ('F'). Default is 8.\n" " -rfreq Frequency for reverse (fast reverse is twice\n" " the speed). Default is 8.\n" "\n" " -pes_padding When outputting in 'normal play' mode, input PES packets\n" " are copied to the output. If '-pes_padding' is used, then \n" " dummy PES packets will be added to the output for each input\n" " packet, causing the amount of data output to be roughly +1\n" " times as great. This can be useful for benchmarking the recipient.\n" "\n" " -drop As TS packets are output, for every + packets,\n" " keep and then drop (throw away) .\n" " Applies to all TS packets output, regardless of selected file.\n" " This can be useful when testing other applications.\n" "\n" "Alternate modes\n" "---------------\n" " Command input and testing modes connect directly to a host, and thus\n" " the host to use must be specified.\n" "\n" " -host [:\n" " The host to which to write TS packets, over\n" " TCP/IP. If is not specified, it defaults\n" " to 88.\n" "\n" "Command input:\n" " -cmd Enables command input, from the host.\n" " -cmdstdin Enables command input, from standard input.\n" " This is not supported on Windows.\n" "\n" " In command input mode, the program connects to the host specified\n" " with -host, and takes commands either from the host, or from\n" " standard input.\n" "\n" " Command characters are:\n" " q quit.\n" " n normal play.\n" " p pause (the startup state).\n" " f fast forward (uses 'strip').\n" " F fast fast forward (uses 'filter').\n" " r reverse.\n" " R fast reverse.\n" " > < skip forwards/back by 10 seconds.\n" " ] [ skip forwards/back by 3 minutes.\n" " 0..9 select file 0 through 9 (as defined by switches -0 to\n" " -9, see above), rewind it and play at normal speed.\n" " Any other character is ignored.\n" " Note that if command input is from standard input, a newline must\n" " be typed before command characters are 'seen', and if there are\n" " multiple characters on a line, they will be obeyed in sequence.\n" "\n" "Testing:\n" " -test Test by running a sequence of pictures at the\n" " specified host. The exact sequence used can be\n" " determined with the -f, etc., switches:\n" "\n" " -f Loop outputting pictures at normal speed,\n" " -n then fast forward past pictures, then at\n" " -ff normal speed, then at the higher fast forward\n" " -r speed, then at normal speed again, then\n" " reverse past . Repeat until stopped.\n" "\n" " If '-f 0 -ff 0 -r 0' is specified, then the data will just play at\n" " normal speed, ignoring -n.\n" "\n" " -skiptest Test forwards and backwards skipping.\n" "\n" " -output , -o \n" " If -test is being used then output may be\n" " redirected to a file, instead of a host.\n" ); } int main(int argc, char **argv) { char *output_name = NULL; int had_input_name = FALSE; int had_output_name = FALSE; int output_port = 88; // Useful default port number int quiet = FALSE; int verbose = FALSE; int use_stdin = FALSE; // for command input... int listen_port = 88; enum ACTION action = ACTION_SERVER; int err = 0; int ii; int argno = 1; // Testing specific options int num_normal = 100; int num_fast = 100; int num_faster = 100; int num_reverse = 100; int output_to_file = FALSE; int skiptest = FALSE; struct tsserve_context context; for (ii = 0; ii < MAX_INPUT_FILES; ii++) context.input_names[ii] = NULL; context.video_only = FALSE; context.pad_start = 8; context.ffrequency = DEFAULT_FORWARD_FREQUENCY; context.rfrequency = DEFAULT_REVERSE_FREQUENCY; context.with_seq_hdrs = TRUE; context.pes_padding = 0; context.drop_packets = 0; context.drop_number = 0; // Program Stream specific options context.pmt_pid = 0x66; context.audio_pid = 0x67; context.video_pid = 0x68; context.pcr_pid = context.video_pid; // Use PCRs from the video stream context.repeat_program_every = 100; // Transport Stream specific options context.tsdirect = FALSE; // Write to server as a side effect of PES reading context.force_stream_type = FALSE; context.want_h262 = TRUE; // shouldn't matter context.dolby_is_dvb = TRUE; if (argc < 2) { print_usage(); return 0; } while (argno < argc) { if (argv[argno][0] == '-') { if (!strcmp("--help",argv[argno]) || !strcmp("-h",argv[argno]) || !strcmp("-help",argv[argno])) { print_usage(); return 0; } else if (!strcmp("-err",argv[argno])) { CHECKARG("tsserve",argno); if (!strcmp(argv[argno+1],"stderr")) redirect_output_stderr(); else if (!strcmp(argv[argno+1],"stdout")) redirect_output_stdout(); else { fprint_err("### tsserve: " "Unrecognised option '%s' to -err (not 'stdout' or" " 'stderr')\n",argv[argno+1]); return 1; } argno++; } else if (!strcmp("-details",argv[argno])) { print_detailed_usage(); return 0; } else if (!strcmp("-noseqhdr",argv[argno]) || !strcmp("-noseqhdrs",argv[argno])) { context.with_seq_hdrs = FALSE; } else if (!strcmp("-skiptest",argv[argno])) { action = ACTION_TEST; skiptest = TRUE; } else if (!strcmp("-test",argv[argno])) { action = ACTION_TEST; skiptest = FALSE; } else if (!strcmp("-tsdirect",argv[argno])) { context.tsdirect = TRUE; // Write to server as a side effect of TS reading } else if (!strcmp("-n",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,10, &num_normal); if (err) return 1; argno++; } else if (!strcmp("-f",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,10,&num_fast); if (err) return 1; argno++; } else if (!strcmp("-ff",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,10, &num_faster); if (err) return 1; argno++; } else if (!strcmp("-r",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,10, &num_reverse); if (err) return 1; argno++; } else if (!strcmp("-ffreq",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,0, &context.ffrequency); if (err) return 1; argno++; } else if (!strcmp("-rfreq",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,0, &context.rfrequency); if (err) return 1; argno++; } else if (!strcmp("-pes_padding",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,10, &context.pes_padding); if (err) return 1; argno++; } else if (!strcmp("-drop",argv[argno])) { if (ii+2 >= argc) { print_err("### tsserve: -drop requires two arguments\n"); return 1; } err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,0, &context.drop_packets); if (err) return 1; err = int_value("tsserve",argv[argno],argv[argno+2],TRUE,0, &context.drop_number); if (err) return 1; argno += 2; } else if (!strcmp("-quiet",argv[argno]) || !strcmp("-q",argv[argno])) { quiet = TRUE; verbose = FALSE; } else if (!strcmp("-verbose",argv[argno]) || !strcmp("-v",argv[argno])) { quiet = FALSE; verbose = TRUE; } else if (!strcmp("-x",argv[argno])) { extra_info = TRUE; } else if (!strcmp("-noaudio",argv[argno])) { context.video_only = TRUE; } else if (!strcmp("-avc",argv[argno]) || !strcmp("-h264",argv[argno])) { context.force_stream_type = TRUE; context.want_h262 = FALSE; } else if (!strcmp("-h262",argv[argno])) { context.force_stream_type = TRUE; context.want_h262 = TRUE; } else if (!strcmp("-dolby",argv[argno])) { CHECKARG("tsserve",argno); if (!strcmp("dvb",argv[argno+1])) context.dolby_is_dvb = TRUE; else if (!strcmp("atsc",argv[argno+1])) context.dolby_is_dvb = FALSE; else { print_err("### tsserve: -dolby must be followed by dvb or atsc\n"); return 1; } ii++; } else if (!strcmp("-prepeat",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,10, &context.repeat_program_every); if (err) return 1; argno++; } else if (!strcmp("-pad",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,10, &context.pad_start); if (err) return 1; argno++; } else if (!strcmp("-port",argv[argno])) { CHECKARG("tsserve",argno); err = int_value("tsserve",argv[argno],argv[argno+1],TRUE,0, &listen_port); if (err) return 1; argno++; } else if (!strcmp("-cmd",argv[argno])) { action = ACTION_CMD; } else if (!strcmp("-cmdstdin",argv[argno])) { use_stdin = TRUE; action = ACTION_CMD; } else if (!strcmp("-host",argv[argno])) { CHECKARG("tsserve",argno); err = host_value("tsserve",argv[argno],argv[argno+1], &output_name,&output_port); if (err) return 1; had_output_name = TRUE; // more or less argno++; } else if (!strcmp("-0",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[0] = argv[argno+1]; argno++; } else if (!strcmp("-1",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[1] = argv[argno+1]; argno++; } else if (!strcmp("-2",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[2] = argv[argno+1]; argno++; } else if (!strcmp("-3",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[3] = argv[argno+1]; argno++; } else if (!strcmp("-4",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[4] = argv[argno+1]; argno++; } else if (!strcmp("-5",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[5] = argv[argno+1]; argno++; } else if (!strcmp("-6",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[6] = argv[argno+1]; argno++; } else if (!strcmp("-7",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[7] = argv[argno+1]; argno++; } else if (!strcmp("-8",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[8] = argv[argno+1]; argno++; } else if (!strcmp("-9",argv[argno])) { CHECKARG("tsserve",argno); had_input_name = TRUE; context.input_names[9] = argv[argno+1]; argno++; } else if (!strcmp("-output",argv[argno]) || !strcmp("-o",argv[argno])) { CHECKARG("tsserve",argno); output_to_file = TRUE; had_output_name = TRUE; output_name = argv[argno+1]; argno++; } else { fprint_err("### tsserve: " "Unrecognised command line switch '%s'\n",argv[argno]); return 1; } } else { if (had_input_name) { fprint_err("### tsserve: Unexpected '%s'\n",argv[argno]); return 1; } else { context.input_names[0] = argv[argno]; had_input_name = TRUE; } } argno++; } if (!had_input_name) { print_err("### tsserve: No input file specified\n"); return 1; } if (!had_output_name && action != ACTION_SERVER) { print_err("### tsserve: No output specified\n"); return 1; } if (output_to_file && action != ACTION_TEST) { print_err("### tsserve: Output to a file (-output) is only allowed" " with -test\n"); return 1; } if (!quiet) { print_msg("Input files:\n"); for (ii = 0; ii < MAX_INPUT_FILES; ii++) { if (context.input_names[ii] != NULL) fprint_msg(" %2d: %s\n",ii,context.input_names[ii]); } } for (ii = 0; ii < MAX_INPUT_FILES; ii++) { if (context.input_names[ii] != NULL) { context.default_file_index = ii; if (!quiet) fprint_msg("File %d (%s) selected as default\n", ii,context.input_names[ii]); break; } } if (context.tsdirect && !quiet) print_msg("Serving all TS packets, not just video/audio streams\n"); if (context.drop_packets && !quiet) fprint_msg("DROPPING: Keeping %d TS packet%s, then dropping (throwing away) %d\n", context.drop_packets,(context.drop_packets==1?"":"s"), context.drop_number); switch (action) { case ACTION_SERVER: err = run_server(&context,listen_port,verbose,quiet); if (err) { print_err("### tsserve: Error in server\n"); return 1; } break; case ACTION_TEST: err = test_reader(&context,output_to_file,output_name,output_port, num_normal,num_fast,num_faster,num_reverse,skiptest, verbose,quiet,context.tsdirect); if (err) { fprint_err("### tsserve: Error playing to %s\n",output_name); return 1; } break; case ACTION_CMD: err = command_reader(&context,output_name,output_port, use_stdin,verbose,quiet); if (err) { fprint_err("### tsserve: Error playing to %s\n",output_name); return 1; } break; default: print_err("### No action specified\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: