F5OEO-tstools/ts.c

3963 wiersze
126 KiB
C
Czysty Zwykły widok Historia

/*
* Utilities for working with H.222 Transport Stream packets
*
* ***** 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 <string.h>
#include <ctype.h>
#include <errno.h>
#ifdef _WIN32
#include <io.h>
#else // _WIN32
#include <unistd.h>
#endif // _WIN32
#include "compat.h"
#include "ts_fns.h"
#include "tswrite_fns.h"
#include "misc_fns.h"
#include "printing_fns.h"
#include "pidint_fns.h"
#include "pes_fns.h"
#define DEBUG 0
#define DEBUG_DTS 0
#define DEBUG_WRITE_PACKETS 0
// Should we report reserved bits that are set to the wrong value?
// For the moment, make this a global, since it lets me suppress
// it easily. There should be some sort of command line switch
// to set this to FALSE in utilities that it matters for.
static int report_bad_reserved_bits = FALSE;
// ============================================================
// Suppport for the creation of Transport Streams.
// ============================================================
// Remember the continuity counter value for each/any PID
// (do I really want to have an array of size 8191+1?
// and do I want it static? not if this ever becomes a
// library module...)
static int continuity_counter[0x1fff+1] = {0};
/*
* Return the next value of continuity_counter for the given pid
*/
static inline int next_continuity_count(uint32_t pid)
{
uint32_t next = (continuity_counter[pid] + 1) & 0x0f;
continuity_counter[pid] = next;
return next;
}
/*
* Create a PES header for our data.
*
* - `data_len` is the length of our ES data
* If this is too long to fit into 16 bits, then we will create a header
* with 0 as its length. Note this is only allowed (by the standard) for
* video data.
* - `stream_id` is the elementary stream id to use (see H.222 Table 2-18).
* If the stream id indicates an audio stream (as elucidated by Note 2 in
* that same table), then the data_alignment_indicator flag will be set
* in the PES header - i.e., we assume that the audio frame *starts*
* (has its syncword) at the start of the PES packet payload.
* - `with_PTS` should be TRUE if the PTS value in `pts` should be written
* to the PES header.
* - `with_DTS` should be TRUE if the DTS value in `dts` should be written
* to the PES header. Note that if `with_DTS` is TRUE, then `with_PTS`
* must also be TRUE. If it is not, then the DTS value will be used for
* the PTS.
* - `PES_hdr` is the resultant PES packet header, and
* - `PES_hdr_len` its length (at the moment that's always the same, as
* we're not yet outputting any timing information (PTS/DTS), and so
* can get away with a minimal PES header).
*/
extern void PES_header(uint32_t data_len,
byte stream_id,
int with_PTS,
uint64_t pts,
int with_DTS,
uint64_t dts,
byte *PES_hdr,
int *PES_hdr_len)
{
int extra_len = 0;
if (with_DTS && !with_PTS)
{
with_PTS = TRUE;
pts = dts;
}
// If PTS=DTS then there is no point explictly coding the DTS so junk it
if (with_DTS && pts == dts)
with_DTS = FALSE;
// packet_start_code_prefix
PES_hdr[0] = 0x00;
PES_hdr[1] = 0x00;
PES_hdr[2] = 0x01;
PES_hdr[3] = stream_id;
// PES_packet_length comes next, but we'll actually sort it out
// at the end, when we know what else we've put into our header
// Flags: '10' then PES_scrambling_control .. original_or_copy
// If it appears to be an audio stream, we set the data alignment indicator
// flag, to indicate that the audio data starts with its syncword. For video
// data, we leave the flag unset.
if (IS_AUDIO_STREAM_ID(stream_id))
PES_hdr[6] = 0x84; // just data alignment indicator flag set
else
PES_hdr[6] = 0x80; // no flags set
// Flags: PTS_DTS_flags .. PES_extension_flag
if (with_DTS && with_PTS)
PES_hdr[7] = 0xC0;
else if (with_PTS)
PES_hdr[7] = 0x80;
else
PES_hdr[7] = 0x00; // yet more unset flags (nb: no PTS/DTS info)
// PES_header_data_length
if (with_DTS && with_PTS)
{
PES_hdr[8] = 0x0A;
encode_pts_dts(&(PES_hdr[9]),3,pts);
encode_pts_dts(&(PES_hdr[14]),1,dts);
*PES_hdr_len = 9 + 10;
extra_len = 3 + 10; // 3 bytes after the length field, plus our PTS & DTS
}
else if (with_PTS)
{
PES_hdr[8] = 0x05;
encode_pts_dts(&(PES_hdr[9]),2,pts);
*PES_hdr_len = 9 + 5;
extra_len = 3 + 5; // 3 bytes after the length field, plus our PTS
}
else
{
PES_hdr[8] = 0x00; // 0 means there is no more data
*PES_hdr_len = 9;
extra_len = 3; // just the basic 3 bytes after the length field
}
// So now we can set the length field itself...
if (data_len > 0xFFFF || (data_len + extra_len) > 0xFFFF)
{
// If the length is too great, we just set it "unset"
// @@@ (this should only really be done for TS-wrapped video, so perhaps
// we should complain if this is not video?)
PES_hdr[4] = 0;
PES_hdr[5] = 0;
}
else
{
// The packet length doesn't include the bytes up to and including the
// packet length field, but it *does* include any bytes of the PES header
// after it.
data_len += extra_len;
PES_hdr[4] = (byte) ((data_len & 0xFF00) >> 8);
PES_hdr[5] = (byte) ((data_len & 0x00FF));
}
}
/*
* Write out our TS packet, as composed from its parts.
*
* - `output` is the TS writer context to write with
* - `TS_packet` is the TS packet buffer, already filled to length `TS_hdr_len`
* with TS header information
* - `pes_hdr` is the PES header data, length `pes_hdr_len`, which is
* written out thereafter
* - `data` is then the actual ES data, length `data_len`
*
* When outputting asynchronously, the writer needs to know any timing
* information that is available for the packet. Thus:
*
* - `pid` is the PID for the packet
* - `got_pcr` is TRUE if we have a PCR for the packet, in which case
* - `pcr` is that PCR.
*
* Restrictions
* ============
*
* `TS_hdr_len` + `pes_hdr_len` + `data_len` should equal 188.
*
* `pes_hdr_len` may be 0 if there is no PES header data to be written
* (i.e., this is not the start of the PES packet's data, or the packet
* is not a PES packet).
*
* For real data, `data_len` should never be 0 (the exception is when
* writing NULL packets).
*
* `TS_hdr_len` must never be 0.
*
* Returns 0 if it worked, 1 if something went wrong.
*/
static int write_TS_packet_parts(TS_writer_p output,
byte TS_packet[TS_PACKET_SIZE],
int TS_hdr_len,
byte pes_hdr[],
int pes_hdr_len,
byte data[],
int data_len,
uint32_t pid,
int got_pcr,
uint64_t pcr)
{
int err;
int total_len = TS_hdr_len + pes_hdr_len + data_len;
if (total_len != TS_PACKET_SIZE)
{
fprint_err("### TS packet length is %d, not 188 (composed of %d + %d + %d)\n",
total_len,TS_hdr_len,pes_hdr_len,data_len);
return 1;
}
// We want to make a single write, so we need to assemble the package
// into our packet buffer
if (pes_hdr_len > 0)
memcpy(&(TS_packet[TS_hdr_len]),pes_hdr,pes_hdr_len);
if (data_len > 0)
memcpy(&(TS_packet[TS_hdr_len+pes_hdr_len]),data,data_len);
err = tswrite_write(output,TS_packet,pid,got_pcr,pcr);
if (err)
{
fprint_err("### Error writing out TS packet: %s\n",strerror(errno));
return 1;
}
return 0;
}
/*
* Write our data as a (series of) Transport Stream PES packets.
*
* - `output` is the TS writer context we're using to write our TS data out
* - `pes_hdr` is NULL if the data to be written out is already PES, and is
* otherwise a PES header constructed with PES_header()
* - `pes_hdr_len` is the length of said PES header (or 0)
* - `data` is (the remainder of) our ES data (e.g., a NAL unit) or PES packet
* - `data_len` is its length
* - `start` is true if this is the first time we've called this function
* to output (part of) this data (in other words, this should be true
* when someone else calls this function, and false when the function
* calls itself). This is expected to be TRUE if a PES header is given...
* - `set_pusi` is TRUE if we should set the payload unit start indicator
* (generally true if `start` is TRUE). This is ignored if `start` is FALSE.
* - `pid` is the PID to use for this TS packet
* - `stream_id` is the PES packet stream id to use (e.g.,
* DEFAULT_VIDEO_STREAM_ID)
* - `got_PCR` is TRUE if we have a `PCR` value (this is only
* relevant when `start` is also TRUE).
* - `PCR_base` and `PCR_extn` then encode that PCR value (ditto)
*
* Returns 0 if it worked, 1 if something went wrong.
*/
static int write_some_TS_PES_packet(TS_writer_p output,
byte *pes_hdr,
int pes_hdr_len,
byte *data,
uint32_t data_len,
int start,
int set_pusi,
uint32_t pid,
byte stream_id,
int got_PCR,
uint64_t PCR_base,
uint32_t PCR_extn)
{
#define DEBUG_THIS 0
byte TS_packet[TS_PACKET_SIZE];
int TS_hdr_len;
uint32_t controls = 0;
uint32_t pes_data_len = 0;
int err;
int got_adaptation_field = FALSE;
uint32_t space_left; // Bytes available for payload, after the TS header
if (pid < 0x0010 || pid > 0x1ffe)
{
fprint_err("### PID %03x is outside legal program stream range",pid);
return 1;
}
// If this is the first time we've "seen" this data, and it is not
// already wrapped up as PES, then we need to remember its PES header
// in our calculations
if (pes_hdr)
pes_data_len = data_len + pes_hdr_len;
else
{
pes_hdr_len = 0;
pes_data_len = data_len;
}
#if DEBUG_THIS
if (start)
print_msg("TS_PES ");
else
print_msg(" ");
print_data(TRUE,"",data,data_len,20);
#endif
// We always start with a sync_byte to identify this as a
// Transport Stream packet
TS_packet[0] = 0x47;
// Should we set the "payload_unit_start_indicator" bit?
// Only for the first packet containing our data.
if (start && set_pusi)
TS_packet[1] = (byte)(0x40 | ((pid & 0x1f00) >> 8));
else
TS_packet[1] = (byte)(0x00 | ((pid & 0x1f00) >> 8));
TS_packet[2] = (byte)(pid & 0xff);
// Sort out the adaptation field, if any
if (start && got_PCR)
{
// This is the start of the data, and we have a PCR value to output,
// so we know we have an adaptation field
controls = 0x30; // adaptation field control = '11' = both
TS_packet[3] = (byte) (controls | next_continuity_count(pid));
// And construct said adaptation field...
TS_packet[4] = 7; // initial adaptation field length
TS_packet[5] = 0x10; // flag bits 0001 0000 -> got PCR
TS_packet[6] = (byte) (PCR_base >> 25);
TS_packet[7] = (byte) ((PCR_base >> 17) & 0xFF);
TS_packet[8] = (byte) ((PCR_base >> 9) & 0xFF);
TS_packet[9] = (byte) ((PCR_base >> 1) & 0xFF);
TS_packet[10] = (byte) (((PCR_base & 0x1) << 7) | 0x7E | (PCR_extn >> 8));
TS_packet[11] = (byte) (PCR_extn >> 1);
TS_hdr_len = 12;
space_left = MAX_TS_PAYLOAD_SIZE - 8;
got_adaptation_field = TRUE;
#if DEBUG_THIS
fprint_msg(" start & got_PCR -> with adaptation field, space left %d, TS_packet[4] %d\n",space_left,TS_packet[4]);
#endif
}
else if (pes_data_len < MAX_TS_PAYLOAD_SIZE)
{
// Our data is less than 184 bytes long, which means it won't fill
// the payload, so we need to pad it out with an (empty) adaptation
// field, padded to the appropriate length
controls = 0x30; // adaptation field control = '11' = both
TS_packet[3] = (byte)(controls | next_continuity_count(pid));
if (pes_data_len == (MAX_TS_PAYLOAD_SIZE - 1)) // i.e., 183
{
TS_packet[4] = 0; // just the length used to pad
TS_hdr_len = 5;
space_left = MAX_TS_PAYLOAD_SIZE - 1;
}
else
{
TS_packet[4] = 1; // initial length
TS_packet[5] = 0; // unset flag bits
TS_hdr_len = 6;
space_left = MAX_TS_PAYLOAD_SIZE - 2; // i.e., 182
}
got_adaptation_field = TRUE;
#if DEBUG_THIS
fprint_msg(" <184, pad with empty adaptation field, space left %d, TS_packet[4] %d\n",space_left,TS_packet[4]);
#endif
}
else
{
// The data either fits exactly, or is too long and will need to be
// continued in further TS packets. In either case, we don't need an
// adaptation field
controls = 0x10; // adaptation field control = '01' = payload only
TS_packet[3] = (byte)(controls | next_continuity_count(pid));
TS_hdr_len = 4;
space_left = MAX_TS_PAYLOAD_SIZE;
#if DEBUG_THIS
fprint_msg(" >=184, space left %d\n",space_left);
#endif
}
if (got_adaptation_field)
{
// Do we need to add stuffing bytes to allow for short PES data?
if (pes_data_len < space_left)
{
int ii;
int padlen = space_left - pes_data_len;
for (ii = 0; ii < padlen; ii++)
TS_packet[TS_hdr_len+ii] = 0xFF;
TS_packet[4] += padlen;
TS_hdr_len += padlen;
space_left -= padlen;
#if DEBUG_THIS
fprint_msg(" stuffing %d, space left %d, TS_packet[4] %d\n",padlen,space_left,TS_packet[4]);
#endif
}
}
if (pes_data_len == space_left)
{
#if DEBUG_THIS
print_msg(" == fits exactly\n");
#endif
// Our data fits exactly
err = write_TS_packet_parts(output,
TS_packet,TS_hdr_len,
pes_hdr,pes_hdr_len,
data,data_len,
pid,got_PCR,(PCR_base*300)+PCR_extn);
if (err) return err;
}
else
{
// We need to look at more than one packet...
// Write out the first 184-pes_hdr_len bytes
int increment = space_left - pes_hdr_len;
err = write_TS_packet_parts(output,
TS_packet,TS_hdr_len,
pes_hdr,pes_hdr_len,
data,increment,
pid,got_PCR,(PCR_base*300)+PCR_extn);
if (err) return err;
#if DEBUG_THIS
fprint_msg(" == wrote %d, leaving %d\n",increment,data_len-increment);
#endif
// Leaving data_len - (184-pes_hdr_len) bytes still to go
// Is recursion going to be efficient enough?
if ((data_len - increment) > 0)
{
err = write_some_TS_PES_packet(output,NULL,0,
&(data[increment]),data_len-increment,
FALSE,FALSE,pid,stream_id,FALSE,0,0);
if (err) return err;
}
}
return 0;
}
/*
* Write out our ES data as a Transport Stream PES packet.
*
* - `output` is the TS output context returned by `tswrite_open`
* - `data` is our ES data (e.g., a NAL unit)
* - `data_len` is its length
* - `pid` is the PID to use for this TS packet
* - `stream_id` is the PES packet stream id to use (e.g.,
* DEFAULT_VIDEO_STREAM_ID)
*
* If the data to be written is more than 65535 bytes long (i.e., the
* length will not fit into 2 bytes), then the PES packet written will
* have PES_packet_length set to zero (see ISO/IEC 13818-1 (H.222.0)
* 2.4.3.7, Semantic definitions of fields in PES packet). This is only
* allowed for video streams.
*
* Returns 0 if it worked, 1 if something went wrong.
*/
extern int write_ES_as_TS_PES_packet(TS_writer_p output,
byte data[],
uint32_t data_len,
uint32_t pid,
byte stream_id)
{
byte pes_hdr[TS_PACKET_SIZE]; // better be more than long enough!
int pes_hdr_len = 0;
#if DEBUG_WRITE_PACKETS
fprint_msg("|| ES as TS/PES, pid %x (%d)\n",pid,pid);
#endif
PES_header(data_len,stream_id,FALSE,0,FALSE,0,pes_hdr,&pes_hdr_len);
return write_some_TS_PES_packet(output,pes_hdr,pes_hdr_len,
data,data_len,TRUE,TRUE,pid,stream_id,
FALSE,0,0);
}
/*
* Write out our ES data as a Transport Stream PES packet, with PTS and/or DTS
* if we've got them, and some attempt to write out a sensible PCR.
*
* - `output` is the TS output context returned by `tswrite_open`
* - `data` is our ES data (e.g., a NAL unit)
* - `data_len` is its length
* - `pid` is the PID to use for this TS packet
* - `stream_id` is the PES packet stream id to use (e.g.,
* DEFAULT_VIDEO_STREAM_ID)
* - `got_pts` is TRUE if we have a PTS value, in which case
* - `pts` is said PTS value
* - `got_dts` is TRUE if we also have DTS, in which case
* - `dts` is said DTS value.
*
* We also want to try to write out a sensible PCR value.
*
* PTS can go up as well as down (it is the time at which the next frame
* should be presented to the user, but frames do not necessarily occur
* in presentation order).
*
* DTS only goes up, since it is the time that the frame should be decoded.
*
* Thus, if we have it, the DTS is sensible to use for the PCR...
*
* If the data to be written is more than 65535 bytes long (i.e., the
* length will not fit into 2 bytes), then the PES packet written will
* have PES_packet_length set to zero (see ISO/IEC 13818-1 (H.222.0)
* 2.4.3.7, Semantic definitions of fields in PES packet). This is only
* allowed for video streams.
*
* Returns 0 if it worked, 1 if something went wrong.
*/
extern int write_ES_as_TS_PES_packet_with_pts_dts(TS_writer_p output,
byte data[],
uint32_t data_len,
uint32_t pid,
byte stream_id,
int got_pts,
uint64_t pts,
int got_dts,
uint64_t dts)
{
byte pes_hdr[TS_PACKET_SIZE]; // better be more than long enough!
int pes_hdr_len = 0;
#if DEBUG_WRITE_PACKETS
fprint_msg("|| ES as TS/PES with PTS/DTS, pid %x (%d)\n",pid,pid);
#endif
PES_header(data_len,stream_id,got_pts,pts,got_dts,dts,pes_hdr,&pes_hdr_len);
return write_some_TS_PES_packet(output,pes_hdr,pes_hdr_len,
data,data_len,TRUE,TRUE,pid,stream_id,
got_dts,dts,0);
}
/*
* Write out our ES data as a Transport Stream PES packet, with PCR.
*
* - `output` is the TS output context returned by `tswrite_open`
* - `data` is our ES data (e.g., a NAL unit)
* - `data_len` is its length
* - `pid` is the PID to use for this TS packet
* - `stream_id` is the PES packet stream id to use (e.g.,
* DEFAULT_VIDEO_STREAM_ID)
* - `pcr_base` and `pcr_extn` encode the PCR value.
*
* If the data to be written is more than 65535 bytes long (i.e., the
* length will not fit into 2 bytes), then the PES packet written will
* have PES_packet_length set to zero (see ISO/IEC 13818-1 (H.222.0)
* 2.4.3.7, Semantic definitions of fields in PES packet). This is only
* allowed for video streams.
*
* Returns 0 if it worked, 1 if something went wrong.
*/
extern int write_ES_as_TS_PES_packet_with_pcr(TS_writer_p output,
byte data[],
uint32_t data_len,
uint32_t pid,
byte stream_id,
uint64_t pcr_base,
uint32_t pcr_extn)
{
byte pes_hdr[TS_PACKET_SIZE]; // better be more than long enough!
int pes_hdr_len = 0;
#if DEBUG_WRITE_PACKETS
fprint_msg("|| ES as TS/PES with PCR, pid %x (%d)\n",pid,pid);
#endif
PES_header(data_len,stream_id,FALSE,0,FALSE,0,pes_hdr,&pes_hdr_len);
return write_some_TS_PES_packet(output,pes_hdr,pes_hdr_len,
data,data_len,TRUE,TRUE,pid,stream_id,
TRUE,pcr_base,pcr_extn);
}
/*
* Write out a PES packet's data as a Transport Stream PES packet.
*
* - `output` is the TS output context returned by `tswrite_open`
* - `data` is our PES data (e.g., a program stream video data packet)
* - `data_len` is its length
* - `pid` is the PID to use for this TS packet
* - `stream_id` is the PES packet stream id to use (e.g.,
* DEFAULT_VIDEO_STREAM_ID)
* - `got_pcr` is TRUE if we have values for the PCR in this packet,
* in which case `pcr_base` and `pcr_extn` are the parts of the PCR.
*
* If the data to be written is more than 65535 bytes long (i.e., the
* length will not fit into 2 bytes), then the PES packet written will
* have PES_packet_length set to zero (see ISO/IEC 13818-1 (H.222.0)
* 2.4.3.7, Semantic definitions of fields in PES packet). This is only
* allowed for video streams.
*
* Returns 0 if it worked, 1 if something went wrong.
*/
extern int write_PES_as_TS_PES_packet(TS_writer_p output,
byte data[],
uint32_t data_len,
uint32_t pid,
byte stream_id,
int got_pcr,
uint64_t pcr_base,
uint32_t pcr_extn)
{
// Should we write MPEG-1 packet data out as ES (wrapped in MPEG-2 PES in TS),
// rather than writing the packets out directly in TS? (that latter doesn't
// work very well, as TS is not defined to work for MPEG-1 style packets).
#define MPEG1_AS_ES 1
#if DEBUG_WRITE_PACKETS
fprint_msg("|| PES as TS/PES, pid %x (%d)\n",pid,pid);
#endif
#if 0 // XXX
print_data(TRUE,"TS_PES",data,data_len,20);
print_end_of_data(" ",data,data_len,20);
#endif // XXX
#if MPEG1_AS_ES
if (IS_H222_PES(data))
{
#endif // MPEG1_AS_ES
return write_some_TS_PES_packet(output,NULL,0,
data,data_len,TRUE,TRUE,pid,stream_id,
got_pcr,pcr_base,pcr_extn);
#if MPEG1_AS_ES
}
else
{
// Write MPEG-1 data out as ES in (MPEG-2) PES
int got_pts, got_dts;
uint64_t pts, dts;
int offset = calc_mpeg1_pes_offset(data,data_len);
int err = find_PTS_DTS_in_PES(data,data_len,
&got_pts,&pts,&got_dts,&dts);
if (err) // Just try to carry on...
{
got_pts = FALSE;
got_dts = FALSE;
}
return write_ES_as_TS_PES_packet_with_pts_dts(output,
data + offset,
data_len - offset,
pid,
stream_id,
got_pts,pts,
got_dts,dts);
}
#endif // MPEG1_AS_ES
}
/*
* Construct a Transport Stream packet header for PAT or PMT data.
*
* The data is required to fit within a single TS packet - i.e., to be
* 183 bytes or less.
*
* - `pid` is the PID to use for this packet.
* - `data_len` is the length of the PAT or PMT data
* - `TS_hdr` is a byte array into (the start of) which to write the
* TS header data.
* - `TS_hdr_len` returns how much data we've written therein.
*
* Returns 0 if it worked, 1 if something went wrong.
*/
static int TS_program_packet_hdr(uint32_t pid,
int data_len,
byte TS_hdr[TS_PACKET_SIZE],
int *TS_hdr_len)
{
uint32_t controls = 0;
int pointer, ii;
if (data_len > (TS_PACKET_SIZE - 5)) // i.e., 183
{
fprint_err("### PMT/PAT data for PID %02x is too long (%d > 183)",
pid,data_len);
return 1;
}
// We always start with a sync_byte to identify this as a
// Transport Stream packet
TS_hdr[0] = 0x47;
// We want the "payload_unit_start_indicator" bit set
TS_hdr[1] = (byte)(0x40 | ((pid & 0x1f00) >> 8));
TS_hdr[2] = (byte)(pid & 0xff);
// We don't need any adaptation field controls
controls = 0x10;
TS_hdr[3] = (byte)(controls | next_continuity_count(pid));
// Next comes a pointer to the actual payload data
// (i.e., 0 if the data is 183 bytes long)
// followed by pad bytes until we *get* to the data
pointer = (byte)(TS_PACKET_SIZE - 5 - data_len);
TS_hdr[4] = pointer;
for (ii=0; ii<pointer; ii++)
TS_hdr[5+ii] = 0xff;
*TS_hdr_len = 5+pointer;
return 0;
}
/*
* Write out a Transport Stream PAT and PMT, for a single stream.
*
* - `output` is the TS output context returned by `tswrite_open`
* - `transport_stream_id` is the id for this particular transport stream.
* - `program_number` is the program number to use for the PID.
* - `pmt_pid` is the PID for the PMT.
* - `pid` is the PID of the stream to enter in the tables. This is also
* used as the PCR PID.
* - `stream_type` is the type of stream. MPEG-2 video is 0x01,
* MPEG-4/AVC (H.264) is 0x1b.
*
* Since we're outputting a TS representing a single ES, we only need to
* support a single program stream, containing a single PID.
*
* Returns 0 if it worked, 1 if something went wrong.
*/
extern int write_TS_program_data(TS_writer_p output,
uint32_t transport_stream_id,
uint32_t program_number,
uint32_t pmt_pid,
uint32_t pid,
byte stream_type)
{
int err;
pidint_list_p prog_list;
pmt_p pmt;
// We have a single program stream
err = build_pidint_list(&prog_list);
if (err) return 1;
err = append_to_pidint_list(prog_list,pmt_pid,program_number);
if (err)
{
free_pidint_list(&prog_list);
return 1;
}
pmt = build_pmt((uint16_t)program_number,0,pid); // Use program stream PID as PCR PID
if (pmt == NULL)
{
free_pidint_list(&prog_list);
return 1;
}
err = add_stream_to_pmt(pmt,pid,stream_type,0,NULL);
if (err)
{
free_pidint_list(&prog_list);
free_pmt(&pmt);
return 1;
}
err = write_pat_and_pmt(output,transport_stream_id,prog_list,pmt_pid,pmt);
if (err)
{
free_pidint_list(&prog_list);
free_pmt(&pmt);
return 1;
}
free_pidint_list(&prog_list);
free_pmt(&pmt);
return 0;
}
/*
* Write out a Transport Stream PAT and PMT, for multiple streams.
*
* - `output` is the TS output context returned by `tswrite_open`
* - `transport_stream_id` is the id for this particular transport stream.
* - `program_number` is the program number to use for the PMT PID.
* - `pmt_pid` is the PID for the PMT.
* - `pcr_pid` is the PID that contains the PCR.
* - `num_progs` is how many program streams are to be defined.
* - `prog_pid` is an array of audio/video PIDs
* - `prog_type` is an array of the corresponding stream types
*
* Note that if `num_progs` is 0, `pcr_pid` is ignored.
*
* Returns 0 if it worked, 1 if something went wrong.
*/
extern int write_TS_program_data2(TS_writer_p output,
uint32_t transport_stream_id,
uint32_t program_number,
uint32_t pmt_pid,
uint32_t pcr_pid,
int num_progs,
uint32_t prog_pid[],
byte prog_type[])
{
int err;
int ii;
pidint_list_p prog_list;
pmt_p pmt;
// We have a single program stream
err = build_pidint_list(&prog_list);
if (err) return 1;
err = append_to_pidint_list(prog_list,pmt_pid,program_number);
if (err)
{
free_pidint_list(&prog_list);
return 1;
}
pmt = build_pmt((uint16_t)program_number,0,pcr_pid);
if (pmt == NULL)
{
free_pidint_list(&prog_list);
return 1;
}
for (ii=0; ii<num_progs; ii++)
{
err = add_stream_to_pmt(pmt,prog_pid[ii],prog_type[ii],0,NULL);
if (err)
{
free_pidint_list(&prog_list);
free_pmt(&pmt);
return 1;
}
}
err = write_pat_and_pmt(output,transport_stream_id,prog_list,pmt_pid,pmt);
if (err)
{
free_pidint_list(&prog_list);
free_pmt(&pmt);
return 1;
}
free_pidint_list(&prog_list);
free_pmt(&pmt);
return 0;
}
/*
* Write out a Transport Stream PAT.
*
* - `output` is the TS output context returned by `tswrite_open`
* - `transport_stream_id` is the id for this particular transport stream.
* - `prog_list` is a PIDINT list of program number / PID pairs.
*
* Returns 0 if it worked, 1 if something went wrong.
*/
extern int write_pat(TS_writer_p output,
uint32_t transport_stream_id,
pidint_list_p prog_list)
{
int ii;
byte data[1021+3];
byte TS_packet[TS_PACKET_SIZE];
int TS_hdr_len;
int err;
int section_length;
int offset, data_length;
uint32_t crc32;
#if DEBUG_WRITE_PACKETS
print_msg("|| PAT pid 0\n");
#endif
section_length = 9 + prog_list->length * 4;
if (section_length > 1021)
{
print_err("### PAT data is too long - will not fit in 1021 bytes\n");
// TODO: Ideally, would be to stderr
report_pidint_list(prog_list,"Program list","Program",FALSE);
return 1;
}
data[0] = 0x00;
// The section length is fixed because our data is fixed
data[1] = (byte) (0xb0 | ((section_length & 0x0F00) >> 8));
data[2] = (byte) (section_length & 0x0FF);
data[3] = (byte) ((transport_stream_id & 0xFF00) >> 8);
data[4] = (byte) (transport_stream_id & 0x00FF);
// For simplicity, we'll have a version_id of 0
data[5] = 0xc1;
// First section of the PAT has section number 0, and there is only
// that section
data[6] = 0x00;
data[7] = 0x00;
offset = 8;
for (ii = 0; ii < prog_list->length; ii++)
{
data[offset+0] = (byte) ((prog_list->number[ii] & 0xFF00) >> 8);
data[offset+1] = (byte) (prog_list->number[ii] & 0x00FF);
data[offset+2] = (byte) (0xE0 | ((prog_list->pid[ii] & 0x1F00) >> 8));
data[offset+3] = (byte) (prog_list->pid[ii] & 0x00FF);
offset += 4;
}
crc32 = crc32_block(0xffffffff,data,offset);
data[12] = (byte) ((crc32 & 0xff000000) >> 24);
data[13] = (byte) ((crc32 & 0x00ff0000) >> 16);
data[14] = (byte) ((crc32 & 0x0000ff00) >> 8);
data[15] = (byte) (crc32 & 0x000000ff);
data_length = offset+4;
#if 1
if (data_length != section_length + 3)
{
fprint_err("### PAT length %d, section length+3 %d\n",
data_length,section_length+3);
return 1;
}
#endif
crc32 = crc32_block(0xffffffff,data,data_length);
if (crc32 != 0)
{
print_err("### PAT CRC does not self-cancel\n");
return 1;
}
err = TS_program_packet_hdr(0x00,data_length,TS_packet,&TS_hdr_len);
if (err)
{
print_err("### Error constructing PAT packet header\n");
return 1;
}
err = write_TS_packet_parts(output,TS_packet,TS_hdr_len,NULL,0,
data,data_length,0x00,FALSE,0);
if (err)
{
print_err("### Error writing PAT\n");
return 1;
}
return 0;
}
/*
* Write out a Transport Stream PMT, given a PMT datastructure
*
* - `output` is the TS output context returned by `tswrite_open`
* - `pmt_pid` is the PID for the PMT.
* - 'pmt' is the datastructure containing the PMT information
*
* Returns 0 if it worked, 1 if something went wrong.
*/
extern int write_pmt(TS_writer_p output,
uint32_t pmt_pid,
pmt_p pmt)
{
int ii;
byte data[3+1021]; // maximum PMT size
byte TS_packet[TS_PACKET_SIZE];
int TS_hdr_len;
int err;
int section_length;
int offset, data_length;
uint32_t crc32;
#if DEBUG_WRITE_PACKETS
fprint_msg("|| PMT pid %x (%d)\n",pmt_pid,pmt_pid);
#endif
if (pmt_pid < 0x0010 || pmt_pid > 0x1ffe)
{
fprint_err("### PMT PID %03x is outside legal range\n",pmt_pid);
return 1;
}
if (pid_in_pmt(pmt,pmt_pid))
{
fprint_err("### PMT PID and program %d PID are both %03x\n",
pid_index_in_pmt(pmt,pmt_pid),pmt_pid);
return 1;
}
// Much of the PMT should look very familiar, after the PAT
// Calculate the length of the section
section_length = 13 + pmt->program_info_length;
for (ii = 0; ii < pmt->num_streams; ii++)
section_length += 5 + pmt->streams[ii].ES_info_length;
if (section_length > 1021)
{
print_err("### PMT data is too long - will not fit in 1021 bytes\n");
report_pmt(FALSE," ",pmt);
return 1;
}
data[0] = 0x02;
data[1] = (byte) (0xb0 | ((section_length & 0x0F00) >> 8));
data[2] = (byte) (section_length & 0x0FF);
data[3] = (byte) ((pmt->program_number & 0xFF00) >> 8);
data[4] = (byte) (pmt->program_number & 0x00FF);
data[5] = 0xc1;
data[6] = 0x00; // section number
data[7] = 0x00; // last section number
data[8] = (byte) (0xE0 | ((pmt->PCR_pid & 0x1F00) >> 8));
data[9] = (byte) (pmt->PCR_pid & 0x00FF);
data[10] = 0xF0;
data[11] = (byte)pmt->program_info_length;
if (pmt->program_info_length > 0)
memcpy(data+12,pmt->program_info,pmt->program_info_length);
offset = 12 + pmt->program_info_length;
for (ii=0; ii < pmt->num_streams; ii++)
{
uint32_t pid = pmt->streams[ii].elementary_PID;
uint16_t len = pmt->streams[ii].ES_info_length;
data[offset+0] = pmt->streams[ii].stream_type;
data[offset+1] = (byte) (0xE0 | ((pid & 0x1F00) >> 8));
data[offset+2] = (byte) (pid & 0x00FF);
data[offset+3] = ((len & 0xFF00) >> 8) | 0xF0;
data[offset+4] = len & 0x00FF;
memcpy(data+offset+5,pmt->streams[ii].ES_info,len);
offset += 5 + len;
}
crc32 = crc32_block(0xffffffff,data,offset);
data[offset+0] = (byte) ((crc32 & 0xff000000) >> 24);
data[offset+1] = (byte) ((crc32 & 0x00ff0000) >> 16);
data[offset+2] = (byte) ((crc32 & 0x0000ff00) >> 8);
data[offset+3] = (byte) (crc32 & 0x000000ff);
data_length = offset + 4;
#if 1
if (data_length != section_length + 3)
{
fprint_err("### PMT length %d, section length+3 %d\n",
data_length,section_length+3);
return 1;
}
#endif
crc32 = crc32_block(0xffffffff,data,data_length);
if (crc32 != 0)
{
print_err("### PMT CRC does not self-cancel\n");
return 1;
}
err = TS_program_packet_hdr(pmt_pid,data_length,TS_packet,&TS_hdr_len);
if (err)
{
print_err("### Error constructing PMT packet header\n");
return 1;
}
err = write_TS_packet_parts(output,TS_packet,TS_hdr_len,NULL,0,
data,data_length,0x02,FALSE,0);
if (err)
{
print_err("### Error writing PMT\n");
return 1;
}
return 0;
}
/*
* Write out a Transport Stream PAT and PMT, given the appropriate
* datastructures
*
* - `output` is the TS output context returned by `tswrite_open`
* - `transport_stream_id` is the id for this particular transport stream.
* - `prog_list` is a PIDINT list of program number / PID pairs.
* - `pmt_pid` is the PID for the PMT.
* - 'pmt' is the datastructure containing the PMT information
*
* Returns 0 if it worked, 1 if something went wrong.
*/
extern int write_pat_and_pmt(TS_writer_p output,
uint32_t transport_stream_id,
pidint_list_p prog_list,
uint32_t pmt_pid,
pmt_p pmt)
{
int err;
err = write_pat(output,transport_stream_id,prog_list);
if (err) return 1;
err = write_pmt(output,pmt_pid,pmt);
if (err) return 1;
return 0;
}
/*
* Write out a Transport Stream PAT, for a single program.
*
* - `output` is the TS output context returned by `tswrite_open`
* - `transport_stream_id` is the id for this particular transport stream.
* - `program_number` is the program number to use for the PID.
* - `pmt_pid` is the PID for the PMT.
*
* Returns 0 if it worked, 1 if something went wrong.
*/
extern int write_single_program_pat(TS_writer_p output,
uint32_t transport_stream_id,
uint32_t program_number,
uint32_t pmt_pid)
{
int err;
pidint_list_p prog_list;
err = build_pidint_list(&prog_list);
if (err) return 1;
err = append_to_pidint_list(prog_list,pmt_pid,program_number);
if (err)
{
free_pidint_list(&prog_list);
return 1;
}
err = write_pat(output,transport_stream_id,prog_list);
if (err)
{
free_pidint_list(&prog_list);
return 1;
}
free_pidint_list(&prog_list);
return 0;
}
/*
* Write out a Transport Stream Null packet.
*
* - `output` is the TS output context returned by `tswrite_open`
*
* Returns 0 if it worked, 1 if something went wrong.
*/
extern int write_TS_null_packet(TS_writer_p output)
{
byte TS_packet[TS_PACKET_SIZE];
int err, ii;
#if DEBUG_WRITE_PACKETS
print_msg("|| Null packet\n");
#endif
TS_packet[0] = 0x47;
TS_packet[1] = 0x1F; // PID is 0x1FFF
TS_packet[2] = 0xFF;
TS_packet[3] = 0x20; // payload only
for (ii=4; ii<TS_PACKET_SIZE; ii++)
TS_packet[ii] = 0xFF;
err = write_TS_packet_parts(output,TS_packet,TS_PACKET_SIZE,NULL,0,NULL,0,
0x1FF,FALSE,0);
if (err)
{
print_err("### Error writing null TS packet\n");
return 1;
}
return 0;
}
// ============================================================
// Reading a Transport Stream
// ============================================================
static uint64_t TWENTY_SEVEN_MHZ = 27000000;
// ------------------------------------------------------------
// File handling
// ------------------------------------------------------------
/*
* Build a TS packet reader, including its read-ahead buffer
*
* - `file` is the file that the TS packets will be read from.
* It is assumed that its read position is at its start.
*
* Returns 0 if all goes well, 1 if something goes wrong.
*/
static int new_TS_reader(TS_reader_p *tsreader)
{
TS_reader_p new = malloc(SIZEOF_TS_READER);
if (new == NULL)
{
print_err("### Unable to allocate TS read-ahead buffer\n");
return 1;
}
memset(new, '\0', SIZEOF_TS_READER);
new->file = -1;
*tsreader = new;
return 0;
}
// ------------------------------------------------------------
// File handling
// ------------------------------------------------------------
/*
* Build a TS packet reader, including its read-ahead buffer
*
* - `file` is the file that the TS packets will be read from.
* It is assumed that its read position is at its start.
*
* Returns 0 if all goes well, 1 if something goes wrong.
*/
extern int build_TS_reader(int file,
TS_reader_p *tsreader)
{
TS_reader_p new;
int err = new_TS_reader(&new);
if (err) return 1;
new->file = file;
*tsreader = new;
return 0;
}
/*
* Build a TS packet reader using the given functions as read() and seek().
*
* Returns 0 on success, 1 on failure.
*/
extern int build_TS_reader_with_fns(void *handle,
int (*read_fn)(void *, byte *, size_t),
int (*seek_fn)(void *, offset_t),
TS_reader_p *tsreader)
{
TS_reader_p new;
int err = new_TS_reader(&new);
if (err) return 1;
new->handle = handle;
new->read_fn = read_fn;
new->seek_fn = seek_fn;
*tsreader = new;
return 0;
}
/*
* Open a file to read TS packets from.
*
* If `filename` is NULL, then the input will be taken from standard input.
*
* Returns 0 if all goes well, 1 if something goes wrong.
*/
extern int open_file_for_TS_read(char *filename,
TS_reader_p *tsreader)
{
int err;
int file;
if (filename == NULL)
file = STDIN_FILENO;
else
{
file = open_binary_file(filename,FALSE);
if (file == -1)
return 1;
}
err = build_TS_reader(file,tsreader);
if (err)
{
(void) close_file(file);
return 1;
}
return 0;
}
/*
* Free a TS packet read-ahead buffer
*
* Sets `buffer` to NULL.
*/
extern void free_TS_reader(TS_reader_p *tsreader)
{
if (*tsreader != NULL)
{
if ((*tsreader)->pcrbuf != NULL)
free((*tsreader)->pcrbuf);
(*tsreader)->file = -1;
free(*tsreader);
*tsreader = NULL;
}
}
/*
* Free a TS packet read-ahead buffer and close the referenced file
* (if it is not standard input).
*
* Sets `buffer` to NULL, whether the file close succeeds or not.
*
* Returns 0 if all goes well, 1 if something goes wrong.
*/
extern int close_TS_reader(TS_reader_p *tsreader)
{
int err = 0;
if (*tsreader == NULL)
return 0;
if ((*tsreader)->file != STDIN_FILENO && (*tsreader)->file != -1)
err = close_file((*tsreader)->file);
free_TS_reader(tsreader);
return err;
}
/*
* Seek to a given offset in the TS reader's file
*
* (This should be used in preference to just seeking on the "bare" file
* since it also unsets the read-ahead buffer. However, it is still just
* a wrapper around `seek_file`.)
*
* It is assumed (but not checked) that the seek will end up at an appropriate
* offset for reading a TS packet - i.e., presumably some multiple of
* TS_PACKET_SIZE.
*
* Returns 0 if all goes well, 1 if something goes wrong
*/
extern int seek_using_TS_reader(TS_reader_p tsreader,
offset_t posn)
{
tsreader->read_ahead_ptr = NULL;
tsreader->read_ahead_end = NULL;
tsreader->posn = posn;
if (tsreader->seek_fn)
{
return tsreader->seek_fn(tsreader->handle, posn);
}
else
{
return seek_file(tsreader->file,posn);
}
}
/*
* Read the next several TS packets, possibly not from the start
*
* - `tsreader` is the TS packet reading context
* - `start_len` is the number of bytes of the first packet we've already
* got in hand - normally 0.
* - `packet` is (a pointer to) the resultant TS packet.
*
* This is a pointer into the reader's read-ahead buffer, and so should not
* be freed. Note that this means that it may not persist after another call
* of this function (and will not persist after a call of
* `free_TS_reader`).
*
* Returns 0 if all goes well, EOF if end of file was read, or 1 if some
* other error occurred (in which case it will already have output a message
* on stderr about the problem).
*/
static int read_next_TS_packets(TS_reader_p tsreader,
int start_len,
byte *packet[TS_PACKET_SIZE])
{
#ifdef _WIN32
int total = start_len;
int length;
#else
ssize_t total = start_len;
ssize_t length;
#endif
// If we exit with an error make sure we don't return anything valid here!
*packet = NULL;
if (tsreader->read_ahead_ptr == tsreader->read_ahead_end)
{
// Try to allow for partial reads
while (total < TS_READ_AHEAD_BYTES)
{
if (tsreader->read_fn)
length = tsreader->read_fn(tsreader->handle,
&(tsreader->read_ahead[total]),
TS_READ_AHEAD_BYTES-total);
else
length = read(tsreader->file,
&(tsreader->read_ahead[total]),
TS_READ_AHEAD_BYTES - total);
if (length == 0) // EOF - no more data to read
break;
else if (length == -1)
{
fprint_err("### Error reading TS packets: %s\n",strerror(errno));
return 1;
}
total += length;
}
// If we didn't manage to read anything at all, then indicate EOF this
// time - we assume that if we actually read to the EOF but got some data,
// we'll "hit" EOF again next time we try to read.
if (total == 0)
return EOF;
if (total % TS_PACKET_SIZE != 0)
{
fprint_err("!!! %d byte%s ignored at end of file - not enough"
" to make a TS packet\n",
(int)(total % TS_PACKET_SIZE),(total % TS_PACKET_SIZE == 1?"":"s"));
// Retain whatever full packets we *do* have
total = total - (total % TS_PACKET_SIZE);
if (total == 0)
return EOF;
}
tsreader->read_ahead_ptr = tsreader->read_ahead;
tsreader->read_ahead_end = tsreader->read_ahead + total;
}
*packet = tsreader->read_ahead_ptr;
tsreader->read_ahead_ptr += TS_PACKET_SIZE; // ready for next time
tsreader->posn += TS_PACKET_SIZE; // ditto
return 0;
}
/*
* Read the (rest of the) first TS packet, given its first four bytes
*
* This is intended for use after inspecting the first four bytes of the
* input file, to determine if the file is TS or PS.
*
* - `tsreader` is the TS packet reading context
* - `start` is the first four bytes of the file
* - `packet` is (a pointer to) the resultant TS packet.
*
* This is a pointer into the reader's read-ahead buffer, and so should not
* be freed. Note that this means that it may not persist after another call
* of this function (and will not persist after a call of
* `free_TS_reader`).
*
* Note that the caller is trusted to call this only when appropriate.
*
* Returns 0 if all goes well, EOF if end of file was read, or 1 if some
* other error occurred (in which case it will already have output a message
* on stderr about the problem).
*/
extern int read_rest_of_first_TS_packet(TS_reader_p tsreader,
byte start[4],
byte **packet)
{
tsreader->read_ahead[0] = start[0];
tsreader->read_ahead[1] = start[1];
tsreader->read_ahead[2] = start[2];
tsreader->read_ahead[3] = start[3];
// So we already have the first 4 bytes in hand
return read_next_TS_packets(tsreader,4,packet);
}
/*
* Read the next TS packet.
*
* - `tsreader` is the TS packet reading context
* - `packet` is (a pointer to) the resultant TS packet.
*
* This is a pointer into the reader's read-ahead buffer, and so should not
* be freed. Note that this means that it may not persist after another call
* of this function (and will not persist after a call of
* `free_TS_reader`).
*
* Returns 0 if all goes well, EOF if end of file was read, or 1 if some
* other error occurred (in which case it will already have output a message
* on stderr about the problem).
*/
extern int read_next_TS_packet(TS_reader_p tsreader,
byte **packet)
{
return read_next_TS_packets(tsreader,0,packet);
}
// ------------------------------------------------------------
// Reading a transport stream with buffered timing
// Keeps a PCR in hand, so that it has accurate timing information
// for each TS packet
// ------------------------------------------------------------
// This is a simplistic approach to the problem -- if it suffices,
// it will be left as-is until something more sophisticated is
// needed
/* Make sure we've got a PCR buffer allocated, and that
* its content is entirely unset (so this also serves as
* a "reset" function).
*
* Returns 0 if all went well, 1 if something went wrong.
*/
static int start_TS_packet_buffer(TS_reader_p tsreader)
{
if (tsreader->pcrbuf == NULL)
{
tsreader->pcrbuf = malloc(SIZEOF_TS_PCR_BUFFER);
if (tsreader->pcrbuf == NULL)
{
print_err("### Unable to allocate TS PCR read-ahead buffer\n");
return 1;
}
}
memset(tsreader->pcrbuf, '\0', SIZEOF_TS_PCR_BUFFER);
return 0;
}
/* Fill up the PCR read-ahead buffer with TS entries, until we hit
* one (of the correct PID) with a PCR.
*
* Returns 0 if all went well, 1 if something went wrong, EOF if EOF was read.
*/
static int fill_TS_packet_buffer(TS_reader_p tsreader)
{
int ii;
// Work out which TS packet we *will* have as our first (zeroth) entry
tsreader->pcrbuf->TS_buffer_posn += tsreader->pcrbuf->TS_buffer_len;
tsreader->pcrbuf->TS_buffer_len = 0;
tsreader->pcrbuf->TS_buffer_next = 0;
for (ii=0; ii<PCR_READ_AHEAD_SIZE; ii++)
{
byte *data;
uint32_t pid;
int got_pcr;
uint64_t pcr;
int payload_unit_start_indicator;
byte *adapt;
int adapt_len;
byte *payload;
int payload_len;
// Retrieve a pointer to the data for the next TS packet
int err = read_next_TS_packet(tsreader,&data);
if (err)
{
if (err == EOF)
{
// For simplicity (of my coding, not anything else), when we hit
// EOF we'll just return as much. This means that we will ignore
// any TS records between the last TS-with-a-PCR and the EOF.
return EOF;
}
else
{
fprint_err("### Error (pre)reading TS packet %d\n",
tsreader->pcrbuf->TS_buffer_posn+ii);
return 1;
}
}
// Copy the data into our own read-ahead buffer
memcpy(tsreader->pcrbuf->TS_buffer[ii],data,TS_PACKET_SIZE);
err = split_TS_packet(data,&pid,&payload_unit_start_indicator,
&adapt,&adapt_len,&payload,&payload_len);
if (err)
{
fprint_err("### Error splitting TS packet %d\n",
tsreader->pcrbuf->TS_buffer_posn+ii);
return 1;
}
tsreader->pcrbuf->TS_buffer_len ++;
if (pid != tsreader->pcrbuf->TS_buffer_pcr_pid)
continue; // don't care about any PCR it might have
get_PCR_from_adaptation_field(adapt,adapt_len,&got_pcr,&pcr);
if (got_pcr)
{
tsreader->pcrbuf->TS_buffer_prev_pcr = tsreader->pcrbuf->TS_buffer_end_pcr;
tsreader->pcrbuf->TS_buffer_end_pcr = pcr;
tsreader->pcrbuf->TS_buffer_time_per_TS =
pcr_unsigned_diff(tsreader->pcrbuf->TS_buffer_end_pcr, tsreader->pcrbuf->TS_buffer_prev_pcr) /
tsreader->pcrbuf->TS_buffer_len;
return 0;
}
}
// If we ran out of buffer, then we've really got no choice but to give up
// with an appropriate grumble
fprint_err("!!! Next PCR not found when reading forwards"
" (for %d TS packets, starting at TS packet %d)\n",PCR_READ_AHEAD_SIZE,
tsreader->pcrbuf->TS_buffer_posn);
return 1;
}
/* Set up the the "looping" buffered TS packet reader and let it know what its
* PCR PID is.
*
* This must be called before any other _buffered_TS_packet function.
*
* - `pcr_pid` is the PID within which we should look for PCR entries
*
* Returns 0 if all went well, 1 if something went wrong (allocating space
* for the TS PCR buffer).
*/
extern int prime_read_buffered_TS_packet(TS_reader_p tsreader,
uint32_t pcr_pid)
{
if (start_TS_packet_buffer(tsreader))
return 1;
tsreader->pcrbuf->TS_buffer_pcr_pid = pcr_pid;
return 0;
}
/* Retrieve the first TS packet from the PCR read-ahead buffer,
* complete with its calculated PCR time.
*
* prime_read_buffered_TS_packet() must have been called before this.
*
* This should be called the first time a TS packet is to be read
* using the PCR read-ahead buffer. It "primes" the read-ahead mechanism
* by performing the first actual read-ahead.
*
* - `pcr_pid` is the PID within which we should look for PCR entries
* - `start_count` is the index of the current (last read) TS entry (which will
* generally be the PMT).
* - `data` returns a pointer to the TS packet data
* - `pid` is its PID
* - `pcr` is its PCR, calculated using the previous known PCR and
* the following known PCR.
* - `count` is the index of the returned TS packet in the file
*
* Note that, like read_next_TS_packet, we return a pointer to our data,
* and, similarly, warn that it will go away next time this function
* is called.
*
* Returns 0 if all went well, 1 if something went wrong, EOF if EOF was read.
*/
extern int read_first_TS_packet_from_buffer(TS_reader_p tsreader,
uint32_t pcr_pid,
uint32_t start_count,
byte *data[TS_PACKET_SIZE],
uint32_t *pid,
uint64_t *pcr,
uint32_t *count)
{
int err;
if (tsreader->pcrbuf == NULL)
{
print_err("### TS PCR read-ahead buffer has not been set up\n"
" Make sure prime_read_buffered_TS_packet() has been called\n");
return 1;
}
// Reset things
tsreader->pcrbuf->TS_buffer_next = 0;
tsreader->pcrbuf->TS_buffer_end_pcr = 0;
tsreader->pcrbuf->TS_buffer_prev_pcr = 0;
tsreader->pcrbuf->TS_buffer_posn = start_count;
tsreader->pcrbuf->TS_buffer_len = 0;
tsreader->pcrbuf->TS_buffer_pcr_pid = pcr_pid;
tsreader->pcrbuf->TS_had_EOF = FALSE;
// Read TS packets into our buffer until we find one with a PCR
err = fill_TS_packet_buffer(tsreader);
if (err) return err;
// However, it's only the last packet (the one with the PCR) that
// we are actually interested in
tsreader->pcrbuf->TS_buffer_next = tsreader->pcrbuf->TS_buffer_len - 1;
// Why, this is the very packet with its own PCR
*pcr = tsreader->pcrbuf->TS_buffer_end_pcr;
*data = tsreader->pcrbuf->TS_buffer[tsreader->pcrbuf->TS_buffer_next];
*pid = tsreader->pcrbuf->TS_buffer_pids[tsreader->pcrbuf->TS_buffer_next];
*count = start_count + tsreader->pcrbuf->TS_buffer_len;
tsreader->pcrbuf->TS_buffer_next ++;
return 0;
}
/* Retrieve the next TS packet from the PCR read-ahead buffer,
* complete with its calculated PCR time.
*
* - `data` returns a pointer to the TS packet data
* - `pid` is its PID
* - `pcr` is its PCR, calculated using the previous known PCR and
* the following known PCR.
*
* Note that, like read_next_TS_packet, we return a pointer to our data,
* and, similarly, warn that it might go away next time this function
* is called.
*
* Returns 0 if all went well, 1 if something went wrong, EOF if EOF was read.
*/
extern int read_next_TS_packet_from_buffer(TS_reader_p tsreader,
byte *data[TS_PACKET_SIZE],
uint32_t *pid,
uint64_t *pcr)
{
int err;
if (tsreader->pcrbuf == NULL)
{
print_err("### TS PCR read-ahead buffer has not been set up\n"
" Make sure read_first_TS_packet_from_buffer() has been called\n");
return 1;
}
if (tsreader->pcrbuf->TS_buffer_next == tsreader->pcrbuf->TS_buffer_len)
{
if (tsreader->pcrbuf->TS_had_EOF)
{
// We'd already run out of look-ahead packets, so just return
// our (deferred) end-of-file
return EOF;
}
else
{
err = fill_TS_packet_buffer(tsreader);
if (err == EOF)
{
// An EOF means we read the end-of-file before finding the next
// TS packet with a PCR. We could stop here (returning EOF), but
// whilst that would mean all TS packets had guaranteed accurate
// PCRs, it would also mean that we would ignore some TS packets
// at the end of the file. This proved unacceptable in practice,
// so our second best choice is to "play out" using the last
// known PCR rate-of-change.
tsreader->pcrbuf->TS_had_EOF = TRUE; // remember we're playing out
// Must move PCR start
tsreader->pcrbuf->TS_buffer_prev_pcr = tsreader->pcrbuf->TS_buffer_end_pcr;
// If we read nothing we must die now
if (tsreader->pcrbuf->TS_buffer_next == tsreader->pcrbuf->TS_buffer_len)
return err;
}
else if (err)
return err;
}
}
*data = tsreader->pcrbuf->TS_buffer[tsreader->pcrbuf->TS_buffer_next];
*pid = tsreader->pcrbuf->TS_buffer_pids[tsreader->pcrbuf->TS_buffer_next];
tsreader->pcrbuf->TS_buffer_next ++;
if (tsreader->pcrbuf->TS_buffer_next == tsreader->pcrbuf->TS_buffer_len &&
!tsreader->pcrbuf->TS_had_EOF)
{
// Why, this is the very packet with its own PCR
*pcr = tsreader->pcrbuf->TS_buffer_end_pcr;
}
else
{
*pcr = pcr_unsigned_wrap(tsreader->pcrbuf->TS_buffer_prev_pcr +
tsreader->pcrbuf->TS_buffer_time_per_TS *
tsreader->pcrbuf->TS_buffer_next);
}
return 0;
}
/*
* Read the next TS packet, coping with looping, etc.
*
* prime_read_buffered_TS_packet() should have been called first.
*
* This is a convenience wrapper around read_first_TS_packet_from_buffer()
* and read_next_TS_packet_from_buffer().
*
* This differs from ``read_TS_packet`` in that it assumes that the
* underlying code will already have read to the next PCR, so that
* it can know the *actual* (PCR-based) time for each TS packet.
*
* - `tsreader` is the TS reader context
* - `count` is a running count of TS packets read from this input
* - `data` is a pointer to the data for the packet
* - `pid` is the PID of the TS packet
* - `pcr` is the PCR value (possibly calculated) for this TS packet
* - if `max` is greater than zero, then at most `max` TS packets should
* be read from the input
* - if `loop`, play the input file repeatedly (up to `max` TS packets
* if applicable) - i.e., rewind to `start_posn` and start again if
* `count` reaches `max` (obviously only if `max` is greater than zero).
* - `start_count` is the value `count` should have after we've looped back
* to `start_posn`
* - if `quiet` is true, then only error messages should be written out
*
* Returns 0 if all went well, 1 if something went wrong, EOF if `loop` is
* false and either EOF was read, or `max` TS packets were read.
*/
extern int read_buffered_TS_packet(TS_reader_p tsreader,
uint32_t *count,
byte *data[TS_PACKET_SIZE],
uint32_t *pid,
uint64_t *pcr,
int max,
int loop,
offset_t start_posn,
uint32_t start_count,
int quiet)
{
int err;
if (max > 0 && (*count) >= (uint32_t)max)
{
if (loop)
{
if (!quiet)
fprint_msg("Read %d packets, rewinding and continuing\n",max);
err = seek_using_TS_reader(tsreader,start_posn);
if (err) return 1;
*count = start_count;
}
else
{
if (!quiet) fprint_msg("Stopping after %d TS packets\n",max);
return EOF;
}
}
// Read the next packet
if (*count == start_count)
{
// XXX
// XXX We *strongly* assume that we will find two PCRs (in the
// XXX required distance -- I think it's best to declare that
// XXX "not a problem", by fiat.
// XXX
// XXX But is it acceptable that we ignore any TS packets before
// XXX the first packet with a PCR? Probably more so than that we
// XXX should ignore any packets at the end of the file.
// XXX
err = read_first_TS_packet_from_buffer(tsreader,
tsreader->pcrbuf->TS_buffer_pcr_pid,
start_count,data,pid,pcr,count);
if (err)
{
if (err == EOF)
{
print_err("### EOF looking for first PCR\n");
return 1;
}
else
{
fprint_err("### Error reading TS packet %d, looking for first PCR\n",
*count);
return 1;
}
}
}
else
{
err = read_next_TS_packet_from_buffer(tsreader,data,pid,pcr);
if (err)
{
if (err == EOF)
{
if (!loop)
return EOF;
if (!quiet)
fprint_msg("EOF (after %d TS packets), rewinding and continuing\n",
*count);
}
else
{
fprint_err("### Error reading TS packet %d\n",*count);
if (!loop)
return 1;
if (!quiet)
print_msg("!!! Rewinding and continuing anyway\n");
}
err = seek_using_TS_reader(tsreader,start_posn);
if (err) return 1;
*count = start_count;
err = read_first_TS_packet_from_buffer(tsreader,
tsreader->pcrbuf->TS_buffer_pcr_pid,
start_count,data,pid,pcr,count);
if (err)
{
print_err("### Failed rewinding\n");
return 1;
}
}
else
(*count) ++;
}
return 0;
}
// ------------------------------------------------------------
// Packet interpretation
// ------------------------------------------------------------
/*
* Retrieve the PCR (if any) from a TS packet's adaptation field
*
* - `adapt` is the adaptation field content
* - `adapt_len` is its length
* - `got_PCR` is TRUE if the adaptation field contains a PCR
* - `pcr` is then the PCR value itself
*/
extern void get_PCR_from_adaptation_field(byte adapt[],
int adapt_len,
int *got_pcr,
uint64_t *pcr)
{
if (adapt_len == 0 || adapt == NULL)
*got_pcr = FALSE;
else if (adapt[0] & 0x10) // We have a PCR
{
*got_pcr = TRUE;
// The program_clock_reference_base
// NB: Force the first byte to be unsigned 64 bit, or else on Windows
// it tends to get shifted as a signed integer, and sign-extended,
// before it gets turned unsigned (which is probably the "correct"
// behaviour according to the standard. oh well).
*pcr = ((uint64_t)adapt[1] << 25) | (adapt[2] << 17) | (adapt[3] << 9) |
(adapt[4] << 1) | (adapt[5] >> 7);
// Plus the program clock reference extension
*pcr = ((*pcr) * 300) + ((adapt[5] & 1) << 8) + adapt[6];
}
else
*got_pcr = FALSE;
return;
}
/*
* Report on the contents of this TS packet's adaptation field
*
* - `adapt` is the adaptation field content
* - `adapt_len` is its length
*
* Returns 0 if all went well, 1 if something went wrong.
*/
extern void report_adaptation_field(byte adapt[],
int adapt_len)
{
int got_pcr;
uint64_t pcr;
if (adapt_len == 0 || adapt == NULL)
return;
fprint_msg(" Adaptation field len %3d [flags %02x]",adapt_len,adapt[0]);
if (adapt[0] != 0)
{
print_msg(":");
if (ON(adapt[0],0x80)) print_msg(" discontinuity ");
if (ON(adapt[0],0x40)) print_msg(" random access ");
if (ON(adapt[0],0x20)) print_msg(" ES-priority ");
if (ON(adapt[0],0x10)) print_msg(" PCR ");
if (ON(adapt[0],0x08)) print_msg(" OPCR ");
if (ON(adapt[0],0x04)) print_msg(" splicing ");
if (ON(adapt[0],0x02)) print_msg(" private ");
if (ON(adapt[0],0x01)) print_msg(" extension ");
}
print_msg("\n");
get_PCR_from_adaptation_field(adapt,adapt_len,&got_pcr,&pcr);
if (got_pcr)
{
fprint_msg(" .. PCR %12" LLU_FORMAT_STUMP "\n", pcr);
}
return;
}
/*
* Report on the timing information from this TS packet's adaptation field
*
* - if `times` is non-NULL, then timing information (derived from the PCR)
* will be calculated and reported
* - `adapt` is the adaptation field content
* - `adapt_len` is its length
* - `packet_count` is a count of how many TS packets up to now
*
* Returns 0 if all went well, 1 if something went wrong.
*/
extern void report_adaptation_timing(timing_p times,
byte adapt[],
int adapt_len,
int packet_count)
{
int got_pcr;
uint64_t pcr;
if (adapt_len == 0 || adapt == NULL || times == NULL)
return;
get_PCR_from_adaptation_field(adapt,adapt_len,&got_pcr,&pcr);
if (got_pcr)
{
fprint_msg(" .. PCR %12" LLU_FORMAT_STUMP, pcr);
if (!times->had_first_pcr)
{
times->last_pcr_packet = times->first_pcr_packet = packet_count;
times->last_pcr = times->first_pcr = pcr;
times->had_first_pcr = TRUE;
}
else
{
if (pcr < times->last_pcr)
fprint_msg(" Discontinuity: PCR was %7" LLU_FORMAT_STUMP ", now %7"
LLU_FORMAT_STUMP,times->last_pcr,pcr);
else
{
fprint_msg(" Mean byterate %7" LLU_FORMAT_STUMP,
((packet_count - times->first_pcr_packet) * TS_PACKET_SIZE) *
TWENTY_SEVEN_MHZ / pcr_unsigned_diff(pcr, times->first_pcr));
fprint_msg(" byterate %7" LLU_FORMAT_STUMP,
((packet_count - times->last_pcr_packet) * TS_PACKET_SIZE) *
TWENTY_SEVEN_MHZ / pcr_unsigned_diff(pcr, times->last_pcr));
}
}
times->last_pcr_packet = packet_count;
times->last_pcr = pcr;
print_msg("\n");
}
return;
}
/*
* Report on the contents of this TS packet's payload. The packet is assumed
* to have a payload that is (part of) a PES packet.
*
* - if `show_data` then the data for the PES packet will also be shown
* - `stream_type` is the stream type of the data, or -1 if it is not
* known
* - `payload` is the payload of the TS packet. We know it can't be more
* than 184 bytes long, because of the packet header bytes.
* - regardless, `payload_len` is the actual length of the payload.
*
* Returns 0 if all went well, 1 if something went wrong.
*/
extern void report_payload(int show_data,
int stream_type,
byte payload[MAX_TS_PAYLOAD_SIZE],
int payload_len,
int payload_unit_start_indicator)
{
if (payload_unit_start_indicator)
report_PES_data_array2(stream_type,payload,payload_len, show_data?1000:0);
else if (show_data)
print_data(TRUE,"Data",payload,payload_len,1000);
}
/*
* Extract the program list from a PAT packet (PID 0x0000).
*
* Handles the result of calling build_psi_data() for this PAT.
*
* - if `verbose`, then report on what we're doing
* - `data` is the data for the PAT packet.
* - `data_len` is the length of said data.
* - `prog_list` is the list of program numbers versus PIDs.
*
* Returns 0 if all went well, 1 if something went wrong.
*/
extern int extract_prog_list_from_pat(int verbose,
byte data[],
int data_len,
pidint_list_p *prog_list)
{
int table_id;
int section_syntax_indicator,zero_bit,reserved1;
int section_length;
int transport_stream_id;
int version_number;
int current_next_indicator;
int section_number;
int last_section_number;
uint32_t crc = 0;
uint32_t check_crc;
byte *program_data;
int program_data_len;
int err;
if (data_len == 0)
{
print_err("### PAT data has zero length\n");
return 1;
}
if (data == NULL)
{
print_err("### PAT data is NULL\n");
return 1;
}
if (DEBUG) print_data(TRUE,"Data",data,data_len,1000);
// The table id in a PAT should be 0
table_id = data[0];
if (table_id != 0)
{
fprint_err("### PAT table id is %0#8x, should be 0\n",table_id);
return 1;
}
// Check bits - do we actually want to check these?
section_syntax_indicator = (data[1] & 0x80) >> 7;
zero_bit = (data[1] & 0x40) >> 6;
reserved1 = (data[1] & 0x30) >> 4;
if (section_syntax_indicator != 1 && report_bad_reserved_bits)
print_err("!!! PAT: section syntax indicator is 0, not 1\n");
if (zero_bit != 0 && report_bad_reserved_bits)
print_err("!!! PAT: zero bit is 1, not 0\n");
if (reserved1 != 3 && report_bad_reserved_bits)
fprint_err("!!! PAT: reserved1 is %d, not 3\n",reserved1);
section_length = ((data[1] & 0xF) << 8) | data[2];
if (verbose)
fprint_msg(" section length: %03x (%d)\n",
section_length,section_length);
// If the section length doesn't match our data length, we've got problems
// (remember, the section_length counts bytes after the section_length field)
if (section_length > data_len - 3)
{
fprint_err("### PAT section length %d is more than"
" length of remaining data %d\n",section_length,data_len-3);
return 1;
}
else if (section_length < data_len - 3)
{
fprint_err("!!! PAT section length %d does not use all of"
" remaining data %d\n",section_length,data_len-3);
// Adjust it and carry on
data_len = section_length + 3;
}
data_len = section_length + 3;
transport_stream_id = (data[3] << 8) | data[4];
if (verbose) fprint_msg(" transport stream id: %04x\n",transport_stream_id);
// reserved2 = (data[5] & 0xC0) >> 14;
version_number = (data[5] & 0x3E) >> 1;
current_next_indicator = data[5] & 0x1;
section_number = data[6];
last_section_number = data[7];
if (verbose)
fprint_msg(" version number %02x, current next %x, section number %x, last"
" section number %x\n",version_number,current_next_indicator,
section_number,last_section_number);
// 32 bits at the end of a program association section is reserved for a CRC
// (OK, let's extract it stupidly...)
crc = (crc << 8) | data[data_len-4];
crc = (crc << 8) | data[data_len-3];
crc = (crc << 8) | data[data_len-2];
crc = (crc << 8) | data[data_len-1];
// Let's check the CRC
check_crc = crc32_block(0xffffffff,data,data_len);
if (check_crc != 0)
{
fprint_err("!!! Calculated CRC for PAT is %08x, not 00000000"
" (CRC in data was %08x)\n",check_crc,crc);
return 1;
}
// (remember the section length is for the bytes *after* the section
// length field, so for data[3...])
program_data = data + 8;
program_data_len = data_len - 8 - 4; // The "-4" is for the CRC
//print_data(TRUE,"Rest:",program_data,program_data_len,1000);
err = build_pidint_list(prog_list);
if (err) return 1;
while (program_data_len > 0)
{
int program_number = (program_data[0] << 8) | program_data[1];
uint32_t pid = ((program_data[2] & 0x1F) << 8) | program_data[3];
// A program_number of 0 indicates the network ID, so ignore it and
// don't append to the program list - rrw 2004-10-13
if (!program_number)
{
if (verbose)
fprint_msg(" Network ID %04x (%3d)\n", pid, pid);
}
else
{
if (verbose)
fprint_msg(" Program %03x (%3d) -> PID %04x (%3d)\n",
program_number,program_number,pid,pid);
err = append_to_pidint_list(*prog_list,pid,program_number);
if (err) return 1;
}
program_data = program_data + 4;
program_data_len = program_data_len - 4;
}
return 0;
}
static const char * dvb_component_type3_str(int component_type)
{
switch (component_type)
{
case 0x01:
return "EBU Teletext subtitles";
case 0x02:
return "associated EBU Teletext";
case 0x03:
return "VBI data";
case 0x10:
return "DVB subtitles (normal) with no monitor aspect ratio criticality";
case 0x11:
return "DVB subtitles (normal) for display on 4:3 aspect ratio monitor";
case 0x12:
return "DVB subtitles (normal) for display on 16:9 aspect ratio monitor";
case 0x13:
return "DVB subtitles (normal) for display on 2.21:1 aspect ratio monitor";
case 0x14:
return "DVB subtitles (normal) for display on a high definition monitor";
case 0x20:
return "DVB subtitles (for the hard of hearing) with no monitor aspect ratio criticality";
case 0x21:
return "DVB subtitles (for the hard of hearing) for display on 4:3 aspect ratio monitor";
case 0x22:
return "DVB subtitles (for the hard of hearing) for display on 16:9 aspect ratio monitor";
case 0x23:
return "DVB subtitles (for the hard of hearing) for display on 2.21:1 aspect ratio monitor";
case 0x24:
return "DVB subtitles (for the hard of hearing) for display on a high definition monitor";
default:
if (component_type >= 0xb0 && component_type <= 0xfe)
{
return "user defined";
}
break;
}
return "reserved";
}
static const char * const descriptor_names[] =
{
"Reserved", // 0
"Forbidden", // 1
"Video stream", // 2
"Audio stream", // 3
"Hierarchy", // 4
"Registration", // 5
"Data stream alignment", // 6
"Target background grid", // 7
"Video window", // 8
"CA", // 9
"ISO 639 language", // 10
"System clock", // 11
"Multiplex buffer utilization", // 12
"Copyright", // 13
"Maximum bitrate", // 14
"Private data indicator", // 15
"Smoothing buffer", // 16
"STD", // 17
"IBP", // 18
"Defined in ISO/IEC 13818-6", // 19
"Defined in ISO/IEC 13818-6", // 20
"Defined in ISO/IEC 13818-6", // 21
"Defined in ISO/IEC 13818-6", // 22
"Defined in ISO/IEC 13818-6", // 23
"Defined in ISO/IEC 13818-6", // 24
"Defined in ISO/IEC 13818-6", // 25
"Defined in ISO/IEC 13818-6", // 26
"MPEG-4 video", // 27
"MPEG-4 audio", // 28
"IOD", // 29
"SL", // 30
"FMC", // 31
"External ES ID", // 32
"MuxCode", // 33
"FmxBufferSize", // 34
"MultiplexBuffer", // 35
"Content labeling", // 36
"Metadata pointer", // 37
"Metadata", // 38
"Metadata STD", // 39
"AVC video descriptor", // 40
"IPMP (defined in ISO/IEC 13818-11, MPEG-2 IPMP)", // 41
"AVC timing and HRD descriptor", // 42
"MPEG-2 AAC audio", // 43
"FlexMuxTiming", // 44
"MPEG-4 text", // 45
"MPEG-4 audio extension", // 46
"auxiliary video stream", // 47
"SVC extension", // 48
"MVC extension", // 49
2013-02-19 11:52:46 +00:00
"J2K video descriptor", // 50
"MVC operation point descriptor", // 51
"MPEG2 stereoscopic video format", // 52
"Stereoscopic_program_info_descriptor", // 53
"Stereoscopic_video_info_descriptor", // 54
"Transport_profile_descriptor", // 55
"HEVC video descriptor", // 56
"Reserved (57)", // 57
"Reserved (58)", // 58
"Reserved (59)", // 59
"Reserved (60)", // 60
"Reserved (61)", // 61
"Reserved (62)", // 62
"Extension descriptor", // 63
};
2013-08-14 10:57:54 +00:00
// From ATSC A/52B section A3.4
// N.B. Horizontal lines in the table represent valid stop points
2013-08-14 10:57:54 +00:00
static void print_ac3_audio_descriptor(const int is_msg, const byte * const buf, const int len)
{
const byte * p = buf;
const byte * const eop = p + len;
static const char * const sample_rate_txt[8] = {
"48k", "44k1", "32k", "Reserved(3)", "48k or 44.1k", "48k or 32k", "44.1k or 32k", "48k or 44.1k or 32k"
};
static const int bit_rate_n[32] = {
32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 576, 640,
};
static const char * const dsurmod_txt[4] = {
"Unknown", "Not Dolby suuround encoded", "Dolby surround encoded", "Reserved"
};
static const char * const num_channels_txt[16] = {
"1 + 1", "1/0", "2/0", "3/0", "2/1", "3/1", "2/2", "3/2",
"1", "<=2", "<=3", "<=4", "<=5", "<=6", "Reserved(14)", "Reserved(15)"
};
static const char * const priority_txt[4] = {
"Reserved(0)", "Primary Audio", "Other Audio", "Not specified"
};
unsigned int nc;
unsigned int bsmod;
if (p >= eop)
{
goto too_short;
}
fprint_msg_or_err(is_msg, "sample_rate: %s, bsid: %d", sample_rate_txt[*p >> 5], *p & 0x1f);
++p;
fprint_msg_or_err(is_msg, ", bit_rate: %s %dk, dsurmod: %s", *p >> 7 ? "Upper" : "Exact",
bit_rate_n[(*p >> 2) & 31],
dsurmod_txt[*p & 3]);
if (++p >= eop)
{
goto too_short;
}
nc = (*p >> 1) & 0x0f;
bsmod = *p >> 5;
fprint_msg_or_err(is_msg, ", bsmod: %d, num_channels: %s, full_svc: %d",
bsmod, num_channels_txt[nc], *p & 1);
if (++p >= eop)
{
goto done;
2013-08-14 10:57:54 +00:00
}
fprint_msg_or_err(is_msg, ", langcod: %d", *p);
if (nc == 0)
{
if (++p >= eop)
{
goto done;
2013-08-14 10:57:54 +00:00
}
fprint_msg_or_err(is_msg, ", langcod2: %d", *p);
}
if (++p >= eop)
{
goto done;
2013-08-14 10:57:54 +00:00
}
if (bsmod < 2)
{
fprint_msg_or_err(is_msg, ", mainid: %d, priority: %s", *p >> 5, priority_txt[(*p >> 3) & 3]);
if ((*p & 7) != 7)
{
fprint_msg_or_err(is_msg, ", reserved(7): %d", *p & 7);
}
}
else
{
fprint_msg_or_err(is_msg, ", asvcflags: %#08x", *p);
}
if (++p >= eop)
{
goto done;
2013-08-14 10:57:54 +00:00
}
{
unsigned int textlen = *p >> 1;
const int utf16 = !(*p & 1);
fprint_msg_or_err(is_msg, ", text(%c): ", utf16 ? 'U' : 'S');
if (p + textlen >= eop)
{
goto too_short;
}
if (textlen == 0)
{
fprint_msg_or_err(is_msg, "<none>");
}
else if (!utf16)
{
fprint_msg_or_err(is_msg, "\"");
while (textlen-- != 0)
{
fprint_msg_or_err(is_msg, "%c", *++p);
}
fprint_msg_or_err(is_msg, "\"");
}
else
{
fprint_msg_or_err(is_msg, "??");
p += textlen;
}
}
if (++p >= eop)
{
goto done;
2013-08-14 10:57:54 +00:00
}
{
const int language_flag = *p >> 7;
const int language_flag_2 = (*p >> 6) & 1;
if ((*p & 0x1f) != 0x1f)
{
fprint_msg_or_err(is_msg, ", reserved(0x1f): %#x", *p & 0x1f);
}
if (language_flag)
{
if (++p + 2 >= eop)
{
goto too_short;
}
fprint_msg_or_err(is_msg, ", language: %c%c%c", p[0], p[1], p[2]);
p += 2;
}
if (language_flag_2)
{
if (++p + 2 >= eop)
{
goto too_short;
}
fprint_msg_or_err(is_msg, ", language_2: %c%c%c", p[0], p[1], p[2]);
p += 2;
}
}
if (++p >= eop)
2013-08-14 10:57:54 +00:00
{
goto done;
2013-08-14 10:57:54 +00:00
}
print_data(is_msg, "additional_info: ", p, eop - p,100);
done:
2013-08-14 10:57:54 +00:00
fprint_msg_or_err(is_msg, "\n");
return;
too_short:
fprint_msg_or_err(is_msg, "; ### block short ###\n");
}
2015-08-04 14:52:22 +00:00
static void print_HEVC_descriptor(const int is_msg, const byte * const buf, const int len)
{
const uint8_t * p = buf;
const byte * const eop = p + len;
static const char * const prog_interlace[4] = {
"unknown scan source",
"interlaced source",
"progressive source",
"mixed scan source"
};
if (len == 9)
{
// I've seen a number of these but I can't find a standard
print_data(is_msg, "HEVC video descriptor ### bad length", buf, len ,100);
return;
}
fprint_msg_or_err(is_msg, "HEVC video descriptor:");
if (p >= eop)
{
goto too_short;
}
fprint_msg_or_err(is_msg, " profile_space=%d, tier_flag=%d, profile_idc=%d", *p >> 6, (*p >> 5) & 1, *p & 0x1f);
if (++p + 3 >= eop)
{
goto too_short;
}
fprint_msg_or_err(is_msg, ", profile_compatability=%#08x", uint_32_be(p));
if ((p += 4) + 5 >= eop)
{
goto too_short;
}
fprint_msg_or_err(is_msg, ", %s%s%s", prog_interlace[*p >> 6], *p & 0x20 ? ", non_packed" : "", *p & 0x10 ? ", frame_only" : "");
if ((*p & 0xf) != 0 || p[1] != 0 || p[2] != 0 || p[3] != 0 || p[4] != 0 || p[5] != 0)
{
fprint_msg_or_err(is_msg, ", ### reserved_zero_44bits=0x%x%02x%08x", *p & 0xf, p[1], uint_32_be(p + 2));
}
if ((p += 6) >= eop)
{
goto too_short;
}
fprint_msg_or_err(is_msg, ", level=%d.%d", *p / 30, *p % 30);
if (++p >= eop)
{
goto too_short;
}
fprint_msg_or_err(is_msg, "%s%s", *p & 0x40 ? ", still" : "", *p & 0x20 ? ", 24hr" : "");
if ((*p & 0x1f) != 0x1f)
{
fprint_msg_or_err(is_msg, ", ### reserved=%#02x", *p & 0x1f);
}
if ((*p++ & 0x80) != 0)
{
fprint_msg_or_err(is_msg, ", temporal_id");
if (p + 2 >= eop)
{
goto too_short;
}
if ((*p >> 3) != 0x1f)
{
fprint_msg_or_err(is_msg, " ### reserved=%#02x", *p & 0x1f);
}
fprint_msg_or_err(is_msg, " min=%d", *p & 7);
++p;
if ((*p >> 3) != 0x1f)
{
fprint_msg_or_err(is_msg, " ### reserved=%#02x", *p & 0x1f);
}
fprint_msg_or_err(is_msg, " max=%d", *p & 7);
++p;
}
fprint_msg_or_err(is_msg,"\n");
return;
too_short:
fprint_msg_or_err(is_msg, "; ### block short ###\n");
}
/*
* Print out information about program descriptors
* (either from the PMT program info, or the PMT/stream ES info)
*
* - if `is_msg` then print as a message, otherwise as an error
* - `leader1` and `leader2` are the text to write at the start of each line
* (either or both may be NULL)
* - `desc_data` is the data containing the descriptors
* - `desc_data_len` is its length
*
* Returns 0 if all went well, 1 if something went wrong
*
* If you want to interpret more descriptors then ITU-T J.94 is the standard
*/
extern int print_descriptors(int is_msg,
char *leader1,
char *leader2,
byte *desc_data,
int desc_data_len)
{
byte data_len = desc_data_len;
byte *data = desc_data;
while (data_len >= 2)
{
byte tag = data[0];
byte this_length = data[1];
data += 2;
data_len -= 2;
if (this_length > data_len)
{
// Not much we can do - try giving up?
fprint_msg_or_err(is_msg,"Descriptor %x says length %d, but only %d bytes left\n",
tag,this_length,data_len);
return 1; // Hmm - well, maybe
}
if (leader1 != NULL) fprint_msg_or_err(is_msg,"%s",leader1);
if (leader2 != NULL) fprint_msg_or_err(is_msg,"%s",leader2);
{
int ii;
uint32_t temp_u;
switch (tag)
{
case 5:
fprint_msg_or_err(is_msg,"Registration ");
if (this_length >= 4)
{
for (ii=0; ii<4; ii++)
{
if (isprint(data[ii]))
fprint_msg_or_err(is_msg,"%c",data[ii]);
else
fprint_msg_or_err(is_msg,"<%02x>",data[ii]);
}
if (this_length > 4)
for (ii=4; ii < this_length; ii++)
fprint_msg_or_err(is_msg," %02x",data[ii]);
}
fprint_msg_or_err(is_msg,"\n");
break;
case 9: // I see this in data, so might as well "explain" it
fprint_msg_or_err(is_msg,"Conditional access: ");
temp_u = (data[0] << 8) | data[1];
fprint_msg_or_err(is_msg,"id %04x (%d) ",temp_u,temp_u);
temp_u = ((data[2] & 0x1F) << 8) | data[3];
fprint_msg_or_err(is_msg,"PID %04x (%d) ",temp_u,temp_u);
if (data_len > 4)
print_data(is_msg,"data",&data[4],data_len-4,data_len-4);
else
fprint_msg_or_err(is_msg,"\n");
break;
case 10: // We'll assume the length is a multiple of 4
fprint_msg_or_err(is_msg,"Languages: ");
for (ii = 0; ii < this_length/4; ii++)
{
byte audio_type;
if (ii > 0) fprint_msg_or_err(is_msg,", ");
fprint_msg_or_err(is_msg,"%c",*(data+(ii*4)+0));
fprint_msg_or_err(is_msg,"%c",*(data+(ii*4)+1));
fprint_msg_or_err(is_msg,"%c",*(data+(ii*4)+2));
audio_type = *(data+(ii*4)+3);
switch (audio_type)
{
case 0: /*fprint_msg_or_err(is_msg,"/undefined");*/ break; // clearer to say nowt?
case 1: fprint_msg_or_err(is_msg,"/clean effects"); break;
case 2: fprint_msg_or_err(is_msg,"/hearing impaired"); break;
case 3: fprint_msg_or_err(is_msg,"/visual impaired commentary"); break;
default: fprint_msg_or_err(is_msg,"/reserved:0x%02x",audio_type); break;
}
}
fprint_msg_or_err(is_msg,"\n");
break;
2013-02-19 11:52:46 +00:00
case 40:
{
const uint8_t * p = data;
unsigned int t;
fprint_msg_or_err(is_msg,"AVC video descriptor: ");
if (this_length != 4)
{
if (this_length < 4)
{
// Give up if too short
fprint_msg_or_err(is_msg,"### descriptor too short %d ###\n", this_length);
break;
}
else
{
// Complain but carry on if too long
fprint_msg_or_err(is_msg,"### descriptor too long %d ###: \n", this_length);
}
}
fprint_msg_or_err(is_msg,"profile idc: %d, ", *p++);
fprint_msg_or_err(is_msg,"constraint_set[");
t = *p;
for (ii = 0; ii != 6; ++ii)
{
fprint_msg_or_err(is_msg,"%c", (t & 0x80) != 0 ? '0' + ii : '-');
t <<= 1;
}
fprint_msg_or_err(is_msg,"], AVC_compatible_flags: %d, ", *p++ & 3);
fprint_msg_or_err(is_msg,"level_idc: %d, ", *p++);
fprint_msg_or_err(is_msg,"AVC_still_present: %d, ", (*p >> 7) & 1);
fprint_msg_or_err(is_msg,"AVC_24_hour_picture_flag: %d, ", (*p >> 6) & 1);
fprint_msg_or_err(is_msg,"Frame_Packing_SEI_not_present_flag: %d, ", (*p >> 5) & 1);
fprint_msg_or_err(is_msg,"reserved: %#x", *p & 0x1f);
fprint_msg_or_err(is_msg,"\n");
break;
}
case 42:
{
const uint8_t * p = data;
fprint_msg_or_err(is_msg,"AVC timing and HRD descriptor: ");
fprint_msg_or_err(is_msg,"hrd_management_valid_flag: %d, ", (*p & 0x80) != 0);
if ((*p & 0x7e) != 0x7e)
{
fprint_msg_or_err(is_msg,"reserved: %#x, ", (*p & 0x7e) >> 1);
}
if ((*p++ & 1) != 0) // picture_and_timing_info_present
{
int flag90 = *p >> 7;
uint32_t n = 1, k = 300;
uint32_t ntick;
if (flag90)
{
fprint_msg_or_err(is_msg,"90kHz_flag, ", *p & 0x7f);
}
if ((*p & 0x7f) != 0x7f)
{
fprint_msg_or_err(is_msg,"reserved: %#x, ", *p & 0x7f);
}
++p;
if (!flag90)
{
n = uint_32_be(p);
p += 4;
k = uint_32_be(p);
p += 4;
fprint_msg_or_err(is_msg,"N/K: %u/%u, ", n, k);
}
ntick = uint_32_be(p);
p += 4;
fprint_msg_or_err(is_msg,"num_units_in_tick: %u, ", ntick);
if (k == 0 || ntick == 0)
fprint_msg_or_err(is_msg,"(frame rate: \?\?\?), ");
else
fprint_msg_or_err(is_msg,"(frame rate: %.6g), ", ((double)n * 27000000.0) / ((double)k * (double)ntick) / 2.0);
}
fprint_msg_or_err(is_msg,"fixed_frame_rate_flag: %u, ", *p >> 7);
fprint_msg_or_err(is_msg,"temporal_poc_flag: %u, ", (*p >> 6) & 1);
fprint_msg_or_err(is_msg,"picture_to_display_conversion_flag: %u", (*p >> 5) & 1);
if ((*p & 0x1f) != 0x1f)
{
fprint_msg_or_err(is_msg,", reserved: %#x", *p & 0x1f);
}
fprint_msg_or_err(is_msg,"\n");
}
break;
2013-02-19 11:52:46 +00:00
case 52:
fprint_msg_or_err(is_msg,"MPEG2 stereoscopic video format: ");
if (this_length != 1)
{
if (this_length < 1)
{
// Give up if too short
fprint_msg_or_err(is_msg,"### descriptor too short %d ###\n", this_length);
break;
}
else
{
// Complain but carry on if too long
fprint_msg_or_err(is_msg,"### descriptor too long %d ###: \n", this_length);
}
}
if ((data[0] & 0x80) != 0)
{
fprint_msg_or_err(is_msg,"arrangement not present: reserved: %#x", data[0] & 0x7f);
}
else
{
fprint_msg_or_err(is_msg,"arrangement: ");
switch (data[0])
{
case 3:
fprint_msg_or_err(is_msg,"S3D side by side");
break;
case 4:
fprint_msg_or_err(is_msg,"S3D top and bottom");
break;
case 8:
fprint_msg_or_err(is_msg,"2D video");
break;
default:
fprint_msg_or_err(is_msg,"reserved: %#x", data[0] & 0x7f);
break;
}
}
fprint_msg_or_err(is_msg,"\n");
break;
2015-08-04 14:52:22 +00:00
case 56:
print_HEVC_descriptor(is_msg, data, this_length);
break;
case 0x56: // teletext
for (ii = 0; ii < this_length; ii += 5)
{
int jj;
int teletext_type, teletext_magazine, teletext_page;
if (ii == 0)
fprint_msg_or_err(is_msg,"Teletext: ");
else
{
if (leader1 != NULL) fprint_msg_or_err(is_msg,"%s",leader1);
if (leader2 != NULL) fprint_msg_or_err(is_msg,"%s",leader2);
fprint_msg_or_err(is_msg," ");
}
fprint_msg_or_err(is_msg,"language=");
for (jj=ii; jj<ii+3; jj++)
{
if (isprint(data[jj]))
fprint_msg_or_err(is_msg,"%c",data[jj]);
else
fprint_msg_or_err(is_msg,"<%02x>",data[jj]);
}
teletext_type = (data[ii+3] & 0xF8) >> 3;
teletext_magazine = (data[ii+3] & 0x07);
teletext_page = data[ii+4];
fprint_msg_or_err(is_msg,", type=");
switch (teletext_type)
{
case 1: fprint_msg_or_err(is_msg,"Initial"); break;
case 2: fprint_msg_or_err(is_msg,"Subtitles"); break;
case 3: fprint_msg_or_err(is_msg,"Additional info"); break;
case 4: fprint_msg_or_err(is_msg,"Programme schedule"); break;
case 5: fprint_msg_or_err(is_msg,"Hearing impaired subtitles"); break;
default: fprint_msg_or_err(is_msg,"%x (reserved)",teletext_type); break;
}
fprint_msg_or_err(is_msg,", magazine %d, page %x",teletext_magazine,teletext_page);
fprint_msg_or_err(is_msg,"\n");
}
break;
case 0x59:
{
fprint_msg_or_err(is_msg, "subtitling_descriptor(s):\n");
for (ii = 0; ii + 8 <= this_length; ii += 8)
{
char lang[4];
unsigned int subtitling_type = data[ii + 3];
unsigned int composition_page_id = (data[ii + 4] << 8) | data[ii + 5];
unsigned int ancillary_page_id = (data[ii + 6] << 8) | data[ii + 7];
lang[0] = data[ii + 0];
lang[1] = data[ii + 1];
lang[2] = data[ii + 2];
lang[3] = 0;
if (leader1 != NULL) fprint_msg_or_err(is_msg,"%s",leader1);
if (leader2 != NULL) fprint_msg_or_err(is_msg,"%s",leader2);
fprint_msg_or_err(is_msg," language='%s', subtitling_type=%u\n",
lang, subtitling_type);
if (leader1 != NULL) fprint_msg_or_err(is_msg,"%s",leader1);
if (leader2 != NULL) fprint_msg_or_err(is_msg,"%s",leader2);
fprint_msg_or_err(is_msg, " (%s)\n", dvb_component_type3_str(subtitling_type));
if (leader1 != NULL) fprint_msg_or_err(is_msg,"%s",leader1);
if (leader2 != NULL) fprint_msg_or_err(is_msg,"%s",leader2);
fprint_msg_or_err(is_msg,
" composition_page_id=%u, ancillary_page_id=%u\n",
composition_page_id, ancillary_page_id);
}
if (ii < this_length)
fprint_msg_or_err(is_msg, "### %d spare bytes at end of descriptor\n", this_length - ii);
break;
}
case 0x6A:
print_data(is_msg,"DVB AC-3",data,this_length,100);
break;
case 0x81:
2013-08-14 10:57:54 +00:00
// print_data(is_msg,"ATSC AC-3",data,this_length,100);
fprint_msg_or_err(is_msg, "ATSC AC-3: ");
print_ac3_audio_descriptor(is_msg, data, this_length);
break;
default:
{
char temp_c[50]; // twice as much as I need...
snprintf(temp_c, sizeof(temp_c), "%s (%d)",
tag < sizeof(descriptor_names)/sizeof(descriptor_names[0]) ?
descriptor_names[tag] :
tag < 64 ? "Reserved" : "User Private",
tag);
print_data(is_msg,temp_c,data,this_length,100);
}
break;
}
}
data_len -= this_length;
data += this_length;
}
return 0;
}
/*
* Given a TS packet, extract the (next bit of) a PAT/PMT's data.
*
* - if `verbose`, then report on what we're doing
* - `payload` is the payload of the current TS packet. We know it can't be
* more than 184 bytes long, because of the packet header bytes.
* - regardless, `payload_len` is the actual length of the payload.
* - `pid` is the PID of this TS packet.
* - `data` is the data array for the whole of the data of this PSI.
* If it is passed as NULL, then the TS packet must be the first for
* this PSI, and this function will malloc an array of the appropriate
* length (and return it here). If it is non-NULL, then it is partially
* full.
* - `data_len` is the actual length of the `data` array -- if `data` is NULL
* then this will be set by the function.
* - `data_used` is how many bytes of data are already in the `data` array.
* This will be updated by this function - if it is returned as equal to
* `data_len`, then the PAT/PMT packet data is complete.
*
* Usage:
*
* If a PSI packet has PUSI set, then it is the first packet of said PSI
* (which, for our purposes, means PAT or PMT). If it does not, then it
* is a continuation. If PUSI was set, call this with ``data`` NULL, otherwise
* pass it some previous data to continue.
*
* Returns 0 if all went well, 1 if something went wrong.
*/
extern int build_psi_data(int verbose,
byte payload[MAX_TS_PAYLOAD_SIZE],
int payload_len,
uint32_t pid,
byte **data,
int *data_len,
int *data_used)
{
byte *packet_data;
int packet_data_len;
int pointer;
int section_length;
if (payload_len == 0)
{
print_err("### PMT payload has zero length\n");
return 1;
}
if (payload == NULL)
{
print_err("### PMT payload is NULL\n");
return 1;
}
if (*data == NULL)
{
// We have the first section of a PSI packet, which contains the pointer
// field - deal with it
pointer = payload[0];
if (pointer > (payload_len - 1))
{
fprint_err("### PMT payload: pointer is %d, which is off the end of"
" the packet (length %d)\n",pointer,payload_len);
return 1;
}
// if (DEBUG) print_data(TRUE,"PMT",payload,payload_len,1000);
packet_data = payload + pointer + 1;
packet_data_len = payload_len - pointer - 1;
if (DEBUG) print_data(TRUE,"Data",packet_data,packet_data_len,1000);
section_length = ((packet_data[1] & 0xF) << 8) | packet_data[2];
#if 0 // XXX
print_msg("===========================================\n");
print_data(TRUE,"build_pmt_data(new)",packet_data,packet_data_len,packet_data_len);
#endif
*data_len = section_length + 3;
// Beware - if our PMT is shorter than our TS packet, we only want to
// "use" the data that belongs to our PMT, not the rest of the packet
// (which is hopefully full of 0xFF anyway)
// We want to get this right because our callers decide if they've
// finished reading a PMT by comparing data_used with data_len.
if (packet_data_len > *data_len)
*data_used = *data_len;
else
*data_used = packet_data_len;
*data = malloc(*data_len);
if (*data == NULL)
{
print_err("### Unable to malloc PSI data array\n");
return 1;
}
memcpy(*data,packet_data,*data_len);
}
else
{
// This is a continuation of a PSI packet - it doesn't contain a pointer
// field, so our data is just data
int space_left = *data_len - *data_used;
packet_data = payload;
packet_data_len = payload_len;
if (DEBUG) print_data(TRUE,"Data",packet_data,packet_data_len,1000);
#if 0 // XXX
print_msg("===========================================\n");
print_data(TRUE,"build_pmt_data(old)",packet_data,packet_data_len,100);
#endif
if (space_left > packet_data_len)
{
// We have more than enough room - use all of this packet
memcpy(*data + *data_used, packet_data, packet_data_len);
*data_used += packet_data_len;
}
else
{
// We have more than enough data - use what we need
// (we assume the rest will be 0xFF padded, but shan't check)
memcpy(*data + *data_used, packet_data, space_left);
*data_used += space_left;
}
}
return 0;
}
/*
* Extract the program map table from a PMT packet.
*
* Handles the result of calling build_psi_data() for this PMT.
*
* - if `verbose`, then report on what we're doing
* - `data` is the data for the PMT packet.
* - `data_len` is the length of said data.
* - `pid` is the PID of this PMT
* - `pmt` is the new PMT datastructure
*
* Returns 0 if all went well, 1 if something went wrong.
*/
extern int extract_pmt(int verbose,
byte data[],
int data_len,
uint32_t pid,
pmt_p *pmt)
{
int table_id;
int section_syntax_indicator,zero_bit,reserved;
uint16_t program_number;
uint32_t pcr_pid;
int section_length;
int version_number;
int current_next_indicator;
int section_number;
int last_section_number;
int program_info_length;
uint32_t crc = 0;
uint32_t check_crc;
byte *stream_data;
int stream_data_len;
int err;
if (data_len == 0)
{
print_err("### PMT data has zero length\n");
return 1;
}
if (data == NULL)
{
print_err("### PMT data is NULL\n");
return 1;
}
if (DEBUG) print_data(TRUE,"Data",data,data_len,1000);
// Check the table id (maybe this should be done by our caller?)
table_id = data[0];
if (table_id != 2)
{
// The table_id for a PMT is 2.
// A PAT may also reference user private tables, and I've seen data with
// other table values (including FF) as well:
if (0x03 <= table_id && table_id <=0xFE) // user private table
{
if (verbose)
{
fprint_msg(" 'PMT' with PID %04x is user private table %02x\n",pid,table_id);
print_data(TRUE," Data",data,data_len,20);
}
}
else
{
if (0x03 <= table_id && table_id <= 0x3F)
fprint_err("### PMT table id is %0#x (H.222 / ISO/IEC 13818-1"
" reserved), should be 2\n",table_id);
else
fprint_err("### PMT table id is %0#x (%s), should be 2\n",
table_id,(table_id==0x00?"PAT":
table_id==0x01?"CAT":
table_id==0xFF?"Forbidden":"???"));
print_data(FALSE," Data",data,data_len,20);
}
// Best we can do is to pretend it didn't happen
*pmt = build_pmt(0,0,0); // empty "PMT" with program number 0, PCR PID 0
if (*pmt == NULL) return 1;
return 0;
}
// Check bits
section_syntax_indicator = (data[1] & 0x80) >> 7;
zero_bit = (data[1] & 0x40) >> 6;
reserved = (data[1] & 0x30) >> 4;
if (section_syntax_indicator != 1 && report_bad_reserved_bits)
print_err("!!! PMT: section syntax indicator is 0, not 1\n");
if (zero_bit != 0 && report_bad_reserved_bits)
print_err("!!! PMT: zero bit is 1, not 0\n");
if (reserved != 3 && report_bad_reserved_bits)
fprint_err("!!! PMT: reserved (after zero bit) is %d, not 3\n",reserved);
section_length = ((data[1] & 0xF) << 8) | data[2];
if (verbose)
fprint_msg(" section length: %03x (%d)\n",section_length,section_length);
// If the section length doesn't match our data length, we've got problems
// (remember, the section_length counts bytes after the section_length field)
if (section_length > data_len - 3)
{
fprint_err("### PMT section length %d is more than"
" length of remaining data %d\n",section_length,data_len-3);
return 1;
}
else if (section_length < data_len - 3)
{
fprint_err("!!! PMT section length %d does not use all of"
" remaining data %d\n",section_length,data_len-3);
// Adjust it and carry on
data_len = section_length + 3;
}
program_number = (data[3] << 8) | data[4];
if (verbose)
fprint_msg(" program number: %04x\n",program_number);
reserved = (data[5] & 0xC0) >> 6;
if (reserved != 3 && report_bad_reserved_bits)
fprint_err("!!! PMT: reserved (after program_number)"
" is %d, not 3\n",reserved);
version_number = (data[5] & 0x3E) >> 1;
current_next_indicator = data[5] & 0x1;
section_number = data[6];
last_section_number = data[7];
if (verbose)
fprint_msg(" version number %02x, current next %x, section number %x, last"
" section number %x\n",version_number,current_next_indicator,
section_number,last_section_number);
reserved = (data[8] & 0xE0) >> 5;
if (reserved != 7 && report_bad_reserved_bits)
fprint_err("!!! PMT: reserved (after last_section_number)"
" is %d, not 7\n",reserved);
pcr_pid = ((data[8] & 0x1F) << 8) | data[9];
if (verbose)
fprint_msg(" PCR PID: %04x\n",pcr_pid);
reserved = (data[10] & 0xF0) >> 4;
if (reserved != 0xF && report_bad_reserved_bits)
fprint_err("!!! PMT: reserved (after PCR PID)"
" is %x, not F\n",reserved);
program_info_length = ((data[10] & 0x0F) << 8) | data[11];
if (verbose)
fprint_msg(" program info length: %d\n",program_info_length);
if (verbose && program_info_length > 0)
{
print_msg(" Program info:\n");
print_descriptors(TRUE," ",NULL,&data[12],program_info_length);
}
// 32 bits at the end of a program association section is reserved for a CRC
// (OK, let's extract it stupidly...)
crc = (crc << 8) | data[data_len-4];
crc = (crc << 8) | data[data_len-3];
crc = (crc << 8) | data[data_len-2];
crc = (crc << 8) | data[data_len-1];
// Let's check the CRC
check_crc = crc32_block(0xffffffff,data,data_len);
if (check_crc != 0)
{
fprint_err("!!! Calculated CRC for PMT (PID %04x) is %08x, not 00000000"
" (CRC in data was %08x)\n",pid,check_crc,crc);
// Should we carry on or give up (if "give up", then "!!!" should be "###").
//return 1;
}
// So we can work out the length of the actual program data
// (remember the section length is for the bytes *after* the section
// length field, so for data[3...])
stream_data = data + 12 + program_info_length;
stream_data_len = data_len - 12 - program_info_length - 4; // "-4" == CRC
//print_data(TRUE,"Rest:",stream_data,stream_data_len,1000);
*pmt = build_pmt(program_number,version_number,pcr_pid);
if (*pmt == NULL) return 1;
if (program_info_length > 0)
{
err = set_pmt_program_info(*pmt,program_info_length,&data[12]);
if (err)
{
free_pmt(pmt);
return 1;
}
}
if (verbose)
print_msg(" Program streams:\n");
while (stream_data_len > 0)
{
int stream_type = stream_data[0];
uint32_t pid = ((stream_data[1] & 0x1F) << 8) | stream_data[2];
int ES_info_length = ((stream_data[3] & 0x0F) << 8) | stream_data[4];
if (verbose)
{
fprint_msg(" PID %04x -> Stream %02x %s\n",pid,stream_type,
h222_stream_type_str(stream_type));
if (ES_info_length > 0)
print_descriptors(TRUE," ",NULL,&stream_data[5],ES_info_length);
}
err = add_stream_to_pmt(*pmt,pid,stream_type,ES_info_length,
stream_data+5);
if (err)
{
free_pmt(pmt);
return 1;
}
stream_data = stream_data + 5 + ES_info_length;
stream_data_len = stream_data_len - 5 - ES_info_length;
}
return 0;
}
/*
* Extract the stream list (and PCR PID) from a PMT packet.
*
* Assumes that the whole content of the PMT is in this single packet.
*
* - if `verbose`, then report on what we're doing
* - `payload` is the payload of the TS packet. We know it can't be more
* than 184 bytes long, because of the packet header bytes.
* - regardless, `payload_len` is the actual length of the payload.
* - `pid` is the PID of this TS packet.
* - `program_number` is the program number.
* - `pcr_pid` is the PID of packets containing the PCR, or 0.
* - `stream_list` is a list of stream versus PID.
*
* Returns 0 if all went well, 1 if something went wrong.
*/
extern int extract_stream_list_from_pmt(int verbose,
byte payload[MAX_TS_PAYLOAD_SIZE],
int payload_len,
uint32_t pid,
int *program_number,
uint32_t *pcr_pid,
pidint_list_p *stream_list)
{
byte *data;
int data_len;
int pointer;
int table_id;
int section_syntax_indicator,zero_bit,reserved;
int section_length;
int version_number;
int current_next_indicator;
int section_number;
int last_section_number;
int program_info_length;
uint32_t crc = 0;
uint32_t check_crc;
byte *stream_data;
int stream_data_len;
int err;
if (payload_len == 0)
{
print_err("### PMT payload has zero length\n");
return 1;
}
if (payload == NULL)
{
print_err("### PMT payload is NULL\n");
return 1;
}
pointer = payload[0];
if (pointer > (payload_len - 1))
{
fprint_err("### PMT payload: pointer is %d, which is off the end of"
" the packet (length %d)\n",pointer,payload_len);
return 1;
}
// if (DEBUG) print_data(TRUE,"PMT",payload,payload_len,1000);
data = payload + pointer + 1;
data_len = payload_len - pointer - 1;
if (DEBUG) print_data(TRUE,"Data",data,data_len,1000);
// Check the table id (maybe this should be done by our caller?)
table_id = data[0];
if (table_id != 2)
{
// The table_id for a PMT is 2.
// A PAT may also reference user private tables, and I've seen data with
// other table values (including FF) as well:
if (0x03 <= table_id && table_id <=0xFE) // user private table
{
if (verbose)
{
fprint_msg(" 'PMT' with PID %04x is user private table %02x\n",pid,table_id);
print_data(TRUE," Data",data,data_len,20);
}
}
else
{
if (0x03 <= table_id && table_id <= 0x3F)
fprint_err("### PMT table id is %0#x (H.222 / ISO/IEC 13818-1"
" reserved), should be 2\n",table_id);
else
fprint_err("### PMT table id is %0#x (%s), should be 2\n",
table_id,(table_id==0x00?"PAT":
table_id==0x01?"CAT":
table_id==0xFF?"Forbidden":"???"));
print_data(FALSE," Data",data,data_len,20);
}
// Best we can do is to pretend it didn't happen
*program_number = 0;
*pcr_pid = 0;
*stream_list = NULL;
return 0;
}
// Check bits
section_syntax_indicator = (data[1] & 0x80) >> 7;
zero_bit = (data[1] & 0x40) >> 6;
reserved = (data[1] & 0x30) >> 4;
if (section_syntax_indicator != 1 && report_bad_reserved_bits)
print_err("!!! PMT: section syntax indicator is 0, not 1\n");
if (zero_bit != 0 && report_bad_reserved_bits)
print_err("!!! PMT: zero bit is 1, not 0\n");
if (reserved != 3 && report_bad_reserved_bits)
fprint_err("!!! PMT: reserved (after zero bit) is %d, not 3\n",reserved);
section_length = ((data[1] & 0xF) << 8) | data[2];
if (verbose)
fprint_msg(" section length: %03x (%d)\n",section_length,section_length);
// If the section length continues into another packet, we're not going
// to cope with it. Otherwise, we need to adjust our idea of how long
// the data we want to "read" is.
if (section_length + 3 > data_len)
{
fprint_err("### PMT continues into another packet - section length %d,"
" remaining packet data length %d\n",
section_length,data_len-3);
fprint_err(" This software does not support PMT data spanning"
" multiple TS packets\n");
return 1;
}
data_len = section_length + 3;
*program_number = (data[3] << 8) | data[4];
if (verbose)
fprint_msg(" program number: %04x\n",*program_number);
reserved = (data[5] & 0xC0) >> 6;
if (reserved != 3 && report_bad_reserved_bits)
fprint_err("!!! PMT: reserved (after program_number)"
" is %d, not 3\n",reserved);
version_number = (data[5] & 0x3E) >> 1;
current_next_indicator = data[5] & 0x1;
section_number = data[6];
last_section_number = data[7];
if (verbose)
fprint_msg(" version number %02x, current next %x, section number %x, last"
" section number %x\n",version_number,current_next_indicator,
section_number,last_section_number);
reserved = (data[8] & 0xE0) >> 5;
if (reserved != 7 && report_bad_reserved_bits)
fprint_err("!!! PMT: reserved (after last_section_number)"
" is %d, not 7\n",reserved);
*pcr_pid = ((data[8] & 0x1F) << 8) | data[9];
if (verbose)
fprint_msg(" PCR PID: %04x\n",*pcr_pid);
reserved = (data[10] & 0xF0) >> 4;
if (reserved != 0xF && report_bad_reserved_bits)
fprint_err("!!! PMT: reserved (after PCR PID)"
" is %x, not F\n",reserved);
program_info_length = ((data[10] & 0x0F) << 8) | data[11];
if (verbose)
fprint_msg(" program info length: %d\n",program_info_length);
if (verbose && program_info_length > 0)
{
print_msg(" Program info:\n");
print_descriptors(TRUE," ",NULL,&data[12],program_info_length);
}
// 32 bits at the end of a program association section is reserved for a CRC
// (OK, let's extract it stupidly...)
crc = (crc << 8) | data[data_len-4];
crc = (crc << 8) | data[data_len-3];
crc = (crc << 8) | data[data_len-2];
crc = (crc << 8) | data[data_len-1];
// Let's check the CRC
check_crc = crc32_block(0xffffffff,data,data_len);
if (check_crc != 0)
{
fprint_err("!!! Calculated CRC for PMT (PID %04x) is %08x, not 00000000"
" (CRC in data was %08x)\n",pid,check_crc,crc);
return 1;
}
// So we can work out the length of the actual program data
// (remember the section length is for the bytes *after* the section
// length field, so for data[3...])
stream_data = data + 12 + program_info_length;
stream_data_len = data_len - 12 - program_info_length - 4; // "-4" == CRC
//print_data(TRUE,"Rest:",stream_data,stream_data_len,1000);
err = build_pidint_list(stream_list);
if (err) return 1;
if (verbose)
print_msg(" Program streams:\n");
while (stream_data_len > 0)
{
int stream_type = stream_data[0];
uint32_t pid = ((stream_data[1] & 0x1F) << 8) | stream_data[2];
int ES_info_length = ((stream_data[3] & 0x0F) << 8) | stream_data[4];
if (verbose)
{
#define SARRAYSIZE 40
char buf[SARRAYSIZE];
snprintf(buf,SARRAYSIZE,"(%s)",h222_stream_type_str(stream_type));
// On Windows, snprintf does not guarantee to write a terminating NULL
buf[SARRAYSIZE-1] = '\0';
fprint_msg(" Stream %02x %-40s -> PID %04x\n",stream_type,buf,pid);
if (ES_info_length > 0)
print_descriptors(TRUE," ",NULL,&stream_data[5],ES_info_length);
}
// For the moment, we shan't bother to remember the extra info.
err = append_to_pidint_list(*stream_list,pid,stream_type);
if (err) return 1;
stream_data = stream_data + 5 + ES_info_length;
stream_data_len = stream_data_len - 5 - ES_info_length;
}
return 0;
}
/*
* Split a TS packet into its main parts
*
* - `buf` is the data for the packet
* - `pid` is the PID of said data
* - `payload_unit_start_indicator` is TRUE if any payload in this
* packet forms the start of a PES packet. Its meaning is not significant
* if there is no payload, or if the payload is not (part of) a PES packet.
* - `adapt` is an offset into `buf`, acting as an array of the actual
* adaptation control bytes. It will be NULL if there are no adaptation
* controls.
* - `adapt_len` is the length of the adaptation controls (i.e., the
* number of bytes). It will be 0 if there are no adaptation controls.
* - `payload` is an offset into `buf`, acting as an array of the actual
* payload bytes. It will be NULL if there is no payload.
* - `payload_len` is the length of the payload *in this packet* (i.e., the
* number of bytes. It will be 0 if there is no payload.
*
* Returns 0 if all went well, 1 if something went wrong.
*/
extern int split_TS_packet(byte buf[TS_PACKET_SIZE],
uint32_t *pid,
int *payload_unit_start_indicator,
byte *adapt[],
int *adapt_len,
byte *payload[],
int *payload_len)
{
int adaptation_field_control;
if (buf[0] != 0x47)
{
fprint_err("### TS packet starts %02x, not %02x\n",buf[0],0x47);
return 1;
}
*payload_unit_start_indicator = (buf[1] & 0x40) >> 6;
*pid = ((buf[1] & 0x1f) << 8) | buf[2];
if (*pid == 0x1FFF)
{
// Null packets don't contain any data, so let's not allow "spurious"
// interpretation of their innards
*adapt = NULL;
*adapt_len = 0;
*payload = NULL;
*payload_len = 0;
return 0;
}
adaptation_field_control = (buf[3] & 0x30) >> 4;
switch (adaptation_field_control)
{
case 0:
fprint_err("### Packet PID %04x has adaptation field control = 0\n"
" which is a reserved value (no payload, no adaptation field)\n",
*pid);
*adapt = NULL;
*adapt_len = 0;
*payload = NULL;
*payload_len = 0;
break;
case 1:
// Payload only
*adapt = NULL;
*adapt_len = 0;
*payload = buf + 4;
*payload_len = TS_PACKET_SIZE - 4;
break;
case 2:
// Adaptation field only
*adapt_len = buf[4];
if (*adapt_len == 0)
*adapt = NULL;
else
*adapt = buf + 5;
*payload = NULL;
*payload_len = 0;
break;
case 3:
// Payload and adaptation field
*adapt_len = buf[4];
if (*adapt_len == 0)
*adapt = NULL;
else
*adapt = buf + 5;
*payload = buf + 5 + buf[4];
*payload_len = TS_PACKET_SIZE - 5 - buf[4];
break;
default:
// How this might occur, other than via program error, I can't think.
fprint_err("### Packet PID %04x has adaptation field control %x\n",
*pid,adaptation_field_control);
return 1;
}
return 0;
}
/*
* Return the next TS packet, as payload and adaptation controls.
*
* This is a convenience wrapping of `read_next_TS_packet` and
* `split_TS_packet`. Because of this, the data referenced by `adapt` and
* `payload` will generally not persist over further calls of this function
* and `read_next_TS_packet`, as it is held within the TS reader's read-ahead
* buffer.
*
* - `tsreader` is the TS packet reading context
* - `pid` is the PID of said data
* - `payload_unit_start_indicator` is TRUE if any payload in this
* packet forms the start of a PES packet. Its meaning is not significant
* if there is no payload, or if the payload is not (part of) a PES packet.
* - `adapt` is an offset into `buf`, acting as an array of the actual
* adaptation control bytes. It will be NULL if there are no adaptation
* controls.
* - `adapt_len` is the length of the adaptation controls (i.e., the
* number of bytes). It will be 0 if there are no adaptation controls.
* - `payload` is an offset into `buf`, acting as an array of the actual
* payload bytes. It will be NULL if there is no payload.
* - `payload_len` is the length of the payload *in this packet* (i.e., the
* number of bytes. It will be 0 if there is no payload.
*
* Returns 0 if all went well, EOF if there is no more data, 1 if something
* went wrong.
*/
extern int get_next_TS_packet(TS_reader_p tsreader,
uint32_t *pid,
int *payload_unit_start_indicator,
byte *adapt[],
int *adapt_len,
byte *payload[],
int *payload_len)
{
int err;
byte *packet;
err = read_next_TS_packet(tsreader,&packet);
if (err == EOF)
return EOF;
else if (err)
{
print_err("### Error reading TS packet\n");
return 1;
}
return split_TS_packet(packet,pid,payload_unit_start_indicator,
adapt,adapt_len,payload,payload_len);
}
/*
* Find the first (next) PAT.
*
* - `tsreader` is the TS packet reading context
* - if `max` is non-zero, then it is the maximum number of TS packets to read
* - if `verbose` is true, then output extra information
* - if `quiet` is true, then don't output normal informational messages
* - `num_read` is the number of packets read to find the PAT (or before
* giving up)
* - `prog_list` is the program list from the PAT, or NULL if none was found
*
* Returns 0 if all went well, EOF if no PAT was found,
* 1 if something else went wrong.
*/
extern int find_pat(TS_reader_p tsreader,
int max,
int verbose,
int quiet,
int *num_read,
pidint_list_p *prog_list)
{
int err;
byte *pat_data = NULL;
int pat_data_len = 0;
int pat_data_used = 0;
*prog_list = NULL;
*num_read = 0;
if (!quiet) print_msg("Locating first PAT\n");
for (;;)
{
uint32_t pid;
int payload_unit_start_indicator;
byte *adapt, *payload;
int adapt_len, payload_len;
err = get_next_TS_packet(tsreader,&pid,
&payload_unit_start_indicator,
&adapt,&adapt_len,&payload,&payload_len);
if (err == EOF)
return EOF;
else if (err)
{
print_err("### Error reading TS packet\n");
if (pat_data) free(pat_data);
return 1;
}
(*num_read) ++;
if (pid == 0x0000)
{
if (!quiet)
fprint_msg("Found PAT after reading %d packet%s\n",
*num_read,(*num_read==1?"":"s"));
if (payload_len == 0)
{
print_err("### Packet is PAT, but has no payload\n");
if (pat_data) free(pat_data);
return 1;
}
if (payload_unit_start_indicator && pat_data)
{
// Lose any data we started but didn't complete
print_err("!!! Discarding previous (uncompleted) PAT data\n");
free(pat_data);
pat_data = NULL; pat_data_len = 0; pat_data_used = 0;
}
else if (!payload_unit_start_indicator && !pat_data)
{
print_err("!!! Discarding PAT continuation, no PAT started\n");
continue;
}
err = build_psi_data(verbose,payload,payload_len,pid,
&pat_data,&pat_data_len,&pat_data_used);
if (err)
{
fprint_err("### Error %s PAT\n",
(payload_unit_start_indicator?"starting new":"continuing"));
if (pat_data) free(pat_data);
return 1;
}
if (pat_data_len == pat_data_used)
{
err = extract_prog_list_from_pat(verbose,pat_data,pat_data_len,prog_list);
if (pat_data) free(pat_data);
return err;
}
}
if (max > 0 && *num_read >= max)
{
if (!quiet) fprint_msg("Stopping after %d TS packets\n",max);
if (pat_data) free(pat_data);
return EOF;
}
}
}
/*
* Find the next PMT, and report on it.
*
* - `tsreader` is the TS packet reading context
* - `pmt_pid` is the PID of the PMT we are looking for
* - if `program_number` is -1, then any PMT with that PID is acceptable,
* otherwise we're only interested in a PMT with that PID and the given
* program number.
* - if `max` is non-zero, then it is the maximum number of TS packets to read
* - if `verbose` is true, then output extra information
* - if `quiet` is true, then don't output normal informational messages
* - `num_read` is the number of packets read to find the PMT (or before
* giving up)
* - `pmt` is a new datastructure representing the PMT found
*
* Returns 0 if all went well, EOF if no PMT was found,
* 1 if something else went wrong.
*/
extern int find_next_pmt(TS_reader_p tsreader,
uint32_t pmt_pid,
int program_number,
int max,
int verbose,
int quiet,
int *num_read,
pmt_p *pmt)
{
int err;
byte *pmt_data = NULL;
int pmt_data_len = 0;
int pmt_data_used = 0;
*pmt = NULL;
*num_read = 0;
if (!quiet) print_msg("Locating next PMT\n");
for (;;)
{
uint32_t pid;
int payload_unit_start_indicator;
byte *adapt, *payload;
int adapt_len, payload_len;
err = get_next_TS_packet(tsreader,&pid,
&payload_unit_start_indicator,
&adapt,&adapt_len,&payload,&payload_len);
if (err == EOF)
{
if (pmt_data) free(pmt_data);
return EOF;
}
else if (err)
{
print_err("### Error reading TS packet\n");
if (pmt_data) free(pmt_data);
return 1;
}
(*num_read) ++;
if (pid == pmt_pid)
{
if (!quiet)
fprint_msg("Found %s PMT with PID %04x (%d) after reading %d packet%s\n",
(payload_unit_start_indicator?"start of":"more of"),
pid,pid,*num_read,(*num_read==1?"":"s"));
if (payload_len == 0)
{
fprint_err("### Packet is PMT with PID %04x (%d),"
" but has no payload\n",pid,pid);
if (pmt_data) free(pmt_data);
return 1;
}
if (payload_unit_start_indicator && pmt_data)
{
// Lose any data we started but didn't complete
print_err("!!! Discarding previous (uncompleted) PMT data\n");
free(pmt_data);
pmt_data = NULL; pmt_data_len = 0; pmt_data_used = 0;
}
else if (!payload_unit_start_indicator && !pmt_data)
{
print_err("!!! Discarding PMT continuation, no PMT started\n");
continue;
}
err = build_psi_data(verbose,payload,payload_len,pid,
&pmt_data,&pmt_data_len,&pmt_data_used);
if (err)
{
fprint_err("### Error %s PMT\n",
(payload_unit_start_indicator?"starting new":"continuing"));
if (pmt_data) free(pmt_data);
return 1;
}
if (pmt_data_len == pmt_data_used)
{
int pmt_program_number;
err = extract_pmt(verbose,pmt_data,pmt_data_len,pid,pmt);
pmt_program_number = *pmt == NULL ? -1 : (int)((*pmt)->program_number);
if (pmt_data)
{
free(pmt_data);
pmt_data = NULL;
}
// Check we've got the right program number - it would appear to be
// legitimate to have multiple PMTs carried in the same PID and some
// abuse of this appears to happen in real life
if (err == 0 && program_number >= 0)
{
if (pmt_program_number != program_number)
{
fprint_err("!!! Discarding PMT with program number %d\n", pmt_program_number);
free_pmt(pmt);
continue;
}
}
return err;
}
}
if (max > 0 && *num_read >= max)
{
if (!quiet) fprint_msg("Stopping after %d TS packets\n",max);
if (pmt_data) free(pmt_data);
return EOF;
}
}
}
/*
* Find the next PAT, and from that the next PMT.
*
* Looks for the next PAT in the input stream, and then for the first
* PMT thereafter. If there is more than one program stream in the PAT,
* it looks for the PMT for the first.
*
* - `tsreader` is the TS packet reading context
* - if `max` is non-zero, then it is the maximum number of TS packets to read
* - if `verbose` is true, then output extra information
* - if `quiet` is true, then don't output normal informational messages
* - `num_read` is the number of packets read to find the PMT (or before
* giving up)
* - `pmt` is a new datastructure containing the information from the PMT.
*
* Returns 0 if all went well, EOF if no PAT or PMT was found (and thus
* no program stream), -2 if a PAT was found but it did not contain any
* programs, 1 if something else went wrong.
*/
extern int find_pmt(TS_reader_p tsreader,
const int req_prog_no,
int max,
int verbose,
int quiet,
int *num_read,
pmt_p *pmt)
{
int err;
pidint_list_p prog_list = NULL;
int sofar;
int prog_index = 0;
int prog_no = 0;
*pmt = NULL;
err = find_pat(tsreader,max,verbose,quiet,&sofar,&prog_list);
if (err == EOF)
{
if (!quiet) print_msg("No PAT found\n");
return 1;
}
else if (err)
{
print_err("### Error finding PAT\n");
return 1;
}
if (!quiet)
{
print_msg("\n");
report_pidint_list(prog_list,"Program list","Program",FALSE);
print_msg("\n");
}
if (prog_list->length == 0)
{
if (!quiet) fprint_msg("No programs defined in PAT (packet %d)\n",sofar);
return -2;
}
else if (prog_list->length > 1 && !quiet)
{
if (req_prog_no == 1)
print_msg("Multiple programs in PAT - using the first non-zero\n\n");
else
fprint_msg("Multiple programs in PAT - program %d\n\n", req_prog_no);
}
for (prog_index = 0; prog_index < prog_list->length; ++prog_index)
{
if (prog_list->number[prog_index] == 0)
continue;
if (++prog_no == req_prog_no)
break;
}
if (prog_no == 0)
{
fprint_msg("No non-zero program_numbers in PAT (packet %d)\n",sofar);
return -2;
}
if (prog_no != req_prog_no)
{
fprint_msg("Unable to find program %d in PAT, only found %d (packet %d)\n", req_prog_no, prog_no, sofar);
return -2;
}
// Amend max to take account of the packets we've already read
max -= sofar;
err = find_next_pmt(tsreader,prog_list->pid[prog_index],prog_list->number[prog_index],
max,verbose,quiet,num_read,pmt);
free_pidint_list(&prog_list);
*num_read += sofar;
if (err == EOF)
{
if (!quiet) print_msg("No PMT found\n");
return EOF;
}
else if (err)
{
print_err("### Error finding PMT\n");
return 1;
}
if (!quiet)
{
print_msg("\n");
print_msg("Program map\n");
report_pmt(TRUE," ",*pmt);
print_msg("\n");
}
return 0;
}
// Local Variables:
// tab-width: 8
// indent-tabs-mode: nil
// c-basic-offset: 2
// End:
// vim: set tabstop=8 shiftwidth=2 expandtab: