F5OEO-tstools/tsplay_innards.c

838 wiersze
26 KiB
C

/*
* This is the core functionality used by tsplay to play (stream) TS packets.
*
* It is abstracted here so that it can be used in other contexts.
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the MPEG TS, PS and ES tools.
*
* The Initial Developer of the Original Code is Amino Communications Ltd.
* Portions created by the Initial Developer are Copyright (C) 2008
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Amino Communications Ltd, Swavesey, Cambridge UK
*
* ***** END LICENSE BLOCK *****
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <math.h>
#ifdef _WIN32
#include <stddef.h>
#else // _WIN32
#include <unistd.h>
#endif // _WIN32
#include <time.h> // Sleeping and timing
#include "compat.h"
#include "printing_fns.h"
#include "ts_fns.h"
#include "ps_fns.h"
#include "pes_fns.h"
#include "misc_fns.h"
#include "printing_fns.h"
#include "tsplay_fns.h"
#include "tswrite_fns.h"
#include "pidint_fns.h"
// ============================================================
// Common TS packet reading code
// ============================================================
/*
* Read the next TS packet, coping with looping, etc.
*
* - `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
* - `got_PCR` is TRUE if the adaptation field of this packet contains a PCR
* - `pcr` is then the PCR value itself
* - 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.
*/
static int read_TS_packet(TS_reader_p tsreader,
uint32_t *count,
byte *data[TS_PACKET_SIZE],
uint32_t *pid,
int *got_pcr,
uint64_t *pcr,
int max,
int loop,
offset_t start_posn,
uint32_t start_count,
int quiet)
{
int err;
int payload_unit_start_indicator;
byte *adapt;
int adapt_len;
byte *payload;
int payload_len;
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
while ((err = read_next_TS_packet(tsreader,data)) != 0)
{
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 = 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",*count);
return 1;
}
get_PCR_from_adaptation_field(adapt,adapt_len,got_pcr,pcr);
(*count) ++;
return 0;
}
/*
* Read TS packets until we have found the PCR PID for our program stream,
* outputting packets (without using their PCR) as we go.
*
* - `tsreader` is the TS reader context
* - `tswriter` is our (buffered) writer
* - `pcr_pid` is the PID containing PCRs as indicated by the PMT
* - `num_read` is how many TS packets we read
* - if `max` is greater than zero, then at most `max` TS packets should
* be read from the input
* - if `quiet` is true, then only error messages should be written out
*
* Returns 0 if all went well, 1 if something went wrong.
*/
static int find_PCR_PID(TS_reader_p tsreader,
TS_writer_p tswriter,
uint32_t *pcr_pid,
uint32_t *num_read,
int max,
int quiet)
{
int err;
int count = 0;
byte *data;
uint32_t pid;
int payload_unit_start_indicator;
byte *adapt;
int adapt_len;
byte *payload;
int payload_len;
int got_PAT = FALSE;
pidint_list_p prog_list = NULL;
pmt_p pmt = NULL;
uint32_t pmt_pid = 0; // safe initial value
byte *pat_data = NULL;
int pat_data_len = 0;
int pat_data_used = 0;
byte *pmt_data = NULL;
int pmt_data_len = 0;
int pmt_data_used = 0;
int pmt_program_number = -1;
for (;;)
{
err = read_next_TS_packet(tsreader,&data);
if (err == EOF)
{
fprint_err("### EOF (after %d TS packets), before finding program"
" information\n",count);
if (pmt_data) free(pmt_data);
return 1;
}
else if (err)
{
fprint_err("### Error reading TS packet %d\n",count+1);
if (pmt_data) free(pmt_data);
return 1;
}
count++;
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",count);
if (pmt_data) free(pmt_data);
return 1;
}
// Whatever we've found, don't forget to write it out via the
// circular buffer (and we *know* it doesn't have a PCR that is
// useful to us, as yet)
err = tswrite_write(tswriter,data,pid,FALSE,0);
if (err)
{
fprint_err("### Error writing TS packet %d to circular buffer\n",
count);
if (pmt_data) free(pmt_data);
return 1;
}
if (pid == 0x0000 && !got_PAT)
{
if (!quiet) fprint_msg("Packet %d is PAT\n",count);
if (payload_unit_start_indicator && pat_data)
{
// This is the start of a new PAT packet, but we'd already
// started one, so throw its data away
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)
{
// This is the continuation of a PAT packet, but we hadn't
// started one yet
print_err("!!! Discarding PAT continuation, no PAT started\n");
continue;
}
err = build_psi_data(FALSE,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);
continue;
}
// Do we need more data to complete this PAT?
if (pat_data_len > pat_data_used)
continue;
err = extract_prog_list_from_pat(FALSE,pat_data,pat_data_len,&prog_list);
if (err != 0)
{
free(pat_data);
continue;
}
if (!quiet)
report_pidint_list(prog_list,"Program list","Program",FALSE);
if (prog_list->length > 1 && !quiet)
print_msg("Multiple programs in PAT - using the first\n\n");
pmt_pid = prog_list->pid[0];
pmt_program_number = prog_list->number[0];
got_PAT = TRUE;
free_pidint_list(&prog_list);
free(pat_data);
pat_data = NULL; pat_data_len = 0; pat_data_used = 0;
}
else if (got_PAT && pid == pmt_pid)
{
if (!quiet)
fprint_msg("Packet %d %s PMT with PID %04x\n",
count, payload_unit_start_indicator?"starts":"continues",
pmt_pid);
if (payload_unit_start_indicator && pmt_data)
{
// This is the start of a new PMT packet, but we'd already
// started one, so throw its data away
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)
{
// This is the continuation of a PMT packet, but we hadn't
// started one yet
print_err("!!! Discarding PMT continuation, no PMT started\n");
continue;
}
err = build_psi_data(FALSE,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;
}
// Do we need more data to complete this PMT?
if (pmt_data_len > pmt_data_used)
continue;
err = extract_pmt(FALSE,pmt_data,pmt_data_len,pmt_pid,&pmt);
free(pmt_data);
pmt_data = NULL;
if (err) return err;
if (pmt->program_number != pmt_program_number)
{
if (!quiet)
fprint_msg("Discarding PMT program %d - looking for %d\n",
pmt->program_number, pmt_program_number);
free_pmt(&pmt);
continue;
}
if (!quiet)
report_pmt(TRUE," ",pmt);
*pcr_pid = pmt->PCR_pid;
free_pmt(&pmt);
if (!quiet)
fprint_msg("Taking timing information from PID 0x%03x\n",*pcr_pid);
*num_read = count;
return 0;
}
if (max > 0 && count >= max)
{
fprint_err("### Stopping after %d TS packets, before finding program"
" information\n",max);
if (pmt_data) free(pmt_data);
return 1;
}
}
}
// ============================================================
// Play the TS data
// ============================================================
/*
* Read TS packets and then output them, using the buffered approach
* so that we read-ahead to get the next PCR, and thus have reliable
* timing information.
*
* Assumes (strongly) that it is starting from the start of the file.
*
* - `tsreader` is the TS reader context
* - `tswriter` is our (maybe buffered) writer
* - if `pid_to_ignore` is non-zero, then any TS packets with that PID
* will not be written out (note: any PCR information in them may still
* be used)
* - if `override_pcr_pid` is non-zero, then it is the PID to use for PCRs,
* ignoring any value found in a PMT
* - 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)
* - if `quiet` is true, then only error messages should be written out
* - if `verbose` is true, then give extra progress messages
*
* Returns 0 if all went well, 1 if something went wrong.
*/
static int play_buffered_TS_packets(TS_reader_p tsreader,
TS_writer_p tswriter,
uint32_t pid_to_ignore,
uint32_t override_pcr_pid,
int max,
int loop,
int quiet,
int verbose)
{
int err;
int total = 0;
uint32_t count = 0;
uint32_t pcr_pid;
uint32_t start_count = 0; // which TS packet to loop from
offset_t start_posn = 0;
// These are only used in the loop below, but the compiler grumbles if
// they're uninitialised (it isn't sure if they're being set by the call
// to read_buffered_TS_packet() or not). I don't want to have to keep
// thinking about the compiler warning, but I also know that these values
// *will* be set by the function, so I don't want them reinitialised
// every time round the loop. So hoist them back up to here...
byte *data = NULL;
uint32_t pid = 0;
uint64_t pcr = 0;
// Before we can use PCRs for timing, we need to read a PMT which tells us
// what our video stream is (so we can get our PCRs therefrom).
if (override_pcr_pid)
{
pcr_pid = override_pcr_pid;
if (!quiet)
fprint_msg("Forcing use of PCR PID 0x%03x (%d)\n",pcr_pid,pcr_pid);
}
else
{
err = find_PCR_PID(tsreader,tswriter,&pcr_pid,&start_count,max,quiet);
if (err)
{
fprint_err("### Unable to find PCR PID for timing information\n"
" Looked in first %d TS packets\n",max);
return 1;
}
}
// Once we've found that, we're ready to play our data
err = prime_read_buffered_TS_packet(tsreader,pcr_pid);
if (err) return 1;
// If we're looping, remember the location of the first packet of (probable)
// data - there's not much point rewinding before that point
if (loop)
start_posn = start_count * TS_PACKET_SIZE;
count = start_count;
for (;;)
{
err = read_buffered_TS_packet(tsreader,&count,&data,&pid,&pcr,
max,loop,start_posn,start_count,quiet);
if (err == EOF) // shouldn't occur if `loop`
break;
else if (err)
{
if (tsreader->file != STDIN_FILENO)
{
fprint_err("### Last TS packet read was at " LLU_FORMAT "\n",
(uint64_t)count * TS_PACKET_SIZE);
}
return 1;
}
total ++;
// If we've been asked to ignore this packet, we should be able to
// just ignore it -- since all TS packets have their time associated
// with them, we shouldn't need to send a "dummy" packet, just in
// case it had time on it.
if (pid_to_ignore != 0 && pid == pid_to_ignore)
continue;
// And write it out via the circular buffer
err = tswrite_write(tswriter,data,pid,TRUE,pcr);
if (err)
{
fprint_err("### Error writing TS packet %d to circular buffer\n",
count);
return 1;
}
if (!quiet && verbose && total%TSPLAY_REPORT_EVERY == 0)
fprint_msg("Transferred %d TS packets\n",total);
}
if (!quiet)
fprint_msg("Transferred %d TS packet%s in total\n",total,(total==1?"":"s"));
return 0;
}
/*
* Read TS packets and then output them.
*
* Assumes (strongly) that it is starting from the start of the file.
*
* - `tsreader` is the TS reader context
* - `tswriter` is our (maybe buffered) writer
* - if `pid_to_ignore` is non-zero, then any TS packets with that PID
* will not be written out (note: any PCR information in them may still
* be used)
* - 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)
* - if `quiet` is true, then only error messages should be written out
* - if `verbose` is true, then give extra progress messages
*
* Returns 0 if all went well, 1 if something went wrong.
*/
static int play_TS_packets(TS_reader_p tsreader,
TS_writer_p tswriter,
const tsplay_output_pace_mode pace_mode,
uint32_t pid_to_ignore,
int max,
int loop,
int quiet,
int verbose)
{
int err;
int total = 0;
uint32_t count = 0;
int pcrs_used = 0;
int pcrs_ignored = 0;
uint32_t pcr_pid = ~0U;
uint32_t start_count = 0; // which TS packet to loop from
offset_t start_posn = 0;
if (pace_mode == TSPLAY_OUTPUT_PACE_PCR2_PMT)
{
// Before we can use PCRs for timing, we need to read a PMT which tells us
// what our video stream is (so we can get our PCRs therefrom).
err = find_PCR_PID(tsreader,tswriter,&pcr_pid,&start_count,max,quiet);
if (err)
{
fprint_err("### Unable to find PCR PID for timing information\n"
" Looked in first %d TS packets\n",max);
return 1;
}
// Once we've found that, we're ready to play our data
// If we're looping, remember the location of the first packet of (probable)
// data - there's not much point rewinding before that point
if (loop)
start_posn = start_count * TS_PACKET_SIZE;
}
count = start_count;
for (;;)
{
byte *data;
uint32_t pid;
int got_pcr;
uint64_t pcr = 0;
err = read_TS_packet(tsreader,&count,&data,&pid,&got_pcr,&pcr,
max,loop,start_posn,start_count,quiet);
if (err == EOF) // shouldn't occur if `loop`
break;
else if (err)
{
if (tsreader->file != STDIN_FILENO)
{
fprint_err("### Last TS packet read was at " LLU_FORMAT "\n",
(uint64_t)count * TS_PACKET_SIZE);
}
return 1;
}
if (count == start_count + 1)
tswrite_discontinuity(tswriter);
total ++;
// We are only interested in timing information from our PCR PID stream
if (got_pcr)
{
// If 1st PCR we see then remember its pid
if (pcr_pid == ~0U)
{
fprint_msg("PCR PID set to 1st seen: %#x (%d)\n", pid, pid);
pcr_pid = pid;
}
if (pid == pcr_pid)
pcrs_used ++;
else
{
if (pcrs_ignored == 0)
{
fprint_msg("Other PCR PIDs seen: %#x (%d)...\n", pid, pid);
}
pcrs_ignored ++;
got_pcr = FALSE;
}
}
if (pid_to_ignore != 0 && pid == pid_to_ignore)
{
// We want to "transmit" this packet, since that's the simplest
// way of sending its timing information (if any) to the writer.
// However, we don't want to *actually* send meaningful data.
// The simplest thing is to ignore it if it doesn't have a PCR:
// and otherwise, change it to a null packet, by resetting its PID.
if (!got_pcr)
continue;
else
{
data[2] = 0xFF;
data[1] |= 0x1F;
}
}
// And write it out via the circular buffer
err = tswrite_write(tswriter,data,pid,got_pcr,pcr);
if (err)
{
fprint_err("### Error writing TS packet %d to circular buffer\n",
count);
return 1;
}
if (!quiet && verbose && total%TSPLAY_REPORT_EVERY == 0)
fprint_msg("Transferred %d TS packets\n",total);
}
if (!quiet)
{
fprint_msg("Transferred %d TS packet%s in total\n",total,(total==1?"":"s"));
fprint_msg("Used PCRs from %d packets, ignored PCRs from %d packets\n",
pcrs_used,pcrs_ignored);
}
return 0;
}
/*
* Read TS packets and then output them.
*
* Assumes (strongly) that it is starting from the start of the file.
*
* - `input` is the input stream (descriptor) to read
* - `tswriter` is our (maybe buffered) writer
* - if `pid_to_ignore` is non-zero, then any TS packets with that PID
* will not be written out (note: any PCR information in them may still
* be used)
* - if `scan_for_PCRs`, use a read-ahead buffer to find the *next* PCR,
* and thus allow exact timing of packets.
* - if we are using the PCR read-ahead buffer, and `override_pcr_pid` is
* non-zero, then it is the PID to use for PCRs, ignoring any value found in
* a PMT
* - 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)
* - if `quiet` is true, then only error messages should be written out
* - if `verbose` is true, then give extra progress messages
*
* Returns 0 if all went well, 1 if something went wrong.
*/
extern int play_TS_stream(int input,
TS_writer_p tswriter,
const tsplay_output_pace_mode pace_mode,
uint32_t pid_to_ignore,
uint32_t override_pcr_pid,
int max,
int loop,
int quiet,
int verbose)
{
int err;
TS_reader_p tsreader;
err = build_TS_reader(input,&tsreader);
if (err) return 1;
fprint_msg("pace_mode=%d\n", pace_mode);
if (pace_mode == TSPLAY_OUTPUT_PACE_PCR1)
err = play_buffered_TS_packets(tsreader,tswriter,pid_to_ignore,
override_pcr_pid,max,loop,quiet,verbose);
else
err = play_TS_packets(tsreader, tswriter, pace_mode, pid_to_ignore,
max,loop,quiet,verbose);
if (err)
{
free_TS_reader(&tsreader);
return 1;
}
free_TS_reader(&tsreader);
return 0;
}
/*
* Read PS packets and then output them as TS.
*
* - `input` is the program stream
* - `output` is the transport stream
* - `pad_start` is the number of filler TS packets to start the output
* with.
* - `program_repeat` is how often (after how many PS packs) to repeat
* the program information (PAT/PMT)
* - `want_h264` should be true to indicate that the video stream is H.264
* (ISO/IEC 14496-2, MPEG-4/AVC), false if it is H.262 (ISO/IEC 13818-3,
* MPEG-2, or indeed 11172-3, MPEG-1)
* - `input_is_dvd` indicates if the PS data came from a DVD, and thus follows
* its conventions for private_stream_1 and AC-3/DTS/etc. substreams
* - `video_stream` indicates which video stream we want - i.e., the stream
* with id 0xE0 + <video_stream> - and -1 means the first video stream found.
* - `audio_stream` indicates which audio stream we want. If `want_ac3_audio`
* is false, then this will be the stream with id 0xC0 + <audio_stream>, or,
* if it is -1, the first audio stream found.
* - if `want_ac3_audio` is true, then if `is_dvd` is true, then we want
* audio from private_stream_1 (0xBD) with substream id <audio_stream>,
* otherwise we ignore `audio_stream` and assume that all data in
* private_stream_1 is the audio we want.
* - `want_dolby_as_dvb` indicates if any Dolby (AC-3) audio data should be output
* with DVB or ATSC stream type
* - `pmt_pid` is the PID of the PMT to write
* - `pcr_pid` is the PID of the TS unit containing the PCR
* - `video_pid` is the PID for the video we write
* - `keep_audio` is true if the audio stream should be output, false if
* it should be ignored
* - `audio_pid` is the PID for the audio we write
* - if `max` is non-zero, then we want to stop reading after we've read
* `max` packets
* - if `loop`, play the input file repeatedly (up to `max` TS packets
* if applicable)
* - if `verbose` then we want to output diagnostic information
* (nb: only applies to first time if looping is enabled)
* - if `quiet` then we want to be as quiet as we can
* (nb: only applies to first time if looping is enabled)
*
* Returns 0 if all went well, 1 if something went wrong.
*/
extern int play_PS_stream(int input,
TS_writer_p output,
int pad_start,
int program_repeat,
int force_stream_type,
int want_h262,
int input_is_dvd,
int video_stream,
int audio_stream,
int want_ac3_audio,
int want_dolby_as_dvb,
uint32_t pmt_pid,
uint32_t pcr_pid,
uint32_t video_pid,
int keep_audio,
uint32_t audio_pid,
int max,
int loop,
int verbose,
int quiet)
{
int err;
int is_h264;
PS_reader_p ps;
err = build_PS_reader(input,quiet,&ps);
if (err)
{
print_err("### Error building PS reader for input\n");
return 1;
}
if (force_stream_type)
{
is_h264 = !want_h262;
if (!quiet)
fprint_msg("Reading input as %s\n",(want_h262?"MPEG-2 (H.262)":
"MPEG-4/AVC (H.264)"));
}
else
{
err = determine_if_PS_is_h264(ps,&is_h264);
if (err) return 1;
if (!quiet)
fprint_msg("Video appears to be %s\n",
(is_h264?"MPEG-4/AVC (H.264)":"MPEG-2 (H.262)"));
}
err = ps_to_ts(ps,output,pad_start,program_repeat,
is_h264,input_is_dvd,
video_stream,audio_stream,want_ac3_audio,
want_dolby_as_dvb,pmt_pid,pcr_pid,
video_pid,keep_audio,audio_pid,max,verbose,quiet);
if (err)
{
if (loop)
print_err("!!! Ignoring error and looping\n");
else
{
free_PS_reader(&ps);
return 1;
}
}
if (loop)
{
for (;;)
{
if (!quiet) print_msg("Rewinding and continuing\n");
err = rewind_program_stream(ps);
if (err)
{
print_err("### Error rewinding\n");
free_PS_reader(&ps);
return 1;
}
err = ps_to_ts(ps,output,pad_start,program_repeat,
is_h264,input_is_dvd,
video_stream,audio_stream,want_ac3_audio,
want_dolby_as_dvb,pmt_pid,pcr_pid,
video_pid,keep_audio,audio_pid,max,FALSE,TRUE);
if (err)
{
if (loop)
print_err("!!! Ignoring error and looping\n");
else
{
free_PS_reader(&ps);
return 1;
}
}
}
}
return 0;
}
// Local Variables:
// tab-width: 8
// indent-tabs-mode: nil
// c-basic-offset: 2
// End:
// vim: set tabstop=8 shiftwidth=2 expandtab: