kopia lustrzana https://github.com/F5OEO/tstools
1968 wiersze
62 KiB
C
1968 wiersze
62 KiB
C
/*
|
||
* Utilities for reading program stream data.
|
||
*
|
||
* ***** 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>
|
||
|
||
#ifdef _WIN32
|
||
#include <stddef.h>
|
||
#include <io.h>
|
||
#else // _WIN32
|
||
#include <unistd.h>
|
||
#endif // _WIN32
|
||
|
||
#include "compat.h"
|
||
#include "ps_fns.h"
|
||
#include "ts_fns.h"
|
||
#include "pes_fns.h"
|
||
#include "pidint_fns.h"
|
||
#include "misc_fns.h"
|
||
|
||
#define DEBUG 0
|
||
#define DEBUG_AC3 0
|
||
|
||
// How many bytes to look through for a pack header, before giving up
|
||
#define PACK_HEADER_SEARCH_DISTANCE 100000
|
||
|
||
// If we lose where we are, should we look for the next PS pack header?
|
||
// (instead of just giving up)
|
||
#define RECOVER_BROKEN_PS 1
|
||
|
||
// ============================================================
|
||
// PS to TS datastructures
|
||
// ============================================================
|
||
|
||
// Data we need to write PAT/PMT and otherwise manage our program streams
|
||
//
|
||
// DVD allows one video stream and up to 8 audio streams,
|
||
// but we're only interested in a single video and a single audio stream
|
||
struct program_data
|
||
{
|
||
// Information supplied by the user
|
||
u_int32 transport_stream_id;
|
||
u_int32 program_number;
|
||
u_int32 pmt_pid; // PID to use for the PMT
|
||
u_int32 pcr_pid; // PID to use for the PCR
|
||
u_int32 video_pid; // PID to use for our single stream of video
|
||
u_int32 audio_pid; // PID to use for our single stream of audio
|
||
int video_stream; // Which stream id our video is (-1 means use first)
|
||
int audio_stream; // Which stream id our audio is (-1 means use first)
|
||
int want_ac3; // True means audio_stream is private_1/AC3 substream
|
||
int audio_substream; // Which substream id if DVD and want_ac3
|
||
int video_type; // Is our video H.264, H.262, etc. (user must decide)
|
||
int is_dvd; // Is our data DVD program stream (ditto)
|
||
int output_dolby_as_dvb; // Output Dolby (AC-3) audio as DVB or ATSC?
|
||
// Information derived from the data
|
||
// PAT and PMT data
|
||
pidint_list_p prog_list;
|
||
pmt_p pmt;
|
||
};
|
||
|
||
|
||
|
||
// ============================================================
|
||
// Read ahead support
|
||
// ============================================================
|
||
/*
|
||
* Read some more data into our read-ahead buffer.
|
||
*
|
||
* Returns 0 if it succeeds, EOF if the end-of-file is read, otherwise
|
||
* 1 if some error occurs.
|
||
*/
|
||
static inline int get_more_data(PS_reader_p ps)
|
||
{
|
||
// Call `read` directly - we don't particularly mind if we get a "short"
|
||
// read, since we'll just catch up later on
|
||
#ifdef _WIN32
|
||
int len = _read(ps->input,&ps->data,PS_READ_AHEAD_SIZE);
|
||
#else
|
||
ssize_t len = read(ps->input,&ps->data,PS_READ_AHEAD_SIZE);
|
||
#endif
|
||
if (len == 0)
|
||
return EOF;
|
||
else if (len == -1)
|
||
{
|
||
fprintf(stderr,"### Error reading next bytes: %s\n",strerror(errno));
|
||
return 1;
|
||
}
|
||
ps->data_posn += ps->data_len; // length of the *last* buffer
|
||
ps->data_len = len;
|
||
ps->data_end = ps->data + len; // one beyond the last byte
|
||
ps->data_ptr = ps->data; // start at the beginning
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Build a program stream context attached to an input file. This handles
|
||
* read-ahead buffering for the PS.
|
||
*
|
||
* - `input` is the file stream to read from.
|
||
* - If `quiet`, then don't report on ignored bytes at the start of the file
|
||
* - `ps` is the new PS context
|
||
*
|
||
* Returns 0 if all goes well, 1 otherwise.
|
||
*/
|
||
extern int build_PS_reader(int input,
|
||
int quiet,
|
||
PS_reader_p *ps)
|
||
{
|
||
int err;
|
||
PS_reader_p new = malloc(SIZEOF_PS_READER);
|
||
if (new == NULL)
|
||
{
|
||
fprintf(stderr,"### Unable to allocate program stream read context\n");
|
||
return 1;
|
||
}
|
||
|
||
new->input = input;
|
||
new->data_posn = 0;
|
||
new->data_len = 0;
|
||
new->start = 0;
|
||
|
||
err = get_more_data(new);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Unable to start reading from new PS read context\n");
|
||
free(new);
|
||
return 1;
|
||
}
|
||
|
||
// And look for the first pack header
|
||
err = find_PS_pack_header_start(new,FALSE,PACK_HEADER_SEARCH_DISTANCE,
|
||
&(new->start));
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### File does not appear to be PS\n"
|
||
" Cannot find PS pack header in first %d bytes of file\n",
|
||
PACK_HEADER_SEARCH_DISTANCE);
|
||
free(new);
|
||
return 1;
|
||
}
|
||
|
||
// But we don't *really* want to have read its start yet
|
||
err = seek_using_PS_reader(new,new->start);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error seeking to start of first pack header\n");
|
||
free(new);
|
||
return 1;
|
||
}
|
||
|
||
if (!quiet && new->start != 0)
|
||
fprintf(stderr,
|
||
"!!! PS file does not start with pack header\n"
|
||
" First PS pack header is at " OFFSET_T_FORMAT "\n",new->start);
|
||
|
||
*ps = new;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Tidy up the PS read-ahead context after we've finished with it.
|
||
*
|
||
* Specifically:
|
||
*
|
||
* - free the datastructure
|
||
* - set `ps` to NULL
|
||
*
|
||
* Does not close the associated file.
|
||
*/
|
||
extern void free_PS_reader(PS_reader_p *ps)
|
||
{
|
||
if (*ps != NULL)
|
||
{
|
||
(*ps)->input = -1; // "forget" our input
|
||
free(*ps);
|
||
*ps = NULL;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Open a PS file for reading.
|
||
*
|
||
* - `name` is the name of the file. If this is NULL, then standard input
|
||
* is used.
|
||
* - If `quiet`, then don't report on ignored bytes at the start of the file
|
||
* - `ps` is the new PS context
|
||
*
|
||
* Returns 0 if all goes well, 1 otherwise.
|
||
*/
|
||
extern int open_PS_file(char *name,
|
||
int quiet,
|
||
PS_reader_p *ps)
|
||
{
|
||
int f;
|
||
|
||
if (name == NULL)
|
||
f = STDIN_FILENO;
|
||
else
|
||
{
|
||
f = open_binary_file(name,FALSE);
|
||
if (f == -1) return 1;
|
||
}
|
||
return build_PS_reader(f,quiet,ps);
|
||
}
|
||
|
||
/*
|
||
* Close a PS file, and free the reader context
|
||
*
|
||
* (Doesn't close the file if it was standard input)
|
||
*
|
||
* Returns 0 if all goes well, 1 otherwise.
|
||
*/
|
||
extern int close_PS_file(PS_reader_p *ps)
|
||
{
|
||
if ((*ps)->input != STDIN_FILENO)
|
||
{
|
||
int err = close_file((*ps)->input);
|
||
if (err) return 1;
|
||
}
|
||
free_PS_reader(ps);
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Given a program stream, attempt to determine if it holds H.262 or H.264
|
||
* data.
|
||
*
|
||
* Leaves the PS rewound to its "start".
|
||
*
|
||
* NOTE: It is probably better to use determine_PS_video_type().
|
||
*
|
||
* - `ps` is the program stream to check (assumed just to have been
|
||
* opened/built). This cannot be standard input, as it must be
|
||
* seekable.
|
||
* - `is_h264` is the result
|
||
*
|
||
* Returns 0 if all goes well, 1 if there was an error (including the
|
||
* stream not appearing to be either).
|
||
*/
|
||
extern int determine_if_PS_is_h264(PS_reader_p ps,
|
||
int *is_h264)
|
||
{
|
||
int err;
|
||
PES_reader_p reader;
|
||
|
||
// Try to decide what sort of data stream we have
|
||
// The simplest (albeit clumsy) way to do this is to use technology
|
||
// that already does this for us - i.e., build a temporary PES reader
|
||
// around our file
|
||
|
||
// It's then safe to build the temporary PES reader
|
||
err = build_PS_PES_reader(ps,FALSE,FALSE,&reader);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error trying to determine PS stream type\n");
|
||
return 1;
|
||
}
|
||
// Which knows the file type
|
||
*is_h264 = reader->is_h264;
|
||
(void) free_PES_reader(&reader);
|
||
|
||
// And then make sure that our file position is where we think it should be
|
||
err = rewind_program_stream(ps);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error rewinding PS file after determining its type\n");
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Given a program stream, attempt to determine what type of video data it
|
||
* contains.
|
||
*
|
||
* Leaves the PS rewound to its "start".
|
||
*
|
||
* - `ps` is the program stream to check (assumed just to have been
|
||
* opened/built). This cannot be standard input, as it must be
|
||
* seekable.
|
||
* - `video_type` is the result. Calls determine_PES_video_type().
|
||
*
|
||
* Returns 0 if all goes well, 1 if there was an error (including the
|
||
* stream not appearing to be either).
|
||
*/
|
||
extern int determine_PS_video_type(PS_reader_p ps,
|
||
int *video_type)
|
||
{
|
||
int err;
|
||
PES_reader_p reader;
|
||
|
||
// Try to decide what sort of data stream we have
|
||
// The simplest (albeit clumsy) way to do this is to use technology
|
||
// that already does this for us - i.e., build a temporary PES reader
|
||
// around our file
|
||
|
||
// It's then safe to build the temporary PES reader
|
||
err = build_PS_PES_reader(ps,FALSE,FALSE,&reader);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error trying to determine PS stream type\n");
|
||
return 1;
|
||
}
|
||
// Which thinks it knows the file type
|
||
*video_type = reader->video_type;
|
||
(void) free_PES_reader(&reader);
|
||
|
||
// And then make sure that our file position is where we think it should be
|
||
err = rewind_program_stream(ps);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error rewinding PS file after determining its type\n");
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Seek within the PS file.
|
||
*
|
||
* Note that if the intent is to *rewind* to the start of the PS data,
|
||
* then `rewind_program_stream` should be used instead, as offset 0 is
|
||
* not necessarily the same as the start of the program stream.
|
||
*
|
||
* - `ps` is the PS read-ahead context
|
||
* - `posn` is the file offset to seek to
|
||
*
|
||
* Return 0 if all goes well, 1 if something goes wrong
|
||
*/
|
||
extern int seek_using_PS_reader(PS_reader_p ps,
|
||
offset_t posn)
|
||
{
|
||
int err = seek_file(ps->input,posn);
|
||
if (err) return 1;
|
||
|
||
ps->data_posn = posn;
|
||
ps->data_len = 0;
|
||
|
||
return get_more_data(ps);
|
||
}
|
||
|
||
/*
|
||
* Rewind the PS context to the remembered "start of data"
|
||
*
|
||
* Returns 0 if all goes well, 1 if something goes wrong
|
||
*/
|
||
extern int rewind_program_stream(PS_reader_p ps)
|
||
{
|
||
return seek_using_PS_reader(ps,ps->start);
|
||
}
|
||
|
||
/*
|
||
* Retrieve the next N bytes from the program stream, into an existing array.
|
||
*
|
||
* - `ps` is the PS read-ahead context
|
||
* - `num_bytes` is how many bytes to read
|
||
* - `buffer` is the buffer to read them into
|
||
* - `posn` is the offset of said data in the file (NULL if the value is not
|
||
* wanted).
|
||
*
|
||
* Returns 0 if all goes well, EOF if end-of-file is encountered before all
|
||
* of the bytes have been read, 1 if some other error occurred.
|
||
*/
|
||
static int read_PS_bytes(PS_reader_p ps,
|
||
int num_bytes,
|
||
byte *buffer,
|
||
offset_t *posn)
|
||
{
|
||
int err;
|
||
int offset = 0;
|
||
int num_bytes_wanted = num_bytes;
|
||
int num_bytes_left = ps->data_end - ps->data_ptr;
|
||
|
||
if (posn != NULL)
|
||
*posn = ps->data_posn + (ps->data_ptr - ps->data);
|
||
|
||
for (;;)
|
||
{
|
||
if (num_bytes_left < num_bytes_wanted)
|
||
{
|
||
memcpy(&(buffer[offset]),ps->data_ptr,num_bytes_left);
|
||
offset += num_bytes_left;
|
||
num_bytes_wanted -= num_bytes_left;
|
||
err = get_more_data(ps);
|
||
if (err) return err;
|
||
num_bytes_left = ps->data_len;
|
||
}
|
||
else
|
||
{
|
||
memcpy(&(buffer[offset]),ps->data_ptr,num_bytes_wanted);
|
||
ps->data_ptr += num_bytes_wanted;
|
||
break;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
// ============================================================
|
||
// Primitives
|
||
// ============================================================
|
||
/*
|
||
* Print out a stream id in a manner consistent with the PS usages
|
||
* of the stream id values.
|
||
*/
|
||
extern void print_stream_id(FILE *stream,
|
||
byte stream_id)
|
||
{
|
||
byte number;
|
||
char *str = NULL;
|
||
switch (stream_id)
|
||
{
|
||
// H.222 Program stream specific codes
|
||
case 0xB9: str = "PS MPEG_program_end_code"; break;
|
||
case 0xBA: str = "PS Pack header start code"; break;
|
||
case 0xBB: str = "PS System header start code"; break;
|
||
case 0xBC: str = "PS Program stream map"; break;
|
||
case 0xFF: str = "PS Program stream directory"; break;
|
||
|
||
// Other "simple" values from H.222 Table 2-18, page 32
|
||
case 0xBD: str = "Private stream 1"; break;
|
||
case 0xBE: str = "Padding stream"; break;
|
||
case 0xBF: str = "Private stream 2"; break;
|
||
case 0xF0: str = "ECM stream"; break;
|
||
case 0xF1: str = "EMM stream"; break;
|
||
case 0xF2: str = "DSMCC stream"; break;
|
||
case 0xF3: str = "13522 stream"; break;
|
||
case 0xF4: str = "H.222.1 A stream"; break;
|
||
case 0xF5: str = "H.222.1 B stream"; break;
|
||
case 0xF6: str = "H.222.1 C stream"; break;
|
||
case 0xF7: str = "H.222.1 D stream"; break;
|
||
case 0xF8: str = "H.222.1 E stream"; break;
|
||
case 0xF9: str = "Ancillary stream"; break;
|
||
|
||
case 0x00: str = "H.262 Picture"; break;
|
||
case 0xB2: str = "H.262 User data"; break;
|
||
case 0xB3: str = "H.262 Sequence header"; break;
|
||
case 0xB4: str = "H.262 Sequence error"; break;
|
||
case 0xB5: str = "H.262 Extension"; break;
|
||
case 0xB7: str = "H.262 Sequence end"; break;
|
||
case 0xB8: str = "H.262 Group start"; break;
|
||
|
||
default: str = NULL; break;
|
||
}
|
||
|
||
if (str != NULL)
|
||
fprintf(stream,str);
|
||
else if (stream_id >= 0xC0 && stream_id <=0xDF)
|
||
{
|
||
number = stream_id & 0x1F;
|
||
fprintf(stream,"Audio stream 0x%02X",number);
|
||
}
|
||
else if (stream_id >= 0xE0 && stream_id <= 0xEF)
|
||
{
|
||
number = stream_id & 0x0F;
|
||
fprintf(stream,"Video stream 0x%X",number);
|
||
}
|
||
else if (stream_id >= 0xFC && stream_id <= 0xFE)
|
||
fprintf(stream,"Reserved data stream");
|
||
else
|
||
fprintf(stream,"Unrecognised stream id");
|
||
}
|
||
|
||
/*
|
||
* Look for the start (the first 4 bytes) of the next program stream packet.
|
||
*
|
||
* Assumes that (for some reason) alignment has been lost, and thus it is
|
||
* necessary to scan forwards to find the next 00 00 01 prefix.
|
||
*
|
||
* Otherwise equivalent to a call of `read_PS_packet_start`.
|
||
*
|
||
* - `ps` is the PS read-ahead context we're reading from
|
||
* - if `verbose`, then we want to explain what we're doing
|
||
* - if `max` is non-zero, then it is the maximum number of bytes
|
||
* to scan before giving up.
|
||
* - `posn` is the file offset of the start of the packet
|
||
* - `stream_id` is the identifying byte, after the 00 00 01 prefix. Note
|
||
* that this is set correctly if MPEG_program_end_code was read, and is
|
||
* 0 if an error occurred.
|
||
*
|
||
* Returns:
|
||
* * 0 if it succeeds,
|
||
* * EOF if EOF is read, or an MPEG_program_end_code is read, or
|
||
* * 1 if some error (including the first 3 bytes not being 00 00 01) occurs.
|
||
*/
|
||
extern int find_PS_packet_start(PS_reader_p ps,
|
||
int verbose,
|
||
u_int32 max,
|
||
offset_t *posn,
|
||
byte *stream_id)
|
||
{
|
||
int err;
|
||
byte prev1 = 0xff;
|
||
byte prev2 = 0xff;
|
||
byte prev3 = 0xff;
|
||
u_int32 count = 0;
|
||
|
||
*stream_id = 0;
|
||
for (;;)
|
||
{
|
||
byte *ptr;
|
||
for (ptr = ps->data_ptr; ptr < ps->data_end; ptr++)
|
||
{
|
||
if (prev3 == 0x00 && prev2 == 0x00 && prev1 == 0x01)
|
||
{
|
||
if (*ptr == 0xB9) // MPEG_program_end_code
|
||
{
|
||
if (verbose)
|
||
printf("Stopping at MPEG_program_end_code\n");
|
||
*stream_id = 0xB9;
|
||
return EOF;
|
||
}
|
||
else
|
||
{
|
||
*stream_id = *ptr;
|
||
*posn = ps->data_posn + (ptr - ps->data) - 3;
|
||
ps->data_ptr = ptr + 1;
|
||
return 0;
|
||
}
|
||
}
|
||
if (max > 0)
|
||
{
|
||
count ++;
|
||
if (count > max)
|
||
{
|
||
fprintf(stderr,"### No PS packet start found in %d bytes\n",max);
|
||
return 1;
|
||
}
|
||
}
|
||
prev3 = prev2;
|
||
prev2 = prev1;
|
||
prev1 = *ptr;
|
||
}
|
||
// We've run out of data - get some more
|
||
err = get_more_data(ps);
|
||
if (err) return err;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Look for the next PS pack header.
|
||
*
|
||
* Equivalent to calling `find_PS_packet_start` until `stream_id` is 0xBA
|
||
* (in other words, equivalent to having read the pack header start with
|
||
* `read_PS_packet_start`).
|
||
*
|
||
* If you want to call `read_PS_packet_start` to read this pack header start
|
||
* in again, then call ``seek_using_PS_reader(ps,posn)`` to reposition ready
|
||
* to read it.
|
||
*
|
||
* - `ps` is the PS read-ahead context we're reading from
|
||
* - if `verbose`, then the 00 00 01 XX sequences found will be logged
|
||
* to stderr, indicating the progress of our search
|
||
* - if `max` is non-zero, then it is the maximum number of bytes
|
||
* to scan before giving up.
|
||
* - `posn` is the file offset of the start of the packet found
|
||
*
|
||
* Returns:
|
||
* * 0 if it succeeds,
|
||
* * EOF if EOF is read, or an MPEG_program_end_code is read, or
|
||
* * 1 if some error (including the first 3 bytes not being 00 00 01) occurs.
|
||
*/
|
||
extern int find_PS_pack_header_start(PS_reader_p ps,
|
||
int verbose,
|
||
u_int32 max,
|
||
offset_t *posn)
|
||
{
|
||
int err;
|
||
byte stream_id = 0;
|
||
|
||
while (stream_id != 0xBA)
|
||
{
|
||
err = find_PS_packet_start(ps,verbose,max,posn,&stream_id);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error looking for PS pack header (0xBA)\n");
|
||
return 1;
|
||
}
|
||
if (verbose)
|
||
{
|
||
fprintf(stderr," Found: stream id %02X at " OFFSET_T_FORMAT " (",
|
||
stream_id,*posn);
|
||
print_stream_id(stderr,stream_id);
|
||
fprintf(stderr,")\n");
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Read in (the rest of) a PS packet according to its length.
|
||
*
|
||
* Suitable for use reading PS PES packets and PS system header packets.
|
||
*
|
||
* NOTE that the `data` buffer in the `packet` is realloc'ed by this
|
||
* function. It is thus important to ensure that the `packet` datastructure
|
||
* contains a NULL pointer for said buffer before the first call of this
|
||
* function.
|
||
*
|
||
* - `ps` is the PS read-ahead context we're reading from
|
||
* - `stream_id` identifies what sort of packet it is
|
||
* - `packet` is the packet we're reading the PES packet into.
|
||
*
|
||
* Returns 0 if it succeeds, EOF if it unexpectedly reads end-of-file, and 1
|
||
* if some other error occurs. `packet->data` will be NULL if EOF is returned.
|
||
*/
|
||
extern int read_PS_packet_body(PS_reader_p ps,
|
||
byte stream_id,
|
||
PS_packet_p packet)
|
||
{
|
||
int err;
|
||
byte buf[2];
|
||
|
||
// First, the packet length
|
||
err = read_PS_bytes(ps,2,buf,NULL);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### %s reading PS packet length\n",
|
||
(err==EOF?"Unexpected end of file":"Error"));
|
||
if (packet->data!=NULL) free(packet->data);
|
||
packet->data = NULL;
|
||
return err;
|
||
}
|
||
|
||
packet->packet_length = (buf[0] << 8) | buf[1];
|
||
|
||
#if DEBUG
|
||
printf("Packet length %d\n",packet->packet_length);
|
||
#endif
|
||
|
||
// Remember that the packet length is the length of data
|
||
// *after* the packet length field. Also, it is only allowed
|
||
// to be 0 within a Transport Stream, so it should never be 0 for us
|
||
// - but let's check anyway
|
||
if (packet->packet_length == 0)
|
||
{
|
||
fprintf(stderr,"### Packet has length 0 - not allowed in PS\n");
|
||
if (packet->data!=NULL) free(packet->data);
|
||
packet->data = NULL;
|
||
return 1;
|
||
}
|
||
|
||
// Since we are, in general, expecting to write the packet out again
|
||
// at some point, it is convenient to also store the leading bytes
|
||
#if 0 // XXX naughty stuff
|
||
packet->data = realloc(packet->data,packet->packet_length + 6 + 10);
|
||
#else
|
||
packet->data = realloc(packet->data,packet->packet_length + 6);
|
||
#endif
|
||
if (packet->data == NULL)
|
||
{
|
||
fprintf(stderr,"### Unable to allocate PS packet data buffer\n");
|
||
return 1;
|
||
}
|
||
packet->data_len = packet->packet_length + 6;
|
||
|
||
// So let us reestablish said leading bytes
|
||
packet->data[0] = 0;
|
||
packet->data[1] = 0;
|
||
packet->data[2] = 1;
|
||
packet->data[3] = stream_id;
|
||
packet->data[4] = buf[0];
|
||
packet->data[5] = buf[1];
|
||
|
||
// And now we can read in the rest of the packet's data
|
||
err = read_PS_bytes(ps,packet->packet_length,&(packet->data[6]),NULL);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### %s reading rest of PS packet\n",
|
||
(err==EOF?"Unexpected end of file":"Error"));
|
||
if (packet->data!=NULL) free(packet->data);
|
||
packet->data = NULL;
|
||
return err;
|
||
}
|
||
|
||
#if 0 // XXX naughty stuff - add some trailing zero bytes
|
||
packet->data[packet->data_len + 0] = 0;
|
||
packet->data[packet->data_len + 1] = 0;
|
||
packet->data[packet->data_len + 2] = 0;
|
||
packet->data[packet->data_len + 3] = 0;
|
||
packet->data[packet->data_len + 4] = 0;
|
||
packet->data[packet->data_len + 5] = 0;
|
||
packet->data[packet->data_len + 6] = 0;
|
||
packet->data[packet->data_len + 7] = 0;
|
||
packet->data[packet->data_len + 8] = 0;
|
||
packet->data[packet->data_len + 9] = 0;
|
||
packet->packet_length += 10;
|
||
packet->data_len += 10;
|
||
#endif
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Read in the body of the pack header (but *not* the system header packets
|
||
* therein).
|
||
*
|
||
* - `ps` is the PS read-ahead context we're reading from
|
||
* - `hdr` is the packet we've read
|
||
*
|
||
* Returns 0 if it succeeds, EOF if it unexpectedly reads end-of-file, and
|
||
* 1 if some other error occurs.
|
||
*/
|
||
extern int read_PS_pack_header_body(PS_reader_p ps,
|
||
PS_pack_header_p hdr)
|
||
{
|
||
int err;
|
||
byte dummy[8]; // a 3 bit length means no more than 7 stuffing bytes
|
||
|
||
// Read just the first 8 bytes, in case it's an MPEG-1 pack header
|
||
err = read_PS_bytes(ps,8,hdr->data,NULL);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### %s reading body of PS pack header\n",
|
||
(err==EOF?"Unexpected end of file":"Error"));
|
||
return err;
|
||
}
|
||
|
||
if ((hdr->data[0] & 0xF0) == 0x20)
|
||
{
|
||
#if DEBUG
|
||
printf("ISO/IEC 11171-1/MPEG-1 pack header\n");
|
||
print_data(stdout,"Pack header",hdr->data,8,8);
|
||
#endif
|
||
hdr->pack_stuffing_length = 0; // since it doesn't exist
|
||
hdr->scr =
|
||
(((u_int64)(hdr->data[0] & 0x09)) << 29) |
|
||
(((u_int64) hdr->data[1] ) << 22) |
|
||
(((u_int64)(hdr->data[2] & 0xFE)) << 14) |
|
||
(((u_int64) hdr->data[3] ) << 7) |
|
||
(((u_int64)(hdr->data[4] & 0xFE)) >> 1);
|
||
hdr->program_mux_rate =
|
||
(((u_int32)(hdr->data[5] & 0x7F)) << 15) |
|
||
(((u_int32) hdr->data[6] ) << 7) |
|
||
(((u_int32)(hdr->data[7] & 0xFE)) >> 1);
|
||
// In MPEG-1, SCR = NINT(SysClockFreq * t[i]) % 2**33
|
||
// where SysClockFreq = 90,000 Hz
|
||
//
|
||
// In H.222.0, SCR = SCRbase[i] * 300 + SCRext[i]
|
||
// = (((SysClockFreq * t[i]) DIV 300) % 2**33) * 300 +
|
||
// ((SysClockFreq * t[i]) DIV 1) % 300
|
||
// where SysClockFreq = 27,000,000 Hz
|
||
// Fudge these to match H.222.0 in case anyone tries to use them later on
|
||
hdr->scr = hdr->scr * 300;
|
||
hdr->scr_base = hdr->scr / 300;
|
||
hdr->scr_extn = 0; // i.e., hdr->scr % 300
|
||
}
|
||
else
|
||
{
|
||
#if DEBUG
|
||
printf("ISO/IEC 13818-1/H.222.0 pack header\n");
|
||
#endif
|
||
err = read_PS_bytes(ps,2,&(hdr->data[8]),NULL);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### %s reading last 2 bytes of body of PS pack header\n",
|
||
(err==EOF?"Unexpected end of file":"Error"));
|
||
return err;
|
||
}
|
||
#if DEBUG
|
||
print_data(stdout,"Pack header",hdr->data,10,10);
|
||
#endif
|
||
hdr->scr_base =
|
||
(((u_int64)(hdr->data[0] & 0x38)) << 27) |
|
||
(((u_int64)(hdr->data[0] & 0x03)) << 28) |
|
||
(((u_int64) hdr->data[1] ) << 20) |
|
||
(((u_int64)(hdr->data[2] & 0xF8)) << 12) |
|
||
(((u_int64)(hdr->data[2] & 0x03)) << 13) |
|
||
(((u_int64) hdr->data[3] ) << 5) |
|
||
(((u_int64)(hdr->data[4] & 0xF8)) >> 3);
|
||
hdr->scr_extn =
|
||
(((u_int32)(hdr->data[4] & 0x03)) << 7) |
|
||
(((u_int32) hdr->data[5] ) >> 1);
|
||
hdr->scr = hdr->scr_base * 300 + hdr->scr_extn;
|
||
hdr->program_mux_rate =
|
||
(((u_int32)hdr->data[6] << 14)) |
|
||
(((u_int32)hdr->data[7] << 6)) |
|
||
(((u_int32)hdr->data[8] >> 2));
|
||
hdr->pack_stuffing_length = hdr->data[9] & 0x07;
|
||
}
|
||
|
||
#if DEBUG // XXX
|
||
printf("Pack header body: scr_base " LLU_FORMAT ", scr_extn %u, scr "
|
||
LLU_FORMAT ", mux rate %u, stuffing %d\n",
|
||
hdr->scr_base,hdr->scr_extn,hdr->scr,hdr->program_mux_rate,
|
||
hdr->pack_stuffing_length);
|
||
#endif
|
||
|
||
// And ignore that many stuffing bytes...
|
||
if (hdr->pack_stuffing_length > 0)
|
||
{
|
||
err = read_PS_bytes(ps,hdr->pack_stuffing_length,dummy,NULL);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### %s reading PS pack header stuffing bytes\n",
|
||
(err==EOF?"Unexpected end of file":"Error"));
|
||
return err;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Clear the contents of a PS packet datastructure. Frees the internal
|
||
* `data` array.
|
||
*/
|
||
extern void clear_PS_packet(PS_packet_p packet)
|
||
{
|
||
if (packet->data != NULL)
|
||
{
|
||
free(packet->data);
|
||
packet->data = NULL;
|
||
packet->data_len = 0;
|
||
}
|
||
packet->packet_length = 0;
|
||
}
|
||
|
||
/*
|
||
* Tidy up and free a PS packet datastructure after we've finished with it.
|
||
*
|
||
* Empties the PS packet datastructure, frees it, and sets `unit` to NULL.
|
||
*
|
||
* If `unit` is already NULL, does nothing.
|
||
*/
|
||
extern void free_PS_packet(PS_packet_p *packet)
|
||
{
|
||
if (*packet == NULL)
|
||
return;
|
||
clear_PS_packet(*packet);
|
||
free(*packet);
|
||
*packet = NULL;
|
||
}
|
||
|
||
/*
|
||
* Read in the start (the first 4 bytes) of the next program stream packet.
|
||
*
|
||
* If the bytes read don't appear to be valid (i.e., they do not start with
|
||
* the 00 00 01 prefix), then the next pack header will be sought and read in.
|
||
*
|
||
* Note that sequences of 00 bytes before the 00 00 01 will be ignored.
|
||
*
|
||
* - `ps` is the PS read-ahead context we're reading from
|
||
* - if `verbose`, then we want to explain what we're doing
|
||
* - `posn` is the file offset of the start of the packet
|
||
* - `stream_id` is the identifying byte, after the 00 00 01 prefix. Note
|
||
* that this is set correctly if MPEG_program_end_code was read, and is
|
||
* 0 if an error occurred.
|
||
*
|
||
* Returns:
|
||
* * 0 if it succeeds,
|
||
* * EOF if EOF is read, or an MPEG_program_end_code is read,
|
||
* * 2 if the bytes read are not 00 00 01 `stream_id`, or
|
||
* * 1 if some other error occurs.
|
||
*/
|
||
extern int read_PS_packet_start(PS_reader_p ps,
|
||
int verbose,
|
||
offset_t *posn,
|
||
byte *stream_id)
|
||
{
|
||
int err;
|
||
byte buf[4];
|
||
|
||
*stream_id = 0;
|
||
|
||
err = read_PS_bytes(ps,4,buf,posn);
|
||
if (err == EOF)
|
||
return EOF;
|
||
else if (err)
|
||
{
|
||
fprintf(stderr,"### Error reading start of PS packet\n");
|
||
return 1;
|
||
}
|
||
|
||
// It's not uncommon to get a sequence of 00 bytes between packs
|
||
// (in particular, after an audio pack). We don't really want to grumble
|
||
// about such things, but just to cope
|
||
if (buf[0] == 0 && buf[1] == 0 && buf[2] == 0)
|
||
{
|
||
#if 0 // XXX
|
||
printf("// %02x %02x %02x %02x\n",buf[0],buf[1],buf[2],buf[3]);
|
||
#endif
|
||
while (buf[2] == 0) // we already know buf[0] and buf[1] are zero
|
||
{
|
||
buf[2] = buf[3];
|
||
err = read_PS_bytes(ps,1,&(buf[3]),posn);
|
||
if (err == EOF)
|
||
return EOF;
|
||
else if (err)
|
||
{
|
||
fprintf(stderr,"### Error skipping 00 bytes before start of PS packet\n");
|
||
return 1;
|
||
}
|
||
}
|
||
#if 0 // XXX
|
||
printf("\\\\ %02x %02x %02x %02x\n",buf[0],buf[1],buf[2],buf[3]);
|
||
#endif
|
||
}
|
||
|
||
if (buf[0] != 0 || buf[1] != 0 || buf[2] != 1)
|
||
{
|
||
fprintf(stderr,"!!! PS packet at " OFFSET_T_FORMAT " should start "
|
||
"00 00 01, but instead found %02X %02X %02X\n",
|
||
*posn,buf[0],buf[1],buf[2]);
|
||
#if RECOVER_BROKEN_PS
|
||
fprintf(stderr,"!!! Attempting to find next PS pack header\n");
|
||
err = find_PS_pack_header_start(ps,TRUE,0,posn);
|
||
if (err == EOF)
|
||
return EOF;
|
||
else if (err)
|
||
{
|
||
fprintf(stderr,"### Error trying to find start of next pack header\n");
|
||
return 1;
|
||
}
|
||
fprintf(stderr,"!!! Continuing with PS pack header at " OFFSET_T_FORMAT
|
||
"\n",*posn);
|
||
*stream_id = 0xBA;
|
||
return 0;
|
||
#else
|
||
return 2;
|
||
#endif
|
||
}
|
||
|
||
*stream_id = buf[3];
|
||
|
||
#if DEBUG
|
||
printf("Packet at " OFFSET_T_FORMAT ", stream id %02X (",*posn,*stream_id);
|
||
print_stream_id(stdout,*stream_id);
|
||
printf(")\n");
|
||
#endif
|
||
|
||
if (buf[3] == 0xB9) // MPEG_program_end_code
|
||
{
|
||
if (verbose)
|
||
printf("Stopping at MPEG_program_end_code\n");
|
||
return EOF;
|
||
}
|
||
else
|
||
return 0;
|
||
}
|
||
|
||
/* Determine the details of an AC3 stream, by looking at its start
|
||
*
|
||
* Naughtily assume that `data` is long enough...
|
||
*/
|
||
static inline void determine_ac3_details(byte *data, int verbose,
|
||
byte *bsmod, byte *acmod)
|
||
{
|
||
// The end of the syncinfo
|
||
int fscod = (data[4] & 0xC0) >> 6;
|
||
int frmsizecode = (data[4] & 0x3F);
|
||
// The start of the bit stream info
|
||
int bsid = (data[5] & 0xF8) >> 3;
|
||
*bsmod = (data[5] & 0x07);
|
||
*acmod = (data[6] & 0xC0) >> 6;
|
||
if (verbose)
|
||
{
|
||
printf(" fscod %x (sample rate %skHz)\n",fscod,
|
||
(fscod==0?"48":fscod==1?"44.1":fscod==2?"32":"??"));
|
||
printf(" frmsizecode %x\n",frmsizecode);
|
||
printf(" bsid %x (%s)\n",bsid,
|
||
(bsid==8?"standard":bsid==6?"A52a alternate":
|
||
bsid<8?"standard subset":"???"));
|
||
printf(" bsmod %x (%s)\n",*bsmod,BSMOD_STR(*bsmod,*acmod));
|
||
printf(" acmod %x (%s)\n",*acmod,ACMOD_STR(*acmod));
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Inspect the given PS packet, and determine if it contains AC3 or DTS audio data.
|
||
*
|
||
* - `packet` is the packet's data, already established as private_data_1
|
||
* - `is_dvd` is true if the data should be interpreted as DVD data
|
||
* - if `verbose`, report on the details of what we find out
|
||
* - `substream_index` returns the substream's index, taken from the low
|
||
* nibble of the substream id, and adjusted to start at 0. This will be
|
||
* a value in the range 0-7 for DTS, AC3 and LPCM, and in the range 0-1F
|
||
* (0-31) for subpictures.
|
||
* - for AC3, `bsmod` and `acmod` return the appropriate quantities,
|
||
* otherwise they are 0.
|
||
*
|
||
* Returns one of the SUBSTREAM_* values.
|
||
*/
|
||
extern int identify_private1_data(struct PS_packet *packet,
|
||
int is_dvd,
|
||
int verbose,
|
||
int *substream_index,
|
||
byte *bsmod,
|
||
byte *acmod)
|
||
{
|
||
// If this packet contains the start of a data packet, then
|
||
// try to determine if it is AC-3
|
||
int PES_header_data_length = packet->data[6+2];
|
||
int what = SUBSTREAM_OTHER;
|
||
byte *data;
|
||
|
||
#if 0 // Hmm - the data never does seem to have the pusi bit set
|
||
if (verbose)
|
||
{
|
||
int data_alignment_indicator = (packet->data[6] & 0x04);
|
||
if (data_alignment_indicator)
|
||
printf("*** Data aligned\n");
|
||
else
|
||
printf("--- Data not aligned\n");
|
||
printf("*** PES header data length 0x%x (%d)\n",
|
||
PES_header_data_length,PES_header_data_length);
|
||
}
|
||
#endif
|
||
|
||
*substream_index = 0;
|
||
*bsmod = 0;
|
||
*acmod = 0;
|
||
|
||
// This *should* be the start of the underlying packet
|
||
// Note that PES_header_data_length should not be 0 for
|
||
// non-video streams...
|
||
data = packet->data + 6 + 3 + PES_header_data_length;
|
||
|
||
// DVD data has some structure within private_stream_1
|
||
if (is_dvd)
|
||
{
|
||
int substream_id, frame_count, offset;
|
||
substream_id = data[0];
|
||
frame_count = data[1];
|
||
offset = (data[2] << 8) | data[3];
|
||
if (0x20 <= substream_id && substream_id <= 0x3F)
|
||
{
|
||
what = SUBSTREAM_SUBPICTURES;
|
||
*substream_index = substream_id - 0x20;
|
||
}
|
||
else if (0x80 <= substream_id && substream_id <= 0x87)
|
||
{
|
||
what = SUBSTREAM_AC3;
|
||
*substream_index = substream_id - 0x80;
|
||
}
|
||
else if (0x88 <= substream_id && substream_id <= 0x8F)
|
||
{
|
||
what = SUBSTREAM_DTS;
|
||
*substream_index = substream_id - 0x88;
|
||
}
|
||
else if (0xA0 <= substream_id && substream_id <= 0xA7)
|
||
{
|
||
what = SUBSTREAM_LPCM;
|
||
*substream_index = substream_id - 0xA0;
|
||
}
|
||
if (verbose)
|
||
{
|
||
printf(">>> substream_id %02x (%s index %d)\n",substream_id,
|
||
(what==SUBSTREAM_AC3?"AC3":
|
||
what==SUBSTREAM_DTS?"DTS":
|
||
what==SUBSTREAM_LPCM?"LPCM":
|
||
what==SUBSTREAM_SUBPICTURES?"subpictures":"???"),
|
||
*substream_index);
|
||
printf(">>> frame_count %02x (%d)\n",frame_count,frame_count);
|
||
printf(">>> offset %04x (%d)\n",offset,offset);
|
||
}
|
||
// For AC3 and DTS, it's easy to check that it *does* appear to be what it
|
||
// says, so let's do so
|
||
if (what == SUBSTREAM_AC3 || what == SUBSTREAM_DTS)
|
||
{
|
||
int packet_length = (packet->data[4] << 8) | packet->data[5];
|
||
byte *sub_data = data + 3 + offset;
|
||
// Roughly check if the offset looks plausible
|
||
// TODO: make this check more robust!
|
||
if (sub_data >= packet->data + packet_length) // leave off the + 6
|
||
{
|
||
// Looks like it's a silly offset, so probably NOT what we want
|
||
if (verbose)
|
||
printf("*** expected %s, but data at %p is beyond"
|
||
" packet->end at %p\n",(what==SUBSTREAM_DTS?"DTS":"AC3"),
|
||
sub_data,packet->data+6+packet_length);
|
||
what = SUBSTREAM_ERROR; // we definitely mustn't try to interpret it!
|
||
}
|
||
else if (what == SUBSTREAM_AC3 &&
|
||
!(sub_data[0] == 0x0B && sub_data[1] == 0x77))
|
||
{
|
||
printf("*** expected AC3 sync 0x0B77, but found 0x%02x%02x\n",
|
||
sub_data[0],sub_data[1]);
|
||
what = SUBSTREAM_ERROR;
|
||
}
|
||
else if (what == SUBSTREAM_DTS &&
|
||
!(sub_data[0] == 0x7F && sub_data[1] == 0xFE &&
|
||
sub_data[2] == 0x80 && sub_data[3] == 0x01))
|
||
{
|
||
printf("*** expected DTS sync 0x7FFE8001,"
|
||
" but found 0x%02x%02x%02x%02x\n",
|
||
sub_data[0],sub_data[1],sub_data[2],sub_data[3]);
|
||
what = SUBSTREAM_ERROR;
|
||
}
|
||
if (what == SUBSTREAM_AC3)
|
||
determine_ac3_details(sub_data,verbose,bsmod,acmod);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// For non-DVD data, we have to decide for ourselves what we've got
|
||
if (data[0] == 0x0B && data[1] == 0x77)
|
||
{
|
||
what = SUBSTREAM_AC3;
|
||
determine_ac3_details(data,verbose,bsmod,acmod);
|
||
}
|
||
else if (data[0] == 0x7F && data[1] == 0xFE &&
|
||
data[2] == 0x80 && data[3] == 0x01)
|
||
what = SUBSTREAM_DTS;
|
||
}
|
||
if (verbose)
|
||
{
|
||
switch (what)
|
||
{
|
||
case SUBSTREAM_AC3:
|
||
printf("*** Looks like AC3\n");
|
||
break;
|
||
case SUBSTREAM_DTS:
|
||
printf("*** Looks like DTS\n");
|
||
break;
|
||
case SUBSTREAM_LPCM:
|
||
printf("*** Looks like LPCM\n");
|
||
break;
|
||
case SUBSTREAM_SUBPICTURES:
|
||
printf("*** Looks like sub-pictures\n");
|
||
break;
|
||
case SUBSTREAM_OTHER:
|
||
printf("*** Other substream: %02x %02x %02x %02x\n",
|
||
data[0],data[1],data[2],data[3]);
|
||
break;
|
||
default:
|
||
printf("*** Error recognising substream: %02x %02x %02x %02x\n",
|
||
data[0],data[1],data[2],data[3]);
|
||
break;
|
||
}
|
||
}
|
||
return what;
|
||
}
|
||
|
||
// ============================================================
|
||
// PS to TS functions
|
||
// ============================================================
|
||
/*
|
||
* Write out a video packet
|
||
*
|
||
* - `output` is the transport stream we're writing to
|
||
* - `header` is the data from the PS pack header, including its SCR data
|
||
* - `stream_id` is the stream id of the PS packet we're writing
|
||
* - `packet` is the PS packet itself
|
||
* - `prog_data` is the programming information we're using
|
||
* - `num_video_ignored` will be be updated if we ignore this video packet
|
||
* - `num_video_written` will be updated if we don't
|
||
* - if `verbose` then we want to output diagnostic information
|
||
* - if `quiet` then we want to be as quiet as we can
|
||
*
|
||
* Returns 0 if all went well, 1 if something went wrong.
|
||
*/
|
||
static int write_video(TS_writer_p output,
|
||
struct PS_pack_header *header,
|
||
byte stream_id,
|
||
struct PS_packet *packet,
|
||
struct program_data *prog_data,
|
||
int *num_video_ignored,
|
||
int *num_video_written,
|
||
int verbose,
|
||
int quiet)
|
||
{
|
||
int err;
|
||
|
||
// Unless the user has requested a particular video stream, we want
|
||
// to use the first we find...
|
||
// (For DVD this should also be the only stream, since DVD only allows
|
||
// one video stream, but that's not for us to check.)
|
||
|
||
if (prog_data->video_stream == -1)
|
||
{
|
||
prog_data->video_stream = stream_id;
|
||
}
|
||
else if (stream_id != prog_data->video_stream)
|
||
{
|
||
static int ignored_stream[NUMBER_VIDEO_STREAMS] = {0};
|
||
int this_stream = stream_id & 0x0F;
|
||
if (!ignored_stream[this_stream])
|
||
{
|
||
ignored_stream[this_stream] = TRUE;
|
||
if (!quiet)
|
||
printf("Ignoring video stream 0x%x (%d)\n",this_stream,this_stream);
|
||
}
|
||
(*num_video_ignored) ++;
|
||
return 0;
|
||
}
|
||
|
||
if (*num_video_written == 0)
|
||
{
|
||
if (!quiet)
|
||
printf("Video: stream %d, PID 0x%03x, stream type 0x%02x\n"
|
||
" %s\n",
|
||
stream_id & 0x0F,prog_data->video_pid,prog_data->video_type,
|
||
H222_STREAM_TYPE_STR(prog_data->video_type));
|
||
|
||
err = add_stream_to_pmt(prog_data->pmt,prog_data->video_pid,
|
||
prog_data->video_type,0,NULL);
|
||
if (err) return 1;
|
||
prog_data->pmt->version_number ++;
|
||
|
||
// And it makes sense to write out our (updated) program
|
||
// information before we write out the first packet of
|
||
// the new video (!)
|
||
err = write_pat_and_pmt(output,
|
||
prog_data->transport_stream_id,
|
||
prog_data->prog_list,
|
||
prog_data->pmt_pid,
|
||
prog_data->pmt);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,
|
||
"### Error writing TS program data before video packet\n");
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
// This is our video stream - output it as such
|
||
err = write_PES_as_TS_PES_packet(output,packet->data,packet->data_len,
|
||
prog_data->video_pid,
|
||
DEFAULT_VIDEO_STREAM_ID,
|
||
TRUE,header->scr_base,header->scr_extn);
|
||
if (err) return 1;
|
||
|
||
(*num_video_written) ++;
|
||
if (verbose)
|
||
{
|
||
printf("v");
|
||
fflush(stdout);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Write out the data for our DVD private_stream_1 audio packet
|
||
*
|
||
* - `output` is the transport stream we're writing to
|
||
* - `packet` is the PS packet itself
|
||
* - `prog_data` is the programming information we're using
|
||
*
|
||
* Returns 0 if all went well, 1 if something went wrong.
|
||
*/
|
||
static int write_DVD_AC3_data(TS_writer_p output,
|
||
struct PS_packet *packet,
|
||
struct program_data *prog_data)
|
||
{
|
||
// DVD private_stream_1 is packaged as substreams - we need to unpack
|
||
// it before we output it
|
||
// Basically, looking at the substream offset:
|
||
// 0 means there is no first frame in this packet (i.e., the data
|
||
// is the middle/end of a frame
|
||
// 1 means the data *starts* with the first frame (to which any
|
||
// PTS applies)
|
||
// N means that the frame to which the PTS applies starts at
|
||
// offset N-1
|
||
int PES_header_data_length = packet->data[6+2];
|
||
byte *data = packet->data + 6 + 3 + PES_header_data_length;
|
||
int data_len = packet->data_len - 6 - 3 - PES_header_data_length;
|
||
//int substream_id = data[0];
|
||
int frame_count = data[1]; // frames starting in this packet
|
||
int offset = (data[2] << 8) | data[3];
|
||
|
||
// If there is no PTS in this packet, do we need to split, even
|
||
// if the offset is > 1? I'll assume not...
|
||
int got_pts;
|
||
u_int64 pts;
|
||
int PES_packet_length = (packet->data[4] << 8) | packet->data[5];
|
||
|
||
int err = find_PTS_in_PES(packet->data,packet->data_len,&got_pts,&pts);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error looking for PTS in PES packet\n");
|
||
return 1;
|
||
}
|
||
|
||
#if DEBUG_AC3
|
||
printf(".. frame_count=%d, offset=%4d, got_pts=%d ",
|
||
frame_count,offset,got_pts);
|
||
if (frame_count > 0 && offset > 0)
|
||
{
|
||
if (data[offset+3]==0x0B && data[offset+4]==0x77)
|
||
printf("(frame is AC3)\n");
|
||
else
|
||
printf("(frame appears to start %02x %02x)\n",
|
||
data[offset+3],data[offset+4]);
|
||
}
|
||
#endif
|
||
|
||
if (frame_count == 0 || offset <= 1 || !got_pts)
|
||
{
|
||
// We can output the data from this packet "unsplit".
|
||
// However, first we need to create a new packet that does not
|
||
// contain the DVD substream header - this is most easily done
|
||
// by (a) copying the audio data bytes "down" over the header
|
||
// we want to lose, and (b) adjusting the various packet length
|
||
// fields appropriately
|
||
|
||
#if DEBUG_AC3
|
||
printf("move data down 4, leaving %4d\n",data_len-4);
|
||
#endif
|
||
|
||
(void) memmove(data,data+4,data_len-4); // 4 bytes of substream header
|
||
|
||
// After copying, need to remember to adjust the packet length
|
||
PES_packet_length -= 4; // still 4 bytes of substream header
|
||
packet->data[4] = (PES_packet_length & 0xFF00) >> 8;
|
||
packet->data[5] = (PES_packet_length & 0x00FF);
|
||
|
||
// And we then have something suitable for outputting as-is
|
||
err = write_PES_as_TS_PES_packet(output,packet->data,packet->data_len-4,
|
||
prog_data->audio_pid,
|
||
PRIVATE1_AUDIO_STREAM_ID,
|
||
FALSE,0,0);
|
||
if (err) return 1;
|
||
}
|
||
else
|
||
{
|
||
// We need to output the data *before* the "first" packet
|
||
// in a plain PES packet, by itself, and then output the
|
||
// packet to which the PTS applies as a separate PES packet.
|
||
|
||
#if DEBUG_AC3
|
||
printf("write first %4d bytes, then move data down %4d, leaving %4d\n",
|
||
offset-1,3+offset,data_len-3-offset);
|
||
#endif
|
||
|
||
// First, the part before the first packet...
|
||
err = write_ES_as_TS_PES_packet(output,data+4,offset-1,
|
||
prog_data->audio_pid,
|
||
PRIVATE1_AUDIO_STREAM_ID);
|
||
if (err) return 1;
|
||
|
||
// And we can then do the "move the data down" trick
|
||
// Remember that offset 1 means the first byte after the substream
|
||
// header, which we would normally expect to be offset 0...
|
||
(void) memmove(data,data+3+offset,data_len-3-offset);
|
||
|
||
// After copying, need to remember to adjust the packet length
|
||
PES_packet_length -= 3 + offset;
|
||
packet->data[4] = (PES_packet_length & 0xFF00) >> 8;
|
||
packet->data[5] = (PES_packet_length & 0x00FF);
|
||
|
||
// And we then have something suitable for outputting as-is
|
||
err = write_PES_as_TS_PES_packet(output,packet->data,
|
||
packet->data_len-3-offset,
|
||
prog_data->audio_pid,
|
||
PRIVATE1_AUDIO_STREAM_ID,
|
||
FALSE,0,0);
|
||
if (err) return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Write out an audio packet
|
||
*
|
||
* - `output` is the transport stream we're writing to
|
||
* - `stream_id` is the stream id of the PS packet we're writing
|
||
* - `packet` is the PS packet itself
|
||
* - `prog_data` is the programming information we're using
|
||
* - `num_audio_ignored` will be be updated if we ignore this audio packet
|
||
* - `num_audio_written` will be updated if we don't
|
||
* - if `verbose` then we want to output diagnostic information
|
||
* - if `quiet` then we want to be as quiet as we can
|
||
*
|
||
* Returns 0 if all went well, 1 if something went wrong.
|
||
*/
|
||
static int write_audio(TS_writer_p output,
|
||
byte stream_id,
|
||
struct PS_packet *packet,
|
||
struct program_data *prog_data,
|
||
int *num_audio_ignored,
|
||
int *num_audio_written,
|
||
int verbose,
|
||
int quiet)
|
||
{
|
||
int err;
|
||
int substream_index = -1;
|
||
byte bsmod = 0xFF;
|
||
byte asmod = 0xFF;
|
||
int is_h222_pes = IS_H222_PES(packet->data);
|
||
|
||
// For MPEG audio, unless the user has requested a particular audio stream,
|
||
// we want to use the first we find.
|
||
// For AC3, we only want audio from the requested substream
|
||
// (DVD allows up to 8 audio streams, but we're only outputting one.
|
||
//
|
||
// Note that we assume that any audio data using private stream 1 to
|
||
// transmit DVD-style audio packets will also be using PES (H.222.0)
|
||
// packeting - i.e., *not* using MPEG-1 packets. This allows the code
|
||
// to "dissect" DVD audio packets to be simpler, and seems a reasonable
|
||
// assumption.
|
||
|
||
if (prog_data->audio_stream == -1) // take the first audio stream we find...
|
||
{
|
||
if (stream_id == PRIVATE1_AUDIO_STREAM_ID && is_h222_pes)
|
||
{
|
||
// Find out what type of data this is
|
||
int what = identify_private1_data(packet,prog_data->is_dvd,verbose,
|
||
&substream_index,&bsmod,&asmod);
|
||
if (what != SUBSTREAM_AC3)
|
||
{
|
||
// We're not interested in it
|
||
return 0;
|
||
}
|
||
prog_data->audio_stream = stream_id;
|
||
prog_data->audio_substream = substream_index; // only meaningful for DVD
|
||
}
|
||
else // some other ("normal") audio stream
|
||
prog_data->audio_stream = stream_id;
|
||
}
|
||
else if (stream_id != prog_data->audio_stream)
|
||
{
|
||
if (!quiet)
|
||
{
|
||
static int ignored_stream[NUMBER_AUDIO_STREAMS] = {0};
|
||
int this_stream = stream_id & 0x1F;
|
||
if (!ignored_stream[this_stream])
|
||
{
|
||
ignored_stream[this_stream] = TRUE;
|
||
printf("Ignoring audio stream 0x%x (%d)\n",this_stream,this_stream);
|
||
}
|
||
}
|
||
(*num_audio_ignored) ++;
|
||
return 0;
|
||
}
|
||
else if (prog_data->is_dvd &&
|
||
stream_id == PRIVATE1_AUDIO_STREAM_ID &&
|
||
prog_data->audio_stream == PRIVATE1_AUDIO_STREAM_ID &&
|
||
is_h222_pes)
|
||
{
|
||
// Check if this is the right substream
|
||
int what = identify_private1_data(packet,prog_data->is_dvd,verbose,
|
||
&substream_index,&bsmod,&asmod);
|
||
if (what != SUBSTREAM_AC3)
|
||
{
|
||
if (!quiet)
|
||
{
|
||
#define MAX_IGNORED_NON_AC3 10 // report the first 10 substreams we're ignoring
|
||
static u_int32 ignored_non_AC3[MAX_IGNORED_NON_AC3] = {0};
|
||
u_int32 lookfor = (what << 16) | substream_index;
|
||
int ii;
|
||
for (ii = 0; ii < MAX_IGNORED_NON_AC3; ii++)
|
||
{
|
||
if (ignored_non_AC3[ii] == lookfor)
|
||
break; // we've reported it before
|
||
else if (ignored_non_AC3[ii] == 0)
|
||
{
|
||
// We've not reported it before, and have room to remember it
|
||
ignored_non_AC3[ii] = lookfor;
|
||
printf("Ignoring %sprivate_stream_1 substream 0x%x (%d)"
|
||
" containing %s\n",
|
||
(SUBSTREAM_IS_AUDIO(what)?"":"non-audio "),
|
||
substream_index,substream_index,SUBSTREAM_STR(what));
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (SUBSTREAM_IS_AUDIO(what))
|
||
(*num_audio_ignored) ++;
|
||
return 0;
|
||
}
|
||
else if (substream_index != prog_data->audio_substream)
|
||
{
|
||
if (!quiet)
|
||
{
|
||
static int ignored_ac3_substream[NUMBER_AC3_SUBSTREAMS] = {0};
|
||
if (!ignored_ac3_substream[substream_index])
|
||
{
|
||
ignored_ac3_substream[substream_index] = TRUE;
|
||
printf("Ignoring private_stream_1 substream 0x%x (%d) "
|
||
"containing AC3\n",substream_index,substream_index);
|
||
}
|
||
}
|
||
(*num_audio_ignored) ++;
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
if (*num_audio_written == 0)
|
||
{
|
||
byte audio_stream_type;
|
||
if (stream_id == PRIVATE1_AUDIO_STREAM_ID)
|
||
{
|
||
if (prog_data->output_dolby_as_dvb)
|
||
audio_stream_type = DVB_DOLBY_AUDIO_STREAM_TYPE;
|
||
else
|
||
audio_stream_type = ATSC_DOLBY_AUDIO_STREAM_TYPE;
|
||
}
|
||
else
|
||
audio_stream_type = MPEG2_AUDIO_STREAM_TYPE;
|
||
|
||
if (!quiet)
|
||
{
|
||
if (stream_id == PRIVATE1_AUDIO_STREAM_ID)
|
||
{
|
||
printf("Audio: private stream 1,");
|
||
if (prog_data->is_dvd && is_h222_pes)
|
||
printf(" substream %d,",prog_data->audio_substream);
|
||
printf(" PID 0x%03x, AC-3 (Dolby)\n",prog_data->audio_pid);
|
||
printf(" %s\n audio coding mode %s\n",
|
||
BSMOD_STR(bsmod,asmod),ACMOD_STR(asmod));
|
||
}
|
||
else
|
||
{
|
||
printf("Audio: stream %d, PID 0x%03x, stream type 0x%02x = %s\n",
|
||
stream_id & 0x1F,prog_data->audio_pid,audio_stream_type,
|
||
H222_STREAM_TYPE_STR(audio_stream_type));
|
||
}
|
||
}
|
||
|
||
if (audio_stream_type == DVB_DOLBY_AUDIO_STREAM_TYPE)
|
||
{
|
||
byte desc[] = {0x6A, 0x01, 0x00};
|
||
int desc_len = 3;
|
||
err = add_stream_to_pmt(prog_data->pmt,prog_data->audio_pid,
|
||
audio_stream_type,desc_len,desc);
|
||
}
|
||
else if (audio_stream_type == ATSC_DOLBY_AUDIO_STREAM_TYPE)
|
||
{
|
||
byte desc[] = {0x05, 0x04, 0x41, 0x43, 0x2D, 0x33};
|
||
int desc_len = 6;
|
||
err = add_stream_to_pmt(prog_data->pmt,prog_data->audio_pid,
|
||
audio_stream_type,desc_len,desc);
|
||
}
|
||
else
|
||
err = add_stream_to_pmt(prog_data->pmt,prog_data->audio_pid,
|
||
audio_stream_type,0,NULL);
|
||
if (err) return 1;
|
||
prog_data->pmt->version_number ++;
|
||
|
||
// And it makes sense to write out our (updated) program
|
||
// information before we write out the first packet of
|
||
// the new audio
|
||
err = write_pat_and_pmt(output,
|
||
prog_data->transport_stream_id,
|
||
prog_data->prog_list,
|
||
prog_data->pmt_pid,
|
||
prog_data->pmt);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,
|
||
"### Error writing TS program data before audio packet\n");
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
if (prog_data->is_dvd && stream_id == PRIVATE1_AUDIO_STREAM_ID &&
|
||
is_h222_pes)
|
||
{
|
||
// Unpack the DVD substreams before outputting them
|
||
err = write_DVD_AC3_data(output,packet,prog_data);
|
||
if (err) return 1;
|
||
}
|
||
else
|
||
{
|
||
err = write_PES_as_TS_PES_packet(output,packet->data,packet->data_len,
|
||
prog_data->audio_pid,
|
||
(prog_data->want_ac3?
|
||
PRIVATE1_AUDIO_STREAM_ID:
|
||
DEFAULT_AUDIO_STREAM_ID),
|
||
FALSE,0,0);
|
||
if (err) return 1;
|
||
}
|
||
|
||
(*num_audio_written) ++;
|
||
if (verbose)
|
||
{
|
||
printf("a");
|
||
fflush(stdout);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Read program stream and write transport stream
|
||
*
|
||
* - `ps` is the program stream
|
||
* - `output` is the transport stream
|
||
* - `pad_start` is the number of filler TS packets to start the output
|
||
* with.
|
||
* - `program_repeat` is how often (after how many PS packs) to repeat
|
||
* the program information (PAT/PMT)
|
||
* - `is_dvd` should be true if this input represents DVD data; i.e., with
|
||
* private_stream_1 used for AC-3/DTS/etc., and with substream headers
|
||
* therein.
|
||
* - `video_stream` indicates which video stream we want - i.e., the stream
|
||
* with id 0xE0 + <video_stream>. -1 means the first encountered.
|
||
* - `audio_stream` indicates which audio stream we want. If `want_ac3_audio`
|
||
* is false, then this will be the stream with id 0xC0 + <audio_stream>,
|
||
* or -1 for the first audio stream encountered.
|
||
* - if `want_ac3_audio` is true, then if `is_dvd` is true, then we want
|
||
* audio from private_stream_1 (0xBD) with substream id <audio_stream>,
|
||
* otherwise we ignore `audio_stream` and assume that all data in
|
||
* private_stream_1 is the audio we want.
|
||
* - `output_dolby_as_dvb` should be true if Dolby (AC-3) audio (if selected) should
|
||
* be output using the DVB stream type 0x06, false if using the ATSC stream
|
||
* type 0x81. This is ignored if the audio being output is not Dolby.
|
||
* - `pmt_pid` is the PID of the PMT to write
|
||
* - `pcr_pid` is the PID of the TS unit containing the PCR
|
||
* - `video_pid` is the PID for the video we write
|
||
* - `keep_audio` is true if the audio stream should be output, false if
|
||
* it should be ignored
|
||
* - `audio_pid` is the PID for the audio we write
|
||
* - if `max` is non-zero, then we want to stop reading after we've read
|
||
* `max` packs
|
||
* - if `verbose` then we want to output diagnostic information
|
||
* - if `quiet` then we want to be as quiet as we can
|
||
*
|
||
* Returns 0 if all went well, 1 if something went wrong.
|
||
*/
|
||
static int _ps_to_ts(PS_reader_p ps,
|
||
TS_writer_p output,
|
||
struct program_data *prog_data,
|
||
int pad_start,
|
||
int program_repeat,
|
||
int keep_audio,
|
||
int max,
|
||
int verbose,
|
||
int quiet)
|
||
{
|
||
int ii, err;
|
||
offset_t posn = 0; // The location in the input file of the current packet
|
||
int count = 0; // Number of PS packets
|
||
byte stream_id; // The packet's stream id
|
||
int end_of_file = FALSE;
|
||
|
||
// Summary data
|
||
int num_packs = 0;
|
||
int num_audio_written = 0;
|
||
int num_video_written = 0;
|
||
int num_video_ignored = 0;
|
||
int num_audio_ignored = 0;
|
||
|
||
struct PS_packet packet = {0};
|
||
struct PS_pack_header header = {0};
|
||
|
||
// 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(output);
|
||
if (err) return 1;
|
||
}
|
||
|
||
if (!quiet)
|
||
printf("Writing transport stream id 1, PMT PID 0x%02x, PCR PID 0x%02x\n",
|
||
prog_data->pmt_pid,prog_data->pcr_pid);
|
||
|
||
// Read the start of the first packet (we confidently expect this
|
||
// to be a pack header)
|
||
err = read_PS_packet_start(ps,verbose,&posn,&stream_id);
|
||
if (err == EOF)
|
||
{
|
||
fprintf(stderr,"### Error reading first pack header\n");
|
||
fprintf(stderr," Unexpected end of PS at start of stream\n");
|
||
return 1;
|
||
}
|
||
else if (err)
|
||
{
|
||
fprintf(stderr,"### Error reading first pack header\n");
|
||
return 1;
|
||
}
|
||
count ++;
|
||
|
||
if (stream_id != 0xba)
|
||
{
|
||
fprintf(stderr,"### Program stream does not start with pack header\n");
|
||
fprintf(stderr," First packet has stream id %02X (",stream_id);
|
||
print_stream_id(stderr,stream_id);
|
||
fprintf(stderr,")\n");
|
||
return 1;
|
||
}
|
||
|
||
// But given that, we can now happily loop reading in packs
|
||
|
||
// I *think* using this macro makes the code marginally more readable,
|
||
// and it helps emphasise that the code *is* identical each time
|
||
#define READ_NEXT_PS_PACKET_START \
|
||
err = read_PS_packet_start(ps,FALSE,&posn,&stream_id); \
|
||
if (err == EOF) \
|
||
{ \
|
||
end_of_file = TRUE; \
|
||
break; \
|
||
} \
|
||
else if (err) \
|
||
return 1; \
|
||
count ++;
|
||
|
||
|
||
for (;;)
|
||
{
|
||
int num_system_headers = 0;
|
||
|
||
if (max > 0 && num_packs >= max)
|
||
{
|
||
if (verbose)
|
||
printf("Stopping after %d packs\n",num_packs);
|
||
return 0;
|
||
}
|
||
|
||
num_packs ++;
|
||
|
||
// Write out our program data every so often, to give the reader
|
||
// a chance to resynchronise with our program stream
|
||
if (num_packs % program_repeat == 0)
|
||
{
|
||
if (verbose)
|
||
{
|
||
printf("PGM");
|
||
fflush(stdout);
|
||
}
|
||
err = write_pat_and_pmt(output,
|
||
prog_data->transport_stream_id,
|
||
prog_data->prog_list,
|
||
prog_data->pmt_pid,
|
||
prog_data->pmt);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error writing out TS program data\n");
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
err = read_PS_pack_header_body(ps,&header);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,
|
||
"### Error reading data for pack header starting at "
|
||
OFFSET_T_FORMAT "\n",posn);
|
||
return 1;
|
||
}
|
||
|
||
// Look at the start of the next packet
|
||
READ_NEXT_PS_PACKET_START;
|
||
|
||
// If it's a system header, ignore it
|
||
if (stream_id == 0xbb)
|
||
{
|
||
err = read_PS_packet_body(ps,stream_id,&packet);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,
|
||
"### Error reading system header starting at "
|
||
OFFSET_T_FORMAT "\n",posn);
|
||
return 1;
|
||
}
|
||
num_system_headers ++;
|
||
|
||
READ_NEXT_PS_PACKET_START;
|
||
}
|
||
|
||
if (end_of_file)
|
||
break;
|
||
|
||
// Then read the data packets
|
||
while (stream_id != 0xba) // i.e., until the start of the next pack
|
||
{
|
||
err = read_PS_packet_body(ps,stream_id,&packet);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error reading PS packet starting at "
|
||
OFFSET_T_FORMAT "\n",posn);
|
||
return 1;
|
||
}
|
||
|
||
if (IS_AUDIO_STREAM_ID(stream_id))
|
||
{
|
||
if (keep_audio)
|
||
{
|
||
err = write_audio(output,stream_id,&packet,prog_data,
|
||
&num_audio_ignored,&num_audio_written,
|
||
verbose,quiet);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error writing audio packet at "
|
||
OFFSET_T_FORMAT " to TS\n",posn);
|
||
return 1;
|
||
}
|
||
}
|
||
}
|
||
else if (IS_VIDEO_STREAM_ID(stream_id))
|
||
{
|
||
err = write_video(output,&header,stream_id,&packet,prog_data,
|
||
&num_video_ignored,&num_video_written,verbose,quiet);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error writing video packet at " OFFSET_T_FORMAT
|
||
" to TS\n",posn);
|
||
return 1;
|
||
}
|
||
}
|
||
else if (verbose)
|
||
{
|
||
// For the moment, we ignore program stream map (0xBC) and
|
||
// program stream directory (0xFF), and indeed everything else
|
||
}
|
||
|
||
READ_NEXT_PS_PACKET_START;
|
||
}
|
||
if (end_of_file)
|
||
break;
|
||
}
|
||
|
||
clear_PS_packet(&packet);
|
||
|
||
if (verbose) printf("\n");
|
||
if (!quiet)
|
||
{
|
||
printf("Packets (total): %6d\n",count);
|
||
printf("Packs: %6d\n",num_packs);
|
||
printf("Video packets written: %6d\n",num_video_written);
|
||
printf("Audio packets written: %6d\n",num_audio_written);
|
||
|
||
if (num_video_ignored > 0)
|
||
printf("Video packets ignored: %6d\n",num_video_ignored);
|
||
if (num_audio_ignored > 0)
|
||
printf("Audio packets ignored: %6d\n",num_audio_ignored);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Read program stream and write transport stream
|
||
*
|
||
* - `ps` is the program stream
|
||
* - `output` is the transport stream
|
||
* - `pad_start` is the number of filler TS packets to start the output
|
||
* with.
|
||
* - `program_repeat` is how often (after how many PS packs) to repeat
|
||
* the program information (PAT/PMT)
|
||
* - `video_type` indicates what type of video is being transferred. It should
|
||
* be VIDEO_H264, VIDEO_H262, etc.
|
||
* - `is_dvd` should be true if this input represents DVD data; i.e., with
|
||
* private_stream_1 used for AC-3/DTS/etc., and with substream headers
|
||
* therein.
|
||
* - `video_stream` indicates which video stream we want - i.e., the stream
|
||
* with id 0xE0 + <video_stream>. -1 means the first encountered.
|
||
* - `audio_stream` indicates which audio stream we want. If `want_ac3_audio`
|
||
* is false, then this will be the stream with id 0xC0 + <audio_stream>,
|
||
* or -1 for the first audio stream encountered.
|
||
* - if `want_ac3_audio` is true, then if `is_dvd` is true, then we want
|
||
* audio from private_stream_1 (0xBD) with substream id <audio_stream>,
|
||
* otherwise we ignore `audio_stream` and assume that all data in
|
||
* private_stream_1 is the audio we want.
|
||
* - `output_dolby_as_dvb` should be true if Dolby (AC-3) audio (if selected) should
|
||
* be output using the DVB stream type 0x06, false if using the ATSC stream
|
||
* type 0x81. This is ignored if the audio being output is not Dolby.
|
||
* - `pmt_pid` is the PID of the PMT to write
|
||
* - `pcr_pid` is the PID of the TS unit containing the PCR
|
||
* - `video_pid` is the PID for the video we write
|
||
* - `keep_audio` is true if the audio stream should be output, false if
|
||
* it should be ignored
|
||
* - `audio_pid` is the PID for the audio we write
|
||
* - if `max` is non-zero, then we want to stop reading after we've read
|
||
* `max` packs
|
||
* - if `verbose` then we want to output diagnostic information
|
||
* - if `quiet` then we want to be as quiet as we can
|
||
*
|
||
* Returns 0 if all went well, 1 if something went wrong.
|
||
*/
|
||
extern int ps_to_ts(PS_reader_p ps,
|
||
TS_writer_p output,
|
||
int pad_start,
|
||
int program_repeat,
|
||
int video_type,
|
||
int is_dvd,
|
||
int video_stream,
|
||
int audio_stream,
|
||
int want_ac3_audio,
|
||
int output_dolby_as_dvb,
|
||
u_int32 pmt_pid,
|
||
u_int32 pcr_pid,
|
||
u_int32 video_pid,
|
||
int keep_audio,
|
||
u_int32 audio_pid,
|
||
int max,
|
||
int verbose,
|
||
int quiet)
|
||
{
|
||
int err;
|
||
struct program_data prog_data = {0};
|
||
|
||
prog_data.transport_stream_id = 1;
|
||
prog_data.program_number = 1;
|
||
prog_data.pmt_pid = pmt_pid;
|
||
prog_data.pcr_pid = pcr_pid;
|
||
prog_data.video_pid = video_pid;
|
||
prog_data.audio_pid = audio_pid;
|
||
prog_data.video_type = video_type;
|
||
prog_data.output_dolby_as_dvb = output_dolby_as_dvb;
|
||
prog_data.video_stream = video_stream;
|
||
prog_data.want_ac3 = want_ac3_audio;
|
||
prog_data.is_dvd = is_dvd;
|
||
if (want_ac3_audio)
|
||
{
|
||
prog_data.audio_stream = PRIVATE1_AUDIO_STREAM_ID;
|
||
if (is_dvd)
|
||
prog_data.audio_substream = audio_stream;
|
||
else
|
||
prog_data.audio_substream = -1; // use the first we find
|
||
}
|
||
else
|
||
prog_data.audio_stream = audio_stream;
|
||
|
||
// We have one program - we'll make it program 1
|
||
#define PROGRAM_NUMBER 1
|
||
err = build_pidint_list(&prog_data.prog_list);
|
||
if (err) return 1;
|
||
err = append_to_pidint_list(prog_data.prog_list,pmt_pid,PROGRAM_NUMBER);
|
||
if (err)
|
||
{
|
||
free_pidint_list(&prog_data.prog_list);
|
||
return 1;
|
||
}
|
||
prog_data.pmt = build_pmt(PROGRAM_NUMBER,0,pcr_pid);
|
||
if (err)
|
||
{
|
||
free_pidint_list(&prog_data.prog_list);
|
||
return 1;
|
||
}
|
||
|
||
err = _ps_to_ts(ps,output,&prog_data,pad_start,program_repeat,keep_audio,
|
||
max,verbose,quiet);
|
||
if (err)
|
||
{
|
||
free_pidint_list(&prog_data.prog_list);
|
||
free_pmt(&prog_data.pmt);
|
||
return 1;
|
||
}
|
||
|
||
free_pidint_list(&prog_data.prog_list);
|
||
free_pmt(&prog_data.pmt);
|
||
return 0;
|
||
}
|
||
|
||
// Local Variables:
|
||
// tab-width: 8
|
||
// indent-tabs-mode: nil
|
||
// c-basic-offset: 2
|
||
// End:
|
||
// vim: set tabstop=8 shiftwidth=2 expandtab:
|