kopia lustrzana https://github.com/F5OEO/tstools
3985 wiersze
126 KiB
C
3985 wiersze
126 KiB
C
/*
|
||
* 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 <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <errno.h>
|
||
#include <string.h>
|
||
#include <fcntl.h>
|
||
#include <math.h>
|
||
|
||
#ifdef _WIN32
|
||
#include <stddef.h>
|
||
#include <winsock2.h>
|
||
#include <ws2tcpip.h>
|
||
#include <process.h>
|
||
#else // _WIN32
|
||
#include <unistd.h>
|
||
#include <sys/types.h>
|
||
#include <sys/socket.h>
|
||
#include <sys/wait.h> // WNOHANG
|
||
#include <netinet/in.h> // sockaddr_in
|
||
#include <signal.h> // sigaction, etc.
|
||
#endif // _WIN32
|
||
|
||
#include <time.h> // 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 "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 <time.h>
|
||
#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 <n> 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)
|
||
{
|
||
fprintf(stderr,"### Error building H.262 context\n");
|
||
return 1;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
err = build_access_unit_context(es,&(stream->u.h264));
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
printf("sequence header");
|
||
else
|
||
printf("%s picture",H262_PICTURE_CODING_STR(pic.type));
|
||
}
|
||
else
|
||
{
|
||
if (pic.u.h264->primary_start == NULL)
|
||
printf("<null>");
|
||
else
|
||
printf("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) printf("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) printf("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) printf(".. 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) printf(".. 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) printf(".. 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) printf("End of file\n");
|
||
return err;
|
||
}
|
||
else if (err)
|
||
{
|
||
fprintf(stderr,"### 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) printf(".. 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) printf(".. 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) printf(".. 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)
|
||
{
|
||
fprintf(stderr,
|
||
"### 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) printf(".. 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) printf("End of file\n");
|
||
return err;
|
||
}
|
||
else if (err)
|
||
{
|
||
fprintf(stderr,"### Error trying to resynchronise after fast forward\n");
|
||
return 1;
|
||
}
|
||
|
||
if (extra_info)
|
||
{
|
||
printf(".. read next picture: ");
|
||
print_picture(picture);
|
||
printf("\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) printf(".. writing it out\n");
|
||
err = write_picture_as_TS(stream,output,picture);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### 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) printf(".. 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) printf(".. writing it out\n");
|
||
err = write_picture_as_TS(stream,output,picture);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### 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) printf("\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)
|
||
printf(" 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)
|
||
{
|
||
fprintf(stderr,"### 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) printf("\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) printf("\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 ... <next sequence>
|
||
//
|
||
// 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)
|
||
{
|
||
fprintf(stderr,"### 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) printf("\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) printf(".. 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)
|
||
{
|
||
printf(".. 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)
|
||
{
|
||
printf(" 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(stdout," 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)
|
||
{
|
||
printf(" 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(stdout," pending NAL unit",
|
||
stream.u.h264->pending_nal->unit.data,
|
||
stream.u.h264->pending_nal->unit.data_len,20);
|
||
}
|
||
}
|
||
printf(".. i.e., last item starts at " OFFSET_T_FORMAT "/%d\n",
|
||
item_start.infile,item_start.inpacket);
|
||
printf(" PES ES data length is %d\n"
|
||
" difference is %d\n",
|
||
reader->packet->es_data_len,
|
||
reader->packet->es_data_len-item_start.inpacket);
|
||
printf(" 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) printf(".. 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) printf(".. 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)
|
||
{
|
||
fprintf(stderr,
|
||
"### 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) printf(".. 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)
|
||
{
|
||
printf(".. so output %d bytes at end of PES packet\n",length_wanted);
|
||
print_data(stdout,".. 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)
|
||
{
|
||
fprintf(stderr,
|
||
"### 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) printf("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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### Error writing out sequence header\n");
|
||
free_picture(&this_picture);
|
||
return 1;
|
||
}
|
||
}
|
||
err = write_picture_as_TS(stream,output,this_picture);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### 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) printf("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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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) printf("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
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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))
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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);
|
||
printf("Started skipping at %s",ctime(&start_time));
|
||
printf("Finished skipping at %s",ctime(&end_time));
|
||
printf("Elapsed time %.3fs\n",difftime(end_time,start_time));
|
||
printf("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) printf("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)
|
||
{
|
||
fprintf(stderr,"### 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) printf("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; ii<reverse_data->length; ii++)
|
||
if (stream.is_h262 && reverse_data->seq_offset[ii] == 0)
|
||
printf("%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
|
||
printf("%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)
|
||
{
|
||
fprintf(stderr,"### 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
|
||
printf("__ obeying command '%c'\n",this_command);
|
||
#endif
|
||
switch (this_command)
|
||
{
|
||
case COMMAND_NORMAL:
|
||
if (!quiet) printf("****************************************\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) printf("****************************************\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) printf("****************************************\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) printf("****************************************\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) printf("****************************************\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) printf("Start of file %d\n",which);
|
||
this_command = COMMAND_PAUSE;
|
||
break;
|
||
}
|
||
break;
|
||
|
||
case COMMAND_FAST_REVERSE:
|
||
if (!quiet) printf("****************************************\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) printf("Start of file %d\n",which);
|
||
this_command = COMMAND_PAUSE;
|
||
break;
|
||
}
|
||
break;
|
||
|
||
case COMMAND_SKIP_FORWARD:
|
||
if (!quiet) printf("****************************************\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) printf("****************************************\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) printf("****************************************\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) printf("****************************************\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) printf("****************************************\n"
|
||
"** [%3d] File %d: Select file\n",
|
||
tswriter->where.socket,new_stream);
|
||
if (reader[new_stream] == NULL)
|
||
{
|
||
printf(".. 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) printf("****************************************\n"
|
||
"** [%3d] File %d: Quitting\n",
|
||
which,tswriter->where.socket);
|
||
return EOF;
|
||
|
||
default:
|
||
fprintf(stderr,"!!! 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) printf("End of file %d\n",which);
|
||
this_command = COMMAND_PAUSE;
|
||
break;
|
||
default:
|
||
fprintf(stderr,"!!! 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; ii<MAX_INPUT_FILES; ii++)
|
||
started[ii] = FALSE;
|
||
|
||
// Select our current PES reader
|
||
if (reader[which] == NULL)
|
||
{
|
||
fprintf(stderr,"### Default input stream %d has no associated file\n",
|
||
which);
|
||
return 1;
|
||
}
|
||
|
||
if (!quiet)
|
||
printf("Starting with input stream %d\n",which);
|
||
|
||
#if 0 // Shouldn't need to do this, as any command that *does* anything will output it
|
||
// Ensure we output program data before anything else "sensible"
|
||
err = write_program_data(reader[which],tswriter);
|
||
if (err) return err;
|
||
#endif
|
||
|
||
for (;;)
|
||
{
|
||
// It is our job to let the underlying interface know that we are
|
||
// ready to read a new command character (i.e., that we have "heard"
|
||
// the last one). We do that by unsetting the command-changed flag.
|
||
tswriter->command_changed = FALSE;
|
||
|
||
this_command = tswriter->command;
|
||
|
||
#ifdef DEBUG_COMMANDS
|
||
printf("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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
printf("\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; ii<context->pad_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)
|
||
printf("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)
|
||
{
|
||
fprintf(stderr,
|
||
"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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
|
||
printf(">> 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;
|
||
}
|
||
|
||
printf(">> Going through sequence twice\n");
|
||
|
||
for (ii=0; ii<2; ii++)
|
||
{
|
||
// ------------------------------------------------------------
|
||
if (verbose || extra_info) printf("\n\n");
|
||
printf("** 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) printf("\n\n");
|
||
printf("** 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) printf("\n\n");
|
||
printf("** 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) printf("\n\n");
|
||
printf("** 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) printf("\n\n");
|
||
printf("** 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) printf("\n\n");
|
||
printf("** 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) printf("\n\n");
|
||
if (err == EOF)
|
||
printf("** End of file\n");
|
||
else
|
||
printf(">> 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;
|
||
|
||
printf(">> Going through sequence once\n");
|
||
|
||
for (ii=0; ii<1; ii++)
|
||
{
|
||
printf("\n>> Iteration %d\n\n",ii);
|
||
|
||
// ------------------------------------------------------------
|
||
if (verbose || extra_info) printf("\n\n");
|
||
printf("** 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) printf("\n\n");
|
||
printf("** 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) printf("\n\n");
|
||
printf("** 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) printf("\n\n");
|
||
printf("** 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) printf("\n\n");
|
||
printf("** 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) printf("\n\n");
|
||
printf("** 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) printf("\n\n");
|
||
printf("** 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) printf("\n\n");
|
||
printf("** 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) printf("\n\n");
|
||
printf("** 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) printf("\n\n");
|
||
printf("** 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)
|
||
{
|
||
printf("** End of file\n");
|
||
return 0;
|
||
}
|
||
else if (err)
|
||
return 1;
|
||
|
||
err = flush_after_normal(stream,tswriter,verbose,quiet);
|
||
if (err == EOF)
|
||
{
|
||
printf("** End of file\n");
|
||
return 0;
|
||
}
|
||
else if (err)
|
||
return 1;
|
||
|
||
// ------------------------------------------------------------
|
||
if (verbose || extra_info) printf("\n\n");
|
||
if (err == EOF)
|
||
printf("** End of file\n");
|
||
else
|
||
printf(">> 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; ii<pad_start; ii++)
|
||
{
|
||
err = write_TS_null_packet(tswriter);
|
||
if (err) return 1;
|
||
}
|
||
|
||
#if 0
|
||
// Ensure we output program data before anything else "sensible"
|
||
stream.program_number = 1;
|
||
err = write_program_data(reader,tswriter);
|
||
if (err) return err;
|
||
#endif
|
||
|
||
// Request that PES packets be written out to the TS writer as
|
||
// a "side effect" of reading them in
|
||
set_server_output(reader,tswriter,!context->tsdirect,
|
||
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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,
|
||
"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,
|
||
"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### Error opening file %s\n",
|
||
context->input_names[context->default_file_index]);
|
||
return 1;
|
||
}
|
||
|
||
if (!quiet)
|
||
printf("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)
|
||
printf("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)
|
||
printf("\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)
|
||
{
|
||
fprintf(stderr,"!!! Error opening file %d (%s)\n",
|
||
ii,context->input_names[ii]);
|
||
// return 1;
|
||
reader[ii] = NULL;
|
||
continue;
|
||
}
|
||
|
||
if (!quiet)
|
||
printf("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)
|
||
printf("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) printf("Establishing connection with client on socket %d\n",
|
||
tswriter->where.socket);
|
||
|
||
err = tswrite_start_input(tswriter,tswriter->where.socket);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### Unable to open input file\n");
|
||
(void) tswrite_close(tswriter,TRUE);
|
||
return 1;
|
||
}
|
||
|
||
// And play...
|
||
if (!quiet) printf("Playing to client via socket %d\n",
|
||
tswriter->where.socket);
|
||
|
||
err = play_pes_packets(reader,tswriter,context,verbose,quiet);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"!!! Error playing PES packets to client\n");
|
||
(void) tswrite_close(tswriter,TRUE);
|
||
for (ii=0;ii<MAX_INPUT_FILES;ii++)
|
||
(void) close_PES_reader(&reader[ii]);
|
||
return 0; // Treat as normal completion, so we continue
|
||
}
|
||
|
||
if (!quiet) printf("Finished talking to client\n");
|
||
err = tswrite_close(tswriter,quiet);
|
||
if (err)
|
||
{
|
||
for (ii=0;ii<MAX_INPUT_FILES;ii++)
|
||
(void) close_PES_reader(&reader[ii]);
|
||
return 1;
|
||
}
|
||
|
||
had_err = FALSE;
|
||
for (ii=0;ii<MAX_INPUT_FILES;ii++)
|
||
{
|
||
err = close_PES_reader(&reader[ii]);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error closing input file %d, %s\n",ii,
|
||
context->input_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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"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)
|
||
{
|
||
fprintf(stderr,"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
|
||
printf("sighandler: starting\n");
|
||
#endif
|
||
for (;;)
|
||
{
|
||
int status;
|
||
int pid = waitpid(-1, &status, WNOHANG);
|
||
#if 0
|
||
if (pid > 0)
|
||
printf("sighandler: finished with child %08x\n",pid);
|
||
else
|
||
printf("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
|
||
printf("sighandler: Setting up signal handler to reap child processes\n");
|
||
#endif
|
||
ret = sigaction(SIGCHLD,&action,0);
|
||
if (ret < 0) fprintf(stderr,"!!! 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();
|
||
fprintf(stderr,"### Unable to create socket: ");
|
||
print_winsock_err(err);
|
||
fprintf(stderr,"\n");
|
||
#else // _WIN32
|
||
fprintf(stderr,"### 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();
|
||
fprintf(stderr,"### Unable to bind to port %d: ",listen_port);
|
||
print_winsock_err(err);
|
||
fprintf(stderr,"\n");
|
||
#else // _WIN32
|
||
fprintf(stderr,"### Unable to bind to port %d: %s\n",
|
||
listen_port,strerror(errno));
|
||
#endif // _WIN32
|
||
return 1;
|
||
}
|
||
|
||
for (;;)
|
||
{
|
||
TS_writer_p tswriter = NULL;
|
||
|
||
if (!quiet) printf("\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)
|
||
{
|
||
fprintf(stderr,"### Error calling winsock_startup before listening\n");
|
||
return 1;
|
||
}
|
||
#endif // _WIN32
|
||
|
||
err = tswrite_wait_for_client(server_socket,quiet,&tswriter);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### Error playing PES packets\n");
|
||
(void) tswrite_close(tswriter,TRUE);
|
||
(void) close_PES_reader(&reader);
|
||
return 1;
|
||
}
|
||
|
||
err = tswrite_close(tswriter,quiet);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error closing output %s: %s\n",output_name,
|
||
strerror(errno));
|
||
(void) close_PES_reader(&reader);
|
||
return 1;
|
||
}
|
||
err = close_PES_reader(&reader);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
printf("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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### Error playing PES packets\n");
|
||
(void) tswrite_close(tswriter,TRUE);
|
||
for (ii=0;ii<MAX_INPUT_FILES;ii++)
|
||
(void) close_PES_reader(&reader[ii]);
|
||
return 1;
|
||
}
|
||
|
||
err = tswrite_close(tswriter,quiet);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error closing output %s: %s\n",output_name,
|
||
strerror(errno));
|
||
for (ii=0;ii<MAX_INPUT_FILES;ii++)
|
||
(void) close_PES_reader(&reader[ii]);
|
||
return 1;
|
||
}
|
||
had_err = FALSE;
|
||
for (ii=0;ii<MAX_INPUT_FILES;ii++)
|
||
{
|
||
err = close_PES_reader(&reader[ii]);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error closing input file %d, %s\n",ii,
|
||
context->input_names[ii]);
|
||
had_err = TRUE;
|
||
}
|
||
}
|
||
return (had_err?1:0);
|
||
}
|
||
|
||
static void print_usage()
|
||
{
|
||
printf(
|
||
"Usage:\n"
|
||
" tsserve <infile>\n"
|
||
" tsserve <infile> -port <n>\n"
|
||
" tsserve [switches] <infile> [switches]\n"
|
||
"\n"
|
||
);
|
||
REPORT_VERSION("tsserve");
|
||
printf(
|
||
"\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"
|
||
" <infile> 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 <file0> .. -9 <file9>\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"
|
||
" -quiet, -q Suppress informational and warning messages.\n"
|
||
" -verbose, -v Output additional diagnostic messages\n"
|
||
" -port <n> Listen for a client on port <n> (default 88)\n"
|
||
" -noaudio Ignore any audio data\n"
|
||
" -pad <n> Pad the start of the output with <n> 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 <n> Output the program data (PAT/PMT) after every <n>\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()
|
||
{
|
||
printf(
|
||
"Usage: tsserve [switches] <infile>\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"
|
||
" <infile> 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 <file0> .. -9 <file9>\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 <n> Listen for a client on port <n> (default 88)\n"
|
||
" Ignored if -cmd, -cmdstdin or -test is\n"
|
||
" specified\n"
|
||
"\n"
|
||
" -noaudio Don't output audio data\n"
|
||
"\n"
|
||
" -pad <n> Pad the start of the output with <n> 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 <n> Output the program data (PAT/PMT) after every <n>\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 <n> Frequency for faster forward ('F'). Default is 8.\n"
|
||
" -rfreq <n> Frequency for reverse (fast reverse is twice\n"
|
||
" the speed). Default is 8.\n"
|
||
"\n"
|
||
" -pes_padding <n> When outputting in 'normal play' mode, input PES packets\n"
|
||
" are copied to the output. If '-pes_padding' is used, then <n>\n"
|
||
" dummy PES packets will be added to the output for each input\n"
|
||
" packet, causing the amount of data output to be roughly <n>+1\n"
|
||
" times as great. This can be useful for benchmarking the recipient.\n"
|
||
"\n"
|
||
" -drop <k> <d> As TS packets are output, for every <k>+<d> packets,\n"
|
||
" keep <k> and then drop (throw away) <d>.\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 <host>[:<port>\n"
|
||
" The host to which to write TS packets, over\n"
|
||
" TCP/IP. If <port> 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 <nf> Loop outputting <nn> pictures at normal speed,\n"
|
||
" -n <nn> then fast forward past <nf> pictures, then <nn> at\n"
|
||
" -ff <nF> normal speed, then <nF> at the higher fast forward\n"
|
||
" -r <nr> speed, then <nn> at normal speed again, then\n"
|
||
" reverse past <nr>. 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 <name>, -o <name>\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("-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)
|
||
{
|
||
fprintf(stderr,"### 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
|
||
{
|
||
fprintf(stderr,"### 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
|
||
{
|
||
fprintf(stderr,"### tsserve: "
|
||
"Unrecognised command line switch '%s'\n",argv[argno]);
|
||
return 1;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (had_input_name)
|
||
{
|
||
fprintf(stderr,"### tsserve: Unexpected '%s'\n",argv[argno]);
|
||
return 1;
|
||
}
|
||
else
|
||
{
|
||
context.input_names[0] = argv[argno];
|
||
had_input_name = TRUE;
|
||
}
|
||
}
|
||
argno++;
|
||
}
|
||
|
||
if (!had_input_name)
|
||
{
|
||
fprintf(stderr,"### tsserve: No input file specified\n");
|
||
return 1;
|
||
}
|
||
if (!had_output_name && action != ACTION_SERVER)
|
||
{
|
||
fprintf(stderr,"### tsserve: No output specified\n");
|
||
return 1;
|
||
}
|
||
if (output_to_file && action != ACTION_TEST)
|
||
{
|
||
fprintf(stderr,"### tsserve: Output to a file (-output) is only allowed"
|
||
" with -test\n");
|
||
return 1;
|
||
}
|
||
|
||
if (!quiet)
|
||
{
|
||
printf("Input files:\n");
|
||
for (ii = 0; ii < MAX_INPUT_FILES; ii++)
|
||
{
|
||
if (context.input_names[ii] != NULL)
|
||
printf(" %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) printf("File %d (%s) selected as default\n",
|
||
ii,context.input_names[ii]);
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (context.tsdirect && !quiet)
|
||
printf("Serving all TS packets, not just video/audio streams\n");
|
||
|
||
if (context.drop_packets && !quiet)
|
||
printf("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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### 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)
|
||
{
|
||
fprintf(stderr,"### tsserve: Error playing to %s\n",output_name);
|
||
return 1;
|
||
}
|
||
break;
|
||
|
||
default:
|
||
fprintf(stderr,"### 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:
|