2008-04-14 04:09:29 +00:00
|
|
|
|
/*
|
|
|
|
|
* 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"
|
2009-05-04 19:06:22 +00:00
|
|
|
|
#include "printing_fns.h"
|
2008-04-14 04:09:29 +00:00
|
|
|
|
#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
|
2008-10-18 15:04:34 +00:00
|
|
|
|
uint32_t pmt_pid;
|
|
|
|
|
uint32_t audio_pid;
|
|
|
|
|
uint32_t video_pid;
|
|
|
|
|
uint32_t pcr_pid;
|
2008-04-14 04:09:29 +00:00
|
|
|
|
int want_h262;
|
|
|
|
|
int dolby_is_dvb;
|
|
|
|
|
int force_stream_type;
|
|
|
|
|
int repeat_program_every;
|
2008-06-25 21:24:17 +00:00
|
|
|
|
|
|
|
|
|
// Transport Stream specific options
|
|
|
|
|
int tsdirect;
|
2008-04-14 04:09:29 +00:00
|
|
|
|
};
|
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error building H.262 context\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
err = build_access_unit_context(es,&(stream->u.h264));
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error building H.264 access unit context\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Unable to build reverse memory\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg("sequence header");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
else
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg("%s picture",H262_PICTURE_CODING_STR(pic.type));
|
2008-04-14 04:09:29 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (pic.u.h264->primary_start == NULL)
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg("<null>");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
else
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg("idc %d/type %d (%s)",
|
|
|
|
|
pic.u.h264->primary_start->nal_ref_idc,
|
|
|
|
|
pic.u.h264->primary_start->nal_unit_type,
|
|
|
|
|
NAL_UNIT_TYPE_STR(pic.u.h264->primary_start->nal_unit_type));
|
2008-04-14 04:09:29 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
int tsdirect,
|
2008-04-14 04:09:29 +00:00
|
|
|
|
reverse_data_p reverse_data)
|
|
|
|
|
{
|
|
|
|
|
int err;
|
|
|
|
|
ES_p es = EXTRACT_ES_FROM_STREAM(stream);
|
|
|
|
|
PES_reader_p reader = es->reader;
|
|
|
|
|
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg("Playing at normal speed\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
2008-08-11 22:30:47 +00:00
|
|
|
|
/* 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;
|
|
|
|
|
}
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg("Flushing PES data after normal play\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg(".. no H.262 last item\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) fprint_msg(".. last item starts at " OFFSET_T_FORMAT "/%d\n",
|
|
|
|
|
item_start.infile,item_start.inpacket);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
// 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;
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg(".. which is in the previous packet - "
|
|
|
|
|
"reading spanning picture into next packet\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
err = get_next_picture(stream,verbose,quiet,&picture);
|
|
|
|
|
if (err == EOF)
|
|
|
|
|
{
|
|
|
|
|
// Clearly there is no next picture
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg("End of file\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
else if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error trying to read into next packet whilst"
|
|
|
|
|
" flushing after normal play\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) fprint_msg(".. new last item starts at " OFFSET_T_FORMAT
|
|
|
|
|
"/%d\n",item_start.infile,item_start.inpacket);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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.
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg(".. so there's no need to output any of this"
|
|
|
|
|
" packet\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We need to output whatever came before the terminating item
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) fprint_msg(".. so need to output %d bytes of this"
|
|
|
|
|
" packet\n",item_start.inpacket);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error flushing start of PES packet after normal play\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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;
|
|
|
|
|
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg(".. outputting next reference picture\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
|
{
|
|
|
|
|
err = get_next_picture(stream,verbose,quiet,&picture);
|
|
|
|
|
if (err == EOF)
|
|
|
|
|
{
|
|
|
|
|
// Clearly there is no next picture - so we can't output it
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg("End of file\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
else if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error trying to resynchronise after fast forward\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (extra_info)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg(".. read next picture: ");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
print_picture(picture);
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg("\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg(".. writing it out\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
err = write_picture_as_TS(stream,output,picture);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error writing out picture list\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
free_picture(&picture);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (( I_only && is_I_or_IDR_picture(picture)) ||
|
|
|
|
|
(!I_only && is_reference_picture(picture)))
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg(".. picture acceptable\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
free_picture(&picture);
|
|
|
|
|
}
|
|
|
|
|
// So we've got something sensible to continue with
|
|
|
|
|
// - don't forget to write it out!
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg(".. writing it out\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
err = write_picture_as_TS(stream,output,picture);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error writing out picture list\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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);
|
|
|
|
|
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg("\nResynchronising PES packets after reverse\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
// 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)
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg(" triple byte = %02x,%02x,%02x, next byte to be from "
|
|
|
|
|
OFFSET_T_FORMAT "/%d\n",
|
|
|
|
|
es->prev2_byte,es->prev1_byte,es->cur_byte,
|
|
|
|
|
es->posn_of_next_byte.infile,
|
|
|
|
|
es->posn_of_next_byte.inpacket);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
// @@@ 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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error outputting next reference picture,"
|
|
|
|
|
" after reversing\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg("\nRewinding\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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;
|
|
|
|
|
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg("\nResynchronising after fast fast forward\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error outputting next reference picture,"
|
|
|
|
|
" after fast forwarding\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
TS_writer_p output,
|
|
|
|
|
int tsdirect)
|
2008-04-14 04:09:29 +00:00
|
|
|
|
{
|
|
|
|
|
int err;
|
|
|
|
|
ES_p es = EXTRACT_ES_FROM_STREAM(stream);
|
|
|
|
|
PES_reader_p reader = es->reader;
|
|
|
|
|
ES_offset item_start;
|
|
|
|
|
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg("\nResynchronising PES packets for normal play\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2008-08-11 22:30:47 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg(".. no H.262 last item, presumably been"
|
|
|
|
|
" reversing\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg(".. posn_of_next_byte is " OFFSET_T_FORMAT "/%d\n",
|
|
|
|
|
es->posn_of_next_byte.infile,es->posn_of_next_byte.inpacket);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
if (stream.is_h262)
|
|
|
|
|
{
|
|
|
|
|
if (stream.u.h262->last_item)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg(" last item starts at " OFFSET_T_FORMAT "/%d,\n",
|
|
|
|
|
stream.u.h262->last_item->unit.start_posn.infile,
|
|
|
|
|
stream.u.h262->last_item->unit.start_posn.inpacket);
|
2009-05-03 15:57:22 +00:00
|
|
|
|
print_data(TRUE," last item",
|
2008-04-14 04:09:29 +00:00
|
|
|
|
stream.u.h262->last_item->unit.data,
|
|
|
|
|
stream.u.h262->last_item->unit.data_len,20);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (stream.u.h264->pending_nal)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg(" last item starts at " OFFSET_T_FORMAT "/%d,\n",
|
|
|
|
|
stream.u.h264->pending_nal->unit.start_posn.infile,
|
|
|
|
|
stream.u.h264->pending_nal->unit.start_posn.inpacket);
|
2009-05-03 15:57:22 +00:00
|
|
|
|
print_data(TRUE," pending NAL unit",
|
2008-04-14 04:09:29 +00:00
|
|
|
|
stream.u.h264->pending_nal->unit.data,
|
|
|
|
|
stream.u.h264->pending_nal->unit.data_len,20);
|
|
|
|
|
}
|
|
|
|
|
}
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg(".. i.e., last item starts at " OFFSET_T_FORMAT "/%d\n",
|
|
|
|
|
item_start.infile,item_start.inpacket);
|
|
|
|
|
fprint_msg(" PES ES data length is %d\n"
|
|
|
|
|
" difference is %d\n",
|
|
|
|
|
reader->packet->es_data_len,
|
|
|
|
|
reader->packet->es_data_len-item_start.inpacket);
|
|
|
|
|
fprint_msg(" reader->packet->posn is " OFFSET_T_FORMAT "\n",
|
|
|
|
|
reader->packet->posn);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
2008-10-18 15:04:34 +00:00
|
|
|
|
int32_t curposn = es->posn_of_next_byte.inpacket;
|
2008-04-14 04:09:29 +00:00
|
|
|
|
// 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)
|
2008-10-18 15:04:34 +00:00
|
|
|
|
int32_t length_wanted;
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
if (stream.is_h262)
|
|
|
|
|
{
|
|
|
|
|
length_wanted = stream.u.h262->last_item->unit.data_len - curposn;
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) fprint_msg(".. next byte is %d, so length wanted is %d"
|
|
|
|
|
" - outputting it\n",curposn,length_wanted);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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;
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) fprint_msg(".. next byte is %d, so length wanted is %d"
|
|
|
|
|
" - outputting it\n",curposn,length_wanted);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error flushing (start of) last item after fast forward\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg(".. i.e., at start of packet, nothing to do\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Said last item starts part way through this PES packet
|
2008-10-18 15:04:34 +00:00
|
|
|
|
int32_t start_offset = item_start.inpacket;
|
|
|
|
|
int32_t length_wanted = reader->packet->es_data_len - start_offset;
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (extra_info)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg(".. so output %d bytes at end of PES packet\n",length_wanted);
|
2009-05-03 15:57:22 +00:00
|
|
|
|
print_data(TRUE,".. end bytes",&reader->packet->es_data[start_offset],
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error flushing rest of PES packet after fast forward\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
int tsdirect,
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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);
|
|
|
|
|
|
2008-08-11 22:30:47 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg("Fast forwarding (strip)\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error getting next stripped picture\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
if (with_seq_hdrs && !is_null_picture(seq_hdr))
|
|
|
|
|
{
|
|
|
|
|
err = write_picture_as_TS(stream,output,seq_hdr);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error writing out sequence header\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
free_picture(&this_picture);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
err = write_picture_as_TS(stream,output,this_picture);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error writing out picture list\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
int tsdirect,
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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);
|
|
|
|
|
|
2008-08-11 22:30:47 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg("Fast forwarding (filter)\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error getting next filtered picture\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error writing out sequence header\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
free_picture(&this_picture);
|
|
|
|
|
free_picture(&last_picture);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
err = write_picture_as_TS(stream,output,this_picture);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error writing out picture\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error outputting 'last' picture at EOF\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
int quiet,
|
|
|
|
|
int tsdirect)
|
2008-04-14 04:09:29 +00:00
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
|
2008-08-11 22:30:47 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) fprint_msg("Skipping forwards (%d frames)\n",num_to_skip);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error skipping pictures\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error outputting 'last' picture at EOF\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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))
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Skipping returned a NULL picture\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error writing out sequence header\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
free_picture(&this_picture);
|
|
|
|
|
tswrite_set_command_atomic(output,FALSE);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
err = write_picture_as_TS(stream,output,this_picture);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error writing out picture\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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);
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg("Started skipping at %s",ctime(&start_time));
|
|
|
|
|
fprint_msg("Finished skipping at %s",ctime(&end_time));
|
|
|
|
|
fprint_msg("Elapsed time %.3fs\n",difftime(end_time,start_time));
|
|
|
|
|
fprint_msg("Process time %.3fs\n",
|
|
|
|
|
((double)(end_clock-start_clock)/CLOCKS_PER_SEC));
|
2008-04-14 04:09:29 +00:00
|
|
|
|
#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,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
int tsdirect,
|
2008-04-14 04:09:29 +00:00
|
|
|
|
reverse_data_p reverse_data)
|
|
|
|
|
{
|
|
|
|
|
int err;
|
|
|
|
|
ES_p es = EXTRACT_ES_FROM_STREAM(stream);
|
|
|
|
|
PES_reader_p reader = es->reader;
|
|
|
|
|
|
2008-08-11 22:30:47 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) fprint_msg("Skipping backwards (%d frames)\n",num_to_skip);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error skipping backwards\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
int tsdirect,
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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;
|
|
|
|
|
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (extra_info) print_msg("Reversing\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
2008-08-11 22:30:47 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
#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)
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg("%3d: seqh at " OFFSET_T_FORMAT "/%d for %d\n",
|
|
|
|
|
ii,
|
|
|
|
|
reverse_data->start_file[ii],
|
|
|
|
|
reverse_data->start_pkt[ii],
|
|
|
|
|
reverse_data->data_len[ii]);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
else
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg("%3d: %4d at " OFFSET_T_FORMAT "/%d for %d\n",
|
|
|
|
|
ii,reverse_data->index[ii],
|
|
|
|
|
reverse_data->start_file[ii],
|
|
|
|
|
reverse_data->start_pkt[ii],
|
|
|
|
|
reverse_data->data_len[ii]);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
err = output_in_reverse_as_TS(es,output,frequency,verbose,quiet,
|
|
|
|
|
-1,num_reverse,reverse_data);
|
|
|
|
|
if (err && err != COMMAND_RETURN_CODE)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error outputting reversed data\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
int tsdirect,
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg("__ obeying command '%c'\n",this_command);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
#endif
|
|
|
|
|
switch (this_command)
|
|
|
|
|
{
|
|
|
|
|
case COMMAND_NORMAL:
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) fprint_msg("****************************************\n"
|
|
|
|
|
"** [%3d] File %d: Forwards, normal speed\n",
|
|
|
|
|
tswriter->where.socket,which);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (last_command != COMMAND_NORMAL && started[which])
|
|
|
|
|
{
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = back_to_normal(stream[which],tswriter,tsdirect);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
tsdirect, reverse_data[which]);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
// 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:
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) fprint_msg("****************************************\n"
|
|
|
|
|
"** [%3d] File %d: Pause\n",
|
|
|
|
|
tswriter->where.socket,which);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
stop_server_output(reader[which]);
|
|
|
|
|
err = wait_for_command(tswriter);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case COMMAND_FAST:
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) fprint_msg("****************************************\n"
|
|
|
|
|
"** [%3d] File %d: Fast forwards\n",
|
|
|
|
|
tswriter->where.socket,which);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
stop_server_output(reader[which]);
|
|
|
|
|
set_PES_reader_video_only(reader[which],TRUE);
|
|
|
|
|
err = play_stripped(stream[which],scontext[which],tswriter,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
verbose,quiet,tsdirect,0,with_seq_hdrs);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case COMMAND_FAST_FAST:
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) fprint_msg("****************************************\n"
|
|
|
|
|
"** [%3d] File %d: Fast fast forwards\n",
|
|
|
|
|
tswriter->where.socket,which);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
stop_server_output(reader[which]);
|
|
|
|
|
set_PES_reader_video_only(reader[which],TRUE);
|
|
|
|
|
err = play_filtered(stream[which],fcontext[which],tswriter,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
verbose,quiet,tsdirect,0,ffrequency,with_seq_hdrs);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case COMMAND_REVERSE:
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) fprint_msg("****************************************\n"
|
|
|
|
|
"** [%3d] File %d: Reverse\n",
|
|
|
|
|
tswriter->where.socket,which);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
stop_server_output(reader[which]);
|
|
|
|
|
set_PES_reader_video_only(reader[which],TRUE);
|
|
|
|
|
err = play_reverse(stream[which],tswriter,verbose,quiet,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
tsdirect,
|
2008-04-14 04:09:29 +00:00
|
|
|
|
rfrequency,0,reverse_data[which]);
|
|
|
|
|
if (err == 0)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) fprint_msg("Start of file %d\n",which);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
this_command = COMMAND_PAUSE;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case COMMAND_FAST_REVERSE:
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) fprint_msg("****************************************\n"
|
|
|
|
|
"** [%3d] File %d: Reverse (faster)\n",
|
|
|
|
|
tswriter->where.socket,which);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
stop_server_output(reader[which]);
|
|
|
|
|
set_PES_reader_video_only(reader[which],TRUE);
|
|
|
|
|
err = play_reverse(stream[which],tswriter,verbose,quiet,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
tsdirect,
|
2008-04-14 04:09:29 +00:00
|
|
|
|
2*rfrequency,0,reverse_data[which]);
|
|
|
|
|
if (err == 0)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) fprint_msg("Start of file %d\n",which);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
this_command = COMMAND_PAUSE;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case COMMAND_SKIP_FORWARD:
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) fprint_msg("****************************************\n"
|
|
|
|
|
"** [%3d] File %d: Skip forwards 10 seconds\n",
|
|
|
|
|
tswriter->where.socket,which);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
stop_server_output(reader[which]);
|
|
|
|
|
set_PES_reader_video_only(reader[which],TRUE);
|
|
|
|
|
err = skip_forwards(stream[which],tswriter,
|
|
|
|
|
fcontext[which],with_seq_hdrs,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
SMALL_SKIP_DISTANCE,verbose,quiet,tsdirect);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
this_command = COMMAND_NORMAL; // aim to continue with normal play
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case COMMAND_SKIP_BACKWARD:
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) fprint_msg("****************************************\n"
|
|
|
|
|
"** [%3d] File %d: Skip backwards 10 seconds\n",
|
|
|
|
|
tswriter->where.socket,which);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
stop_server_output(reader[which]);
|
|
|
|
|
set_PES_reader_video_only(reader[which],TRUE);
|
|
|
|
|
err = skip_backwards(stream[which],tswriter,SMALL_SKIP_DISTANCE,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
verbose,quiet,tsdirect,reverse_data[which]);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
this_command = COMMAND_NORMAL; // aim to continue with normal play
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case COMMAND_SKIP_FORWARD_LOTS:
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) fprint_msg("****************************************\n"
|
|
|
|
|
"** [%3d] File %d: Skip forwards 3 minutes\n",
|
|
|
|
|
tswriter->where.socket,which);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
stop_server_output(reader[which]);
|
|
|
|
|
set_PES_reader_video_only(reader[which],TRUE);
|
|
|
|
|
err = skip_forwards(stream[which],tswriter,
|
|
|
|
|
fcontext[which],with_seq_hdrs,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
BIG_SKIP_DISTANCE,verbose,quiet,tsdirect);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
this_command = COMMAND_NORMAL; // aim to continue with normal play
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case COMMAND_SKIP_BACKWARD_LOTS:
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) fprint_msg("****************************************\n"
|
|
|
|
|
"** [%3d] File %d: Skip backwards 3 minutes\n",
|
|
|
|
|
tswriter->where.socket,which);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
stop_server_output(reader[which]);
|
|
|
|
|
set_PES_reader_video_only(reader[which],TRUE);
|
|
|
|
|
err = skip_backwards(stream[which],tswriter,BIG_SKIP_DISTANCE,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
verbose,quiet,tsdirect,reverse_data[which]);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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:
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) fprint_msg("****************************************\n"
|
|
|
|
|
"** [%3d] File %d: Select file\n",
|
|
|
|
|
tswriter->where.socket,new_stream);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (reader[new_stream] == NULL)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg(".. No input file defined for stream %d - ignored\n",
|
|
|
|
|
new_stream);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
}
|
|
|
|
|
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:
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) fprint_msg("****************************************\n"
|
|
|
|
|
"** [%3d] File %d: Quitting\n",
|
|
|
|
|
which,tswriter->where.socket);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return EOF;
|
|
|
|
|
|
|
|
|
|
default:
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("!!! Command '%c' ignored\n",this_command);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
this_command = COMMAND_NORMAL;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Work out what to do next
|
|
|
|
|
switch (err)
|
|
|
|
|
{
|
|
|
|
|
case 0:
|
|
|
|
|
case COMMAND_RETURN_CODE:
|
|
|
|
|
break;
|
|
|
|
|
case EOF:
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) fprint_msg("End of file %d\n",which);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
this_command = COMMAND_PAUSE;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("!!! Error playing file %d - pausing\n",which);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
int tsdirect,
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### Default input stream %d has no associated file\n",
|
|
|
|
|
which);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!quiet)
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg("Starting with input stream %d\n",which);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
#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
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg("xx Command is '%c', last command '%c'\n",
|
|
|
|
|
this_command,last_command);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
err = obey_command(this_command,last_command,&which,
|
|
|
|
|
started,reader,stream,fcontext,scontext,reverse_data,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
tswriter,video_only,verbose,quiet,tsdirect,with_seq_hdrs,
|
2008-04-14 04:09:29 +00:00
|
|
|
|
ffrequency,rfrequency);
|
|
|
|
|
if (err == EOF)
|
|
|
|
|
return 0; // The user gave the 'q'uit command
|
|
|
|
|
else if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error terminated play\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg("\nSetting up environment\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
2008-09-18 15:13:57 +00:00
|
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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...
|
2008-09-18 15:13:57 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
2008-09-18 15:13:57 +00:00
|
|
|
|
// 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;
|
2008-04-14 04:09:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2008-09-18 15:13:57 +00:00
|
|
|
|
// And sort out our stack-of-streams atop each input file
|
2008-04-14 04:09:29 +00:00
|
|
|
|
for (ii = 0; ii < MAX_INPUT_FILES; ii++)
|
|
|
|
|
{
|
|
|
|
|
if (reader[ii] == NULL)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (!quiet)
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg("Setting up stream %d\n",ii);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
2008-08-11 22:30:47 +00:00
|
|
|
|
|
2008-04-14 04:09:29 +00:00
|
|
|
|
// Wrap our PES stream up as an ES stream
|
2008-09-18 15:13:57 +00:00
|
|
|
|
// 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.
|
2008-04-14 04:09:29 +00:00
|
|
|
|
err = build_elementary_stream_PES(reader[ii],&es[ii]);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### Error trying to build ES reader for PES reader %d\n",ii);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### Unable to build input stream %d\n",ii);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
goto tidy_up;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build our reverse memory datastructure
|
|
|
|
|
err = build_and_attach_reverse(stream[ii],&reverse_data[ii]);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### Unable to build reverse memory for stream %d\n",ii);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
goto tidy_up;
|
|
|
|
|
}
|
|
|
|
|
|
2008-08-11 22:30:47 +00:00
|
|
|
|
|
2008-04-14 04:09:29 +00:00
|
|
|
|
// 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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### Unable to build filter context for stream %d\n",ii);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
goto tidy_up;
|
|
|
|
|
}
|
2008-08-11 22:30:47 +00:00
|
|
|
|
|
|
|
|
|
|
2008-04-14 04:09:29 +00:00
|
|
|
|
err = build_filter_context(stream[ii],TRUE,0,&scontext[ii]);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### Unable to build strip context for stream %d\n",ii);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
goto tidy_up;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// And, at last, do what we came for
|
|
|
|
|
err = play(context->default_file_index,reader,stream,fcontext,scontext,reverse_data,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
tswriter,context->video_only,verbose,quiet,context->tsdirect,
|
|
|
|
|
context->with_seq_hdrs,context->ffrequency,context->rfrequency);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
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,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
int tsdirect,
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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;
|
|
|
|
|
|
2008-06-25 21:24:17 +00:00
|
|
|
|
if (num_fast == 0 && num_faster == 0 && num_reverse == 0)
|
|
|
|
|
{
|
|
|
|
|
// Special case -- just play through
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg(">> Just playing at normal speed\n");
|
2008-06-25 21:24:17 +00:00
|
|
|
|
set_PES_reader_video_only(reader,video_only);
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = play_normal(stream,tswriter,verbose,quiet,tsdirect,0,reverse_data);
|
2008-06-25 21:24:17 +00:00
|
|
|
|
if (err == EOF)
|
|
|
|
|
return 0;
|
|
|
|
|
else
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg(">> Going through sequence twice\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
for (ii=0; ii<2; ii++)
|
|
|
|
|
{
|
|
|
|
|
// ------------------------------------------------------------
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (verbose || extra_info) print_msg("\n\n");
|
|
|
|
|
fprint_msg("** Normal speed for %d\n",num_normal);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (started)
|
|
|
|
|
{
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = back_to_normal(stream,tswriter,tsdirect);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (err) return 1;
|
|
|
|
|
}
|
|
|
|
|
started = TRUE;
|
|
|
|
|
|
|
|
|
|
set_PES_reader_video_only(reader,video_only);
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (verbose || extra_info) print_msg("\n\n");
|
|
|
|
|
fprint_msg("** Fast forward for %d\n",num_fast);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
set_PES_reader_video_only(reader,TRUE);
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = play_stripped(stream,scontext,tswriter,verbose,quiet,tsdirect,
|
|
|
|
|
num_fast,
|
2008-04-14 04:09:29 +00:00
|
|
|
|
with_seq_hdrs);
|
|
|
|
|
if (err == EOF)
|
|
|
|
|
break;
|
|
|
|
|
else if (err)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (verbose || extra_info) print_msg("\n\n");
|
|
|
|
|
fprint_msg("** Normal speed for %d\n",num_normal);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = back_to_normal(stream,tswriter,tsdirect);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (err) return 1;
|
|
|
|
|
|
|
|
|
|
set_PES_reader_video_only(reader,video_only);
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (verbose || extra_info) print_msg("\n\n");
|
|
|
|
|
fprint_msg("** Faster forward for %d\n",num_faster);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
set_PES_reader_video_only(reader,TRUE);
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = play_filtered(stream,fcontext,tswriter,verbose,quiet,tsdirect,
|
|
|
|
|
num_faster,
|
2008-04-14 04:09:29 +00:00
|
|
|
|
ffrequency,with_seq_hdrs);
|
|
|
|
|
if (err == EOF)
|
|
|
|
|
break;
|
|
|
|
|
else if (err)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (verbose || extra_info) print_msg("\n\n");
|
|
|
|
|
fprint_msg("** Normal speed for %d\n",num_normal);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = back_to_normal(stream,tswriter,tsdirect);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (err) return 1;
|
|
|
|
|
|
|
|
|
|
set_PES_reader_video_only(reader,video_only);
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (verbose || extra_info) print_msg("\n\n");
|
|
|
|
|
fprint_msg("** Reverse for %d\n",num_reverse);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
set_PES_reader_video_only(reader,TRUE);
|
|
|
|
|
err = play_reverse(stream,tswriter,verbose,quiet,rfrequency,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
tsdirect,
|
2008-04-14 04:09:29 +00:00
|
|
|
|
num_reverse,reverse_data);
|
|
|
|
|
if (err == EOF)
|
|
|
|
|
break;
|
|
|
|
|
else if (err)
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (verbose || extra_info) print_msg("\n\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (err == EOF)
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg("** End of file\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
else
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg(">> End of sequences\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
int tsdirect,
|
2008-04-14 04:09:29 +00:00
|
|
|
|
int with_seq_hdrs)
|
|
|
|
|
{
|
|
|
|
|
int err = 0;
|
|
|
|
|
int num_normal = 100;
|
|
|
|
|
int started = FALSE;
|
|
|
|
|
int ii;
|
|
|
|
|
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg(">> Going through sequence once\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
for (ii=0; ii<1; ii++)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg("\n>> Iteration %d\n\n",ii);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (verbose || extra_info) print_msg("\n\n");
|
|
|
|
|
fprint_msg("** Normal speed for %d\n",num_normal);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (started)
|
|
|
|
|
{
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = back_to_normal(stream,tswriter,tsdirect);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (err) return 1;
|
|
|
|
|
}
|
|
|
|
|
started = TRUE;
|
|
|
|
|
|
|
|
|
|
set_PES_reader_video_only(reader,video_only);
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (verbose || extra_info) print_msg("\n\n");
|
|
|
|
|
print_msg("** Skip forwards\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
stop_server_output(reader);
|
|
|
|
|
set_PES_reader_video_only(reader,TRUE);
|
|
|
|
|
err = skip_forwards(stream,tswriter,fcontext,with_seq_hdrs,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
SMALL_SKIP_DISTANCE,verbose,quiet,tsdirect);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (err == EOF)
|
|
|
|
|
break;
|
|
|
|
|
else if (err)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (verbose || extra_info) print_msg("\n\n");
|
|
|
|
|
print_msg("** Skip forwards\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
stop_server_output(reader);
|
|
|
|
|
set_PES_reader_video_only(reader,TRUE);
|
|
|
|
|
err = skip_forwards(stream,tswriter,fcontext,with_seq_hdrs,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
SMALL_SKIP_DISTANCE,verbose,quiet,tsdirect);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (err == EOF)
|
|
|
|
|
break;
|
|
|
|
|
else if (err)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (verbose || extra_info) print_msg("\n\n");
|
|
|
|
|
fprint_msg("** Normal speed for %d\n",num_normal);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = back_to_normal(stream,tswriter,tsdirect);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (err) return 1;
|
|
|
|
|
|
|
|
|
|
set_PES_reader_video_only(reader,video_only);
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (verbose || extra_info) print_msg("\n\n");
|
|
|
|
|
print_msg("** Skip backwards\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
stop_server_output(reader);
|
|
|
|
|
set_PES_reader_video_only(reader,TRUE);
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = skip_backwards(stream,tswriter,1,verbose,quiet,tsdirect,reverse_data);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (err == EOF)
|
|
|
|
|
break;
|
|
|
|
|
else if (err)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (verbose || extra_info) print_msg("\n\n");
|
|
|
|
|
print_msg("** Skip backwards\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
stop_server_output(reader);
|
|
|
|
|
set_PES_reader_video_only(reader,TRUE);
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = skip_backwards(stream,tswriter,1,verbose,quiet,tsdirect,reverse_data);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (err == EOF)
|
|
|
|
|
break;
|
|
|
|
|
else if (err)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (verbose || extra_info) print_msg("\n\n");
|
|
|
|
|
fprint_msg("** Normal speed for %d\n",num_normal);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = back_to_normal(stream,tswriter,tsdirect);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (err) return 1;
|
|
|
|
|
|
|
|
|
|
set_PES_reader_video_only(reader,video_only);
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (verbose || extra_info) print_msg("\n\n");
|
|
|
|
|
print_msg("** Skip forwards\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
stop_server_output(reader);
|
|
|
|
|
set_PES_reader_video_only(reader,TRUE);
|
|
|
|
|
err = skip_forwards(stream,tswriter,fcontext,with_seq_hdrs,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
SMALL_SKIP_DISTANCE,verbose,quiet,tsdirect);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (err == EOF)
|
|
|
|
|
break;
|
|
|
|
|
else if (err)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (verbose || extra_info) print_msg("\n\n");
|
|
|
|
|
print_msg("** Skip backwards\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
stop_server_output(reader);
|
|
|
|
|
set_PES_reader_video_only(reader,TRUE);
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = skip_backwards(stream,tswriter,1,verbose,quiet,tsdirect,reverse_data);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (err == EOF)
|
|
|
|
|
break;
|
|
|
|
|
else if (err)
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
num_normal = 100;
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (verbose || extra_info) print_msg("\n\n");
|
|
|
|
|
fprint_msg("** Normal speed for %d\n",num_normal);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = back_to_normal(stream,tswriter,tsdirect);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (err) return 1;
|
|
|
|
|
|
|
|
|
|
set_PES_reader_video_only(reader,video_only);
|
2008-08-11 22:30:47 +00:00
|
|
|
|
err = play_normal(stream,tswriter,verbose,quiet,tsdirect,num_normal,reverse_data);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (err == EOF)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg("** End of file\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
else if (err)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
err = flush_after_normal(stream,tswriter,verbose,quiet);
|
|
|
|
|
if (err == EOF)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg("** End of file\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
else if (err)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (verbose || extra_info) print_msg("\n\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (err == EOF)
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg("** End of file\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
else
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg(">> End of sequences\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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.
|
|
|
|
|
*/
|
2008-09-18 15:13:57 +00:00
|
|
|
|
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)
|
2008-04-14 04:09:29 +00:00
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
|
2008-09-18 15:13:57 +00:00
|
|
|
|
|
2008-04-14 04:09:29 +00:00
|
|
|
|
// 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
|
|
|
|
|
|
2008-09-18 15:13:57 +00:00
|
|
|
|
// 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);
|
|
|
|
|
|
2008-04-14 04:09:29 +00:00
|
|
|
|
// Wrap our PES stream up as an ES stream
|
|
|
|
|
err = build_elementary_stream_PES(reader,&es);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error trying to build ES reader from PES reader\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build our reverse memory datastructure
|
|
|
|
|
err = build_reverse_data(&reverse_data,reader->is_h264);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Unable to build reverse memory\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error trying to build access unit reader from ES reader\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Unable to build filter context\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Unable to build strip context\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
video_only,verbose,quiet,tsdirect,FALSE);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
else
|
|
|
|
|
err = test_play(reader,stream,fcontext,scontext,reverse_data,tswriter,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
video_only,verbose,quiet,tsdirect,
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error trying to build H.262 reader from ES reader\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Unable to build filter context\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Unable to build strip context\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
video_only,verbose,quiet,tsdirect,with_seq_hdrs);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
else
|
|
|
|
|
err = test_play(reader,stream,fcontext,scontext,reverse_data,tswriter,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
video_only,verbose,quiet,tsdirect,
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### Error opening file %s\n",
|
|
|
|
|
context->input_names[context->default_file_index]);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!quiet)
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg("Opened input file %s (as %s)\n",
|
|
|
|
|
context->input_names[context->default_file_index],
|
|
|
|
|
((*reader)->is_TS?"TS":"PS"));
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
// 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)
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg("File appeared to contain %s, forcing %s\n",
|
|
|
|
|
(*reader)->is_h264?"MPEG-4/AVC (H.264)":"MPEG-2 (H.272)",
|
|
|
|
|
context->want_h262?"MPEG-2":"MPEG-4/AVC");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg("\nLooking at input file %d, %s\n",ii,context->input_names[ii]);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
err = open_PES_reader(context->input_names[ii],!quiet,verbose,&reader[ii]);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("!!! Error opening file %d (%s)\n",
|
|
|
|
|
ii,context->input_names[ii]);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
// return 1;
|
|
|
|
|
reader[ii] = NULL;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!quiet)
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg("Opened input file %2d, %s, as %s\n",ii,context->input_names[ii],
|
|
|
|
|
(reader[ii]->is_TS?"TS":"PS"));
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
// 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)
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg("File appeared to contain %s, forcing %s\n",
|
|
|
|
|
reader[ii]->is_h264?"MPEG-4/AVC (H.264)":"MPEG-2 (H.272)",
|
|
|
|
|
context->want_h262?"MPEG-2":"MPEG-4/AVC");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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];
|
|
|
|
|
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) fprint_msg("Establishing connection with client on socket %d\n",
|
|
|
|
|
tswriter->where.socket);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
err = tswrite_start_input(tswriter,tswriter->where.socket);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Unable to start command input from client\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
(void) tswrite_close(tswriter,TRUE);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = open_input_files(context,quiet,verbose,reader);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Unable to open input file\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
(void) tswrite_close(tswriter,TRUE);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// And play...
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) fprint_msg("Playing to client via socket %d\n",
|
|
|
|
|
tswriter->where.socket);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
err = play_pes_packets(reader,tswriter,context,verbose,quiet);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("!!! Error playing PES packets to client\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
(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
|
|
|
|
|
}
|
|
|
|
|
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) print_msg("Finished talking to client\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### Error closing input file %d, %s\n",ii,
|
|
|
|
|
context->input_names[ii]);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Unable to allocate memory for child datastructure\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("Error creating child process: %s\n",strerror(errno));
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("Error forking: %s\n",strerror(errno));
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg("sighandler: starting\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
#endif
|
|
|
|
|
for (;;)
|
|
|
|
|
{
|
|
|
|
|
int status;
|
|
|
|
|
int pid = waitpid(-1, &status, WNOHANG);
|
|
|
|
|
#if 0
|
|
|
|
|
if (pid > 0)
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg("sighandler: finished with child %08x\n",pid);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
else
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg("sighandler: finished with %d\n",pid);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
#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
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg("sighandler: Setting up signal handler to reap child processes\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
#endif
|
|
|
|
|
ret = sigaction(SIGCHLD,&action,0);
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (ret < 0) print_err("!!! tsserve: Error starting signal handler to reap child processes\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
}
|
|
|
|
|
#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();
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Unable to create socket: ");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
print_winsock_err(err);
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
#else // _WIN32
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### Unable to create socket: %s\n",strerror(errno));
|
2008-04-14 04:09:29 +00:00
|
|
|
|
#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();
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### Unable to bind to port %d: ",listen_port);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
print_winsock_err(err);
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
#else // _WIN32
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### Unable to bind to port %d: %s\n",
|
|
|
|
|
listen_port,strerror(errno));
|
2008-04-14 04:09:29 +00:00
|
|
|
|
#endif // _WIN32
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
|
{
|
|
|
|
|
TS_writer_p tswriter = NULL;
|
|
|
|
|
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) fprint_msg("\nListening for a connection on port %d"
|
|
|
|
|
" with socket %d\n",listen_port,server_socket);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
|
|
|
|
|
#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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error calling winsock_startup before listening\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
#endif // _WIN32
|
|
|
|
|
|
|
|
|
|
err = tswrite_wait_for_client(server_socket,quiet,&tswriter);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### Error listening for client on port %d\n",
|
|
|
|
|
listen_port);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error spawning child server\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error closing socket in parent process\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
int quiet,
|
|
|
|
|
int tsdirect)
|
2008-04-14 04:09:29 +00:00
|
|
|
|
{
|
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### Unable to connect to %s\n",output_name);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Unable to open input file\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
(void) tswrite_close(tswriter,TRUE);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// And play...
|
2008-09-18 15:13:57 +00:00
|
|
|
|
err = test_play_pes_packets(reader,tswriter,context,
|
2008-04-14 04:09:29 +00:00
|
|
|
|
context->pad_start,context->video_only,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
verbose,quiet,tsdirect,
|
2008-04-14 04:09:29 +00:00
|
|
|
|
num_normal,num_fast,num_faster,num_reverse,
|
|
|
|
|
context->ffrequency,context->rfrequency,
|
|
|
|
|
skiptest,context->with_seq_hdrs);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error playing PES packets\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
(void) tswrite_close(tswriter,TRUE);
|
|
|
|
|
(void) close_PES_reader(&reader);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = tswrite_close(tswriter,quiet);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### Error closing output %s: %s\n",output_name,
|
|
|
|
|
strerror(errno));
|
2008-04-14 04:09:29 +00:00
|
|
|
|
(void) close_PES_reader(&reader);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
err = close_PES_reader(&reader);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### Error closing input file %s\n",
|
|
|
|
|
context->input_names[context->default_file_index]);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### Unable to connect to %s\n",output_name);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg("Commands from standard input:\n"
|
|
|
|
|
" q = quit\n"
|
|
|
|
|
" n = normal speed\n"
|
|
|
|
|
" p = pause (the initial state)\n"
|
|
|
|
|
" f = fast forward\n"
|
|
|
|
|
" F = fast fast forward\n"
|
|
|
|
|
" r = reverse\n"
|
|
|
|
|
" R = fast reverse\n"
|
|
|
|
|
" > < = skip forwards, back by 10 seconds\n"
|
|
|
|
|
" ] [ = skip forwards, back by 3 minutes\n"
|
|
|
|
|
" 0..9 = select file 0 through 9 (if defined),\n"
|
|
|
|
|
" rewind it and play at normal speed\n"
|
|
|
|
|
"Use newline to 'send' a command or sequence of commands.\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
err= tswrite_start_input(tswriter,STDIN_FILENO);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Unable to start command input from stdin\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
(void) tswrite_close(tswriter,TRUE);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
#endif // _WIN32
|
|
|
|
|
{
|
|
|
|
|
err= tswrite_start_input(tswriter,tswriter->where.socket);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### Unable to start command input from %s\n",
|
|
|
|
|
output_name);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
(void) tswrite_close(tswriter,TRUE);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = open_input_files(context,quiet,verbose,reader);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Unable to open input file\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
(void) tswrite_close(tswriter,TRUE);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// And play...
|
|
|
|
|
err = play_pes_packets(reader,tswriter,context,verbose,quiet);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### Error playing PES packets\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
(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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### Error closing output %s: %s\n",output_name,
|
|
|
|
|
strerror(errno));
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### Error closing input file %d, %s\n",ii,
|
|
|
|
|
context->input_names[ii]);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
had_err = TRUE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return (had_err?1:0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void print_usage()
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg(
|
2008-04-14 04:09:29 +00:00
|
|
|
|
"Usage:\n"
|
|
|
|
|
" tsserve <infile>\n"
|
|
|
|
|
" tsserve <infile> -port <n>\n"
|
|
|
|
|
" tsserve [switches] <infile> [switches]\n"
|
|
|
|
|
"\n"
|
|
|
|
|
);
|
|
|
|
|
REPORT_VERSION("tsserve");
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg(
|
2008-04-14 04:09:29 +00:00
|
|
|
|
"\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"
|
2009-06-16 20:27:25 +00:00
|
|
|
|
" -err stdout Write error messages to standard output (the default)\n"
|
|
|
|
|
" -err stderr Write error messages to standard error (Unix traditional)\n"
|
2008-04-14 04:09:29 +00:00
|
|
|
|
" -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()
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg(
|
2008-04-14 04:09:29 +00:00
|
|
|
|
"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"
|
2008-06-25 21:24:17 +00:00
|
|
|
|
"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"
|
2008-04-14 04:09:29 +00:00
|
|
|
|
"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"
|
2008-06-25 21:24:17 +00:00
|
|
|
|
" If '-f 0 -ff 0 -r 0' is specified, then the data will just play at\n"
|
|
|
|
|
" normal speed, ignoring -n.\n"
|
|
|
|
|
"\n"
|
2008-04-14 04:09:29 +00:00
|
|
|
|
" -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;
|
|
|
|
|
|
2008-06-25 21:24:17 +00:00
|
|
|
|
// Transport Stream specific options
|
|
|
|
|
context.tsdirect = FALSE; // Write to server as a side effect of PES reading
|
|
|
|
|
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2009-06-16 20:27:25 +00:00
|
|
|
|
else if (!strcmp("-err",argv[argno]))
|
|
|
|
|
{
|
|
|
|
|
CHECKARG("tsserve",argno);
|
|
|
|
|
if (!strcmp(argv[argno+1],"stderr"))
|
|
|
|
|
redirect_output_stderr();
|
|
|
|
|
else if (!strcmp(argv[argno+1],"stdout"))
|
|
|
|
|
redirect_output_stdout();
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
fprint_err("### tsserve: "
|
|
|
|
|
"Unrecognised option '%s' to -err (not 'stdout' or"
|
|
|
|
|
" 'stderr')\n",argv[argno+1]);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
argno++;
|
|
|
|
|
}
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2008-06-25 21:24:17 +00:00
|
|
|
|
else if (!strcmp("-tsdirect",argv[argno]))
|
|
|
|
|
{
|
|
|
|
|
context.tsdirect = TRUE; // Write to server as a side effect of TS reading
|
|
|
|
|
}
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### tsserve: -drop requires two arguments\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### tsserve: -dolby must be followed by dvb or atsc\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### tsserve: "
|
|
|
|
|
"Unrecognised command line switch '%s'\n",argv[argno]);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (had_input_name)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### tsserve: Unexpected '%s'\n",argv[argno]);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
context.input_names[0] = argv[argno];
|
|
|
|
|
had_input_name = TRUE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
argno++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!had_input_name)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### tsserve: No input file specified\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
if (!had_output_name && action != ACTION_SERVER)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### tsserve: No output specified\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
if (output_to_file && action != ACTION_TEST)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### tsserve: Output to a file (-output) is only allowed"
|
|
|
|
|
" with -test\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!quiet)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg("Input files:\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
for (ii = 0; ii < MAX_INPUT_FILES; ii++)
|
|
|
|
|
{
|
|
|
|
|
if (context.input_names[ii] != NULL)
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg(" %2d: %s\n",ii,context.input_names[ii]);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (ii = 0; ii < MAX_INPUT_FILES; ii++)
|
|
|
|
|
{
|
|
|
|
|
if (context.input_names[ii] != NULL)
|
|
|
|
|
{
|
|
|
|
|
context.default_file_index = ii;
|
2009-05-04 19:06:22 +00:00
|
|
|
|
if (!quiet) fprint_msg("File %d (%s) selected as default\n",
|
|
|
|
|
ii,context.input_names[ii]);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2008-06-26 09:21:05 +00:00
|
|
|
|
if (context.tsdirect && !quiet)
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_msg("Serving all TS packets, not just video/audio streams\n");
|
2008-06-26 09:21:05 +00:00
|
|
|
|
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (context.drop_packets && !quiet)
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_msg("DROPPING: Keeping %d TS packet%s, then dropping (throwing away) %d\n",
|
|
|
|
|
context.drop_packets,(context.drop_packets==1?"":"s"),
|
2008-04-14 04:09:29 +00:00
|
|
|
|
context.drop_number);
|
|
|
|
|
|
|
|
|
|
switch (action)
|
|
|
|
|
{
|
|
|
|
|
case ACTION_SERVER:
|
|
|
|
|
err = run_server(&context,listen_port,verbose,quiet);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### tsserve: Error in server\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
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,
|
2008-08-11 22:30:47 +00:00
|
|
|
|
verbose,quiet,context.tsdirect);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### tsserve: Error playing to %s\n",output_name);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ACTION_CMD:
|
|
|
|
|
err = command_reader(&context,output_name,output_port,
|
|
|
|
|
use_stdin,verbose,quiet);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2009-05-04 19:06:22 +00:00
|
|
|
|
fprint_err("### tsserve: Error playing to %s\n",output_name);
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
2009-05-04 19:06:22 +00:00
|
|
|
|
print_err("### No action specified\n");
|
2008-04-14 04:09:29 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2008-06-14 16:05:00 +00:00
|
|
|
|
|
|
|
|
|
// Local Variables:
|
|
|
|
|
// tab-width: 8
|
|
|
|
|
// indent-tabs-mode: nil
|
|
|
|
|
// c-basic-offset: 2
|
|
|
|
|
// End:
|
|
|
|
|
// vim: set tabstop=8 shiftwidth=2 expandtab:
|