kopia lustrzana https://github.com/F5OEO/tstools
1673 wiersze
47 KiB
C
1673 wiersze
47 KiB
C
/*
|
||
* Utilities for reading H.264 elementary streams.
|
||
*
|
||
* ***** 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>
|
||
#ifdef _WIN32
|
||
#include <io.h>
|
||
#else // _WIN32
|
||
#include <unistd.h>
|
||
#endif // _WIN32
|
||
|
||
#include "compat.h"
|
||
#include "misc_fns.h"
|
||
#include "pes_fns.h"
|
||
#include "tswrite_fns.h"
|
||
#include "es_fns.h"
|
||
|
||
#define DEBUG 0
|
||
|
||
// A lone forwards reference
|
||
static inline int get_more_data(ES_p es);
|
||
|
||
// ------------------------------------------------------------
|
||
// Basic functions
|
||
// ------------------------------------------------------------
|
||
/*
|
||
* Open an ES file and build an elementary stream datastructure to read
|
||
* it with.
|
||
*
|
||
* - `filename` is the ES files name. As a special case, if this is NULL
|
||
* then standard input (STDIN_FILENO) will be read from.
|
||
*
|
||
* Opens the file for read, builds the datastructure, and reads the first 3
|
||
* bytes of the input file (this is done to prime the triple-byte search
|
||
* mechanism).
|
||
*
|
||
* Returns 0 if all goes well, 1 otherwise.
|
||
*/
|
||
extern int open_elementary_stream(char *filename,
|
||
ES_p *es)
|
||
{
|
||
int err;
|
||
int input;
|
||
|
||
if (filename == NULL)
|
||
input = STDIN_FILENO;
|
||
else
|
||
{
|
||
input = open_binary_file(filename,FALSE);
|
||
if (input == -1) return 1;
|
||
}
|
||
|
||
err = build_elementary_stream_file(input,es);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error building elementary stream for file %s\n",
|
||
filename);
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int setup_readahead(ES_p es)
|
||
{
|
||
int err;
|
||
|
||
es->read_ahead_len = 0;
|
||
es->read_ahead_posn = 0;
|
||
|
||
es->data = NULL;
|
||
es->data_end = NULL;
|
||
es->data_ptr = NULL;
|
||
|
||
es->last_packet_posn = 0;
|
||
es->last_packet_es_data_len = 0;
|
||
|
||
// Try to get the first chunk of data from the file
|
||
err = get_more_data(es);
|
||
if (err) return err;
|
||
|
||
if (es->reading_ES)
|
||
{
|
||
if (es->read_ahead_len < 3)
|
||
{
|
||
fprintf(stderr,"### File only contains %d byte%s\n",
|
||
es->read_ahead_len,(es->read_ahead_len==1?"":"s"));
|
||
return 1;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (es->reader->packet->es_data_len < 3)
|
||
{
|
||
fprintf(stderr,"### File PES packet only contains %d byte%s\n",
|
||
es->reader->packet->es_data_len,
|
||
(es->reader->packet->es_data_len==1?"":"s"));
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
if (DEBUG)
|
||
printf("File starts %02x %02x %02x\n",es->data[0],es->data[1],es->data[2]);
|
||
|
||
// Despite (maybe) reporting the above, we haven't actually read anything
|
||
// yet
|
||
es->prev2_byte = es->prev1_byte = es->cur_byte = 0xFF;
|
||
es->posn_of_next_byte.infile = 0;
|
||
es->posn_of_next_byte.inpacket = 0;
|
||
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Build an elementary stream datastructure attached to an input file.
|
||
* This is intended for reading ES data files.
|
||
*
|
||
* - `input` is the file stream to read from.
|
||
*
|
||
* Builds the datastructure, and reads the first 3 bytes of the input
|
||
* file (this is done to prime the triple-byte search mechanism).
|
||
*
|
||
* Returns 0 if all goes well, 1 otherwise.
|
||
*/
|
||
extern int build_elementary_stream_file(int input,
|
||
ES_p *es)
|
||
{
|
||
ES_p new = malloc(SIZEOF_ES);
|
||
if (new == NULL)
|
||
{
|
||
fprintf(stderr,"### Unable to allocate elementary stream datastructure\n");
|
||
return 1;
|
||
}
|
||
|
||
new->reading_ES = TRUE;
|
||
new->input = input;
|
||
new->reader = NULL;
|
||
|
||
setup_readahead(new);
|
||
|
||
*es = new;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Build an elementary stream datastructure for use with a PES reader.
|
||
* Reads the first (or next) three bytes of the ES.
|
||
*
|
||
* This reads data from the PES video data, ignoring any audio data.
|
||
*
|
||
* - `reader` is the PES reader we want to use to read our TS or PS data.
|
||
*
|
||
* The caller must explicitly close the PES reader as well as closing the
|
||
* elementary stream (closing the ES does not affect the PES reader).
|
||
*
|
||
* Returns 0 if all goes well, 1 otherwise.
|
||
*/
|
||
extern int build_elementary_stream_PES(PES_reader_p reader,
|
||
ES_p *es)
|
||
{
|
||
ES_p new = malloc(SIZEOF_ES);
|
||
if (new == NULL)
|
||
{
|
||
fprintf(stderr,"### Unable to allocate elementary stream datastructure\n");
|
||
return 1;
|
||
}
|
||
|
||
new->reading_ES = FALSE;
|
||
new->input = -1;
|
||
new->reader = reader;
|
||
|
||
setup_readahead(new);
|
||
|
||
*es = new;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Tidy up the elementary stream datastructure after we've finished with it.
|
||
*
|
||
* Specifically:
|
||
*
|
||
* - free the datastructure
|
||
* - set `es` to NULL
|
||
*
|
||
* No return status is given, since there's not much one can do if anything
|
||
* *did* go wrong, and if something went wrong and the program is continuing,
|
||
* it's bound to show up pretty soon.
|
||
*/
|
||
extern void free_elementary_stream(ES_p *es)
|
||
{
|
||
(*es)->input = -1; // "forget" our input
|
||
free(*es);
|
||
*es = NULL;
|
||
}
|
||
|
||
/*
|
||
* Tidy up the elementary stream datastructure after we've finished with it.
|
||
*
|
||
* Specifically:
|
||
*
|
||
* - close the input file (if its stream is set, and if it's not STDIN)
|
||
* - call `free_elementary_stream()`
|
||
*
|
||
* No return status is given, since there's not much one can do if anything
|
||
* *did* go wrong, and if something went wrong and the program is continuing,
|
||
* it's bound to show up pretty soon.
|
||
*/
|
||
extern void close_elementary_stream(ES_p *es)
|
||
{
|
||
int input;
|
||
if (*es == NULL)
|
||
return;
|
||
input = (*es)->input;
|
||
if (input != -1 && input != STDIN_FILENO)
|
||
(void) close_file(input);
|
||
free_elementary_stream(es);
|
||
}
|
||
|
||
/*
|
||
* Ask an ES context if changed input is available.
|
||
*
|
||
* This is a convenience wrapper to save querying the ES context to see
|
||
* if it is (a) reading from PES, (b) automatically writing the PES packets
|
||
* out via a TS writer, and (c) if said TS writer has a changed command.
|
||
*
|
||
* Calls `tswrite_command_changed()` on the TS writer associated with this ES.
|
||
*
|
||
* Returns TRUE if there is a changed command.
|
||
*/
|
||
extern int es_command_changed(ES_p es)
|
||
{
|
||
if (es->reading_ES)
|
||
return FALSE;
|
||
|
||
if (es->reader->tswriter == NULL)
|
||
return FALSE;
|
||
|
||
return tswrite_command_changed(es->reader->tswriter);
|
||
}
|
||
|
||
// ------------------------------------------------------------
|
||
// Handling elementary stream data units
|
||
// ------------------------------------------------------------
|
||
/*
|
||
* Prepare the contents of a (new) ES unit datastructure.
|
||
*
|
||
* Allocates a new data array, and unsets the counts.
|
||
*
|
||
* Returns 0 if it succeeds, 1 if some error occurs.
|
||
*/
|
||
extern int setup_ES_unit(ES_unit_p unit)
|
||
{
|
||
unit->data = malloc(ES_UNIT_DATA_START_SIZE);
|
||
if (unit->data == NULL)
|
||
{
|
||
fprintf(stderr,"### Unable to allocate ES unit data buffer\n");
|
||
return 1;
|
||
}
|
||
unit->data_len = 0;
|
||
unit->data_size = ES_UNIT_DATA_START_SIZE;
|
||
unit->start_posn.infile = 0;
|
||
unit->start_posn.inpacket = 0;
|
||
|
||
unit->PES_had_PTS = FALSE; // See the header file
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Tidy up an ES unit datastructure after we've finished with it.
|
||
*
|
||
* (Frees the internal data array, and unsets the counts)
|
||
*/
|
||
extern void clear_ES_unit(ES_unit_p unit)
|
||
{
|
||
if (unit->data != NULL)
|
||
{
|
||
free(unit->data);
|
||
unit->data = NULL;
|
||
unit->data_size = 0;
|
||
unit->data_len = 0;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Build a new ES unit datastructure.
|
||
*
|
||
* Returns 0 if it succeeds, 1 if some error occurs.
|
||
*/
|
||
extern int build_ES_unit(ES_unit_p *unit)
|
||
{
|
||
int err;
|
||
ES_unit_p new = malloc(SIZEOF_ES_UNIT);
|
||
if (new == NULL)
|
||
{
|
||
fprintf(stderr,"### Unable to allocate ES unit datastructure\n");
|
||
return 1;
|
||
}
|
||
err = setup_ES_unit(new);
|
||
if (err)
|
||
{
|
||
free(new);
|
||
return 1;
|
||
}
|
||
*unit = new;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Build a new ES unit datastructure, from a given data array.
|
||
*
|
||
* Takes a copy of 'data'. Sets 'start_code' appropriately,
|
||
* sets 'start_posn' to (0,0), and 'PES_had_PTS' to FALSE.
|
||
*
|
||
* Returns 0 if it succeeds, 1 if some error occurs.
|
||
*/
|
||
extern int build_ES_unit_from_data(ES_unit_p *unit,
|
||
byte *data,
|
||
uint32_t data_len)
|
||
{
|
||
ES_unit_p new = malloc(SIZEOF_ES_UNIT);
|
||
if (new == NULL)
|
||
{
|
||
fprintf(stderr,"### Unable to allocate ES unit datastructure\n");
|
||
return 1;
|
||
}
|
||
new->data = malloc(data_len);
|
||
if (new->data == NULL)
|
||
{
|
||
fprintf(stderr,"### Unable to allocate ES unit data buffer\n");
|
||
return 1;
|
||
}
|
||
(void) memcpy(new->data, data, data_len);
|
||
new->data_len = data_len;
|
||
new->data_size = data_len;
|
||
new->start_code = data[3];
|
||
new->start_posn.infile = 0;
|
||
new->start_posn.inpacket = 0;
|
||
new->PES_had_PTS = FALSE; // See the header file
|
||
*unit = new;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Tidy up and free an ES unit datastructure after we've finished with it.
|
||
*
|
||
* Empties the ES unit datastructure, frees it, and sets `unit` to NULL.
|
||
*
|
||
* If `unit` is already NULL, does nothing.
|
||
*/
|
||
extern void free_ES_unit(ES_unit_p *unit)
|
||
{
|
||
if (*unit == NULL)
|
||
return;
|
||
clear_ES_unit(*unit);
|
||
free(*unit);
|
||
*unit = NULL;
|
||
}
|
||
|
||
/*
|
||
* Print out some information this ES unit, on the given stream
|
||
*/
|
||
extern void report_ES_unit(FILE *stream,
|
||
ES_unit_p unit)
|
||
{
|
||
byte s = unit->start_code;
|
||
fprintf(stream,OFFSET_T_FORMAT_08 "/%4d: ES unit (%02x '%d%d%d%d %d%d%d%d')",
|
||
unit->start_posn.infile,unit->start_posn.inpacket,s,
|
||
(s&0x80)>>7,(s&0x40)>>6,(s&0x20)>>5,(s&0x10)>>4,
|
||
(s&0x08)>>3,(s&0x04)>>2,(s&0x02)>>1,(s&0x01));
|
||
|
||
// Show the data bytes - but we don't need to show the first 4,
|
||
// since we know they're 00 00 01 <start-code>
|
||
if (unit->data_len > 0)
|
||
{
|
||
int ii;
|
||
int data_len = unit->data_len - 4;
|
||
int show_len = (data_len>10?10:data_len);
|
||
fprintf(stream," %6d:",data_len);
|
||
for (ii = 0; ii < show_len; ii++)
|
||
fprintf(stream," %02x",unit->data[4+ii]);
|
||
if (show_len < data_len)
|
||
fprintf(stream,"...");
|
||
}
|
||
fprintf(stream,"\n");
|
||
}
|
||
|
||
// ------------------------------------------------------------
|
||
// ES unit *data* stuff
|
||
// ------------------------------------------------------------
|
||
/*
|
||
* A wrapper for `read_next_PES_ES_packet()`, to save us forgetting things
|
||
* we need to do when we call it.
|
||
*
|
||
* Returns 0 if it succeeds, EOF if the end-of-file is read, otherwise
|
||
* 1 if some error occurs.
|
||
*/
|
||
static inline int get_next_pes_packet(ES_p es)
|
||
{
|
||
int err;
|
||
PES_reader_p reader = es->reader;
|
||
|
||
// Before reading the *next* packet, remember where the last one was
|
||
if (reader->packet == NULL)
|
||
{
|
||
// What can we do if there was no last packet?
|
||
es->last_packet_posn = 0;
|
||
es->last_packet_es_data_len = 0;
|
||
}
|
||
else
|
||
{
|
||
es->last_packet_posn = reader->packet->posn;
|
||
es->last_packet_es_data_len = reader->packet->es_data_len;
|
||
}
|
||
|
||
err = read_next_PES_ES_packet(es->reader);
|
||
if (err) return err;
|
||
|
||
// Point to our (new) data buffer
|
||
es->data = reader->packet->es_data;
|
||
es->data_end = es->data + reader->packet->es_data_len;
|
||
es->data_ptr = es->data;
|
||
|
||
es->posn_of_next_byte.infile = reader->packet->posn;
|
||
es->posn_of_next_byte.inpacket = 0;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Read some more data into our read-ahead buffer. For a "bare" file,
|
||
* reads the next buffer-full in, and for PES based data, reads the
|
||
* next PES packet that contains ES data.
|
||
*
|
||
* Returns 0 if it succeeds, EOF if the end-of-file is read, otherwise
|
||
* 1 if some error occurs.
|
||
*/
|
||
static inline int get_more_data(ES_p es)
|
||
{
|
||
if (es->reading_ES)
|
||
{
|
||
// Call `read` directly - we don't particularly mind if we get a "short"
|
||
// read, since we'll just catch up later on
|
||
#ifdef _WIN32
|
||
int len = _read(es->input,&es->read_ahead,ES_READ_AHEAD_SIZE);
|
||
#else
|
||
ssize_t len = read(es->input,&es->read_ahead,ES_READ_AHEAD_SIZE);
|
||
#endif
|
||
if (len == 0)
|
||
return EOF;
|
||
else if (len == -1)
|
||
{
|
||
fprintf(stderr,"### Error reading next bytes: %s\n",strerror(errno));
|
||
return 1;
|
||
}
|
||
es->read_ahead_posn += es->read_ahead_len; // length of the *last* buffer
|
||
es->read_ahead_len = len;
|
||
es->data = es->read_ahead; // should be done in the setup function
|
||
es->data_end = es->data + len; // one beyond the last byte
|
||
es->data_ptr = es->data;
|
||
return 0;
|
||
}
|
||
else
|
||
{
|
||
return get_next_pes_packet(es);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Find the start of the next ES unit - i.e., a 00 00 01 start code prefix.
|
||
*
|
||
* Doesn't move the read position if we're already *at* the start of
|
||
* an ES unit.
|
||
*
|
||
* ((new scheme: Leaves the data_ptr set to read the *next* byte, since
|
||
* we know that we've "used up" the 00 00 01 at the start of this unit.))
|
||
*
|
||
* Returns 0 if it succeeds, EOF if the end-of-file is read, otherwise
|
||
* 1 if some error occurs.
|
||
*/
|
||
static int find_ES_unit_start(ES_p es,
|
||
ES_unit_p unit)
|
||
{
|
||
int err;
|
||
byte prev1 = es->prev1_byte;
|
||
byte prev2 = es->prev2_byte;
|
||
|
||
// In almost all cases (hopefully, except for the very start of the file),
|
||
// a previous call to find_ES_unit_end will already have positioned us
|
||
// "over" the start of the next unit
|
||
for (;;)
|
||
{
|
||
byte *ptr;
|
||
for (ptr = es->data_ptr; ptr < es->data_end; ptr++)
|
||
{
|
||
if (prev2 == 0x00 && prev1 == 0x00 && *ptr == 0x01)
|
||
{
|
||
es->prev1_byte = es->prev2_byte = 0x00;
|
||
es->cur_byte = 0x01;
|
||
if (es->reading_ES)
|
||
{
|
||
unit->start_posn.infile = es->read_ahead_posn + (ptr - es->data) - 2;
|
||
}
|
||
else
|
||
{
|
||
unit->start_posn.infile = es->reader->packet->posn;
|
||
unit->start_posn.inpacket = (ptr - es->data) - 2;
|
||
if (unit->start_posn.inpacket < 0)
|
||
{
|
||
unit->start_posn.infile = es->last_packet_posn;
|
||
unit->start_posn.inpacket += es->last_packet_es_data_len;
|
||
}
|
||
// Does the PES packet that we are starting in have a PTS?
|
||
unit->PES_had_PTS = es->reader->packet->has_PTS;
|
||
}
|
||
es->data_ptr = ptr + 1; // the *next* byte to read
|
||
unit->data[0] = 0x00; // i.e., the values we just read
|
||
unit->data[1] = 0x00;
|
||
unit->data[2] = 0x01;
|
||
unit->data_len = 3;
|
||
return 0;
|
||
}
|
||
prev2 = prev1;
|
||
prev1 = *ptr;
|
||
}
|
||
|
||
// We've run out of data - get some more
|
||
err = get_more_data(es);
|
||
if (err) return err;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Find (read to) the end of the current ES unit.
|
||
*
|
||
* Reads to just before the next 00 00 01 start code prefix.
|
||
*
|
||
* H.264 rules would also allow us to read to just before a 00 00 00
|
||
* sequence, but we shall ignore this ability, because when reading
|
||
* ES we want to ensure that there are no bytes "left out" of the ES
|
||
* units, so that the sum of the lengths of all the ES units we read will
|
||
* be the same as the length of data we have read. This is desirable
|
||
* because it makes handling ES via PES much easier (we can, for instance,
|
||
* position to the first ES unit of a picture, and then just read in N
|
||
* bytes, where N is the sum of the lengths of the ES units making up the
|
||
* picture, which is much more efficient than having to read in individual
|
||
* ES units, and takes less room to remember than having to remember the
|
||
* end position (offset of PES packet in file + offset of end of ES unit in
|
||
* PES packet)).
|
||
*
|
||
* ((new scheme: Leaves the data_ptr set to read the current byte again,
|
||
* since we know that, in general, we want to detect it in find_ES_unit_start
|
||
* as the 01 following on from a 00 and a 00.))
|
||
*
|
||
* Returns 0 if it succeeds, otherwise 1 if some error occurs.
|
||
*
|
||
* Note that finding end-of-file is not counted as an error - it is
|
||
* assumed that it is just the natural end of the ES unit.
|
||
*/
|
||
static int find_ES_unit_end(ES_p es,
|
||
ES_unit_p unit)
|
||
{
|
||
int err;
|
||
byte prev1 = es->cur_byte;
|
||
byte prev2 = es->prev1_byte;
|
||
for (;;)
|
||
{
|
||
byte *ptr;
|
||
for (ptr = es->data_ptr; ptr < es->data_end; ptr++)
|
||
{
|
||
// Have we reached the end of our unit?
|
||
// We know we are if we've found the next 00 00 01 start code prefix.
|
||
// (as stated in the header comment above, we're ignoring the H.264
|
||
// ability to end if we've found a 00 00 00 sequence)
|
||
if (prev2 == 0x00 && prev1 == 0x00 && *ptr == 0x01)
|
||
{
|
||
es->data_ptr = ptr; // remember where we've got to
|
||
es->prev2_byte = 0x00; // we know prev1_byte is already 0
|
||
es->cur_byte = 0x01;
|
||
// We've read two 00 bytes we don't need into our data buffer...
|
||
unit->data_len -= 2;
|
||
|
||
if (es->reading_ES)
|
||
{
|
||
es->posn_of_next_byte.infile = es->read_ahead_posn +
|
||
(ptr - es->data) - 2;
|
||
}
|
||
else
|
||
{
|
||
es->posn_of_next_byte.infile = es->reader->packet->posn;
|
||
es->posn_of_next_byte.inpacket = (ptr - es->data) - 2;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
// Otherwise, it's a data byte
|
||
if (unit->data_len == unit->data_size)
|
||
{
|
||
int newsize = unit->data_size + ES_UNIT_DATA_INCREMENT;
|
||
unit->data = realloc(unit->data,newsize);
|
||
if (unit->data == NULL)
|
||
{
|
||
fprintf(stderr,"### Unable to extend ES unit data array\n");
|
||
return 1;
|
||
}
|
||
unit->data_size = newsize;
|
||
}
|
||
unit->data[unit->data_len++] = *ptr;
|
||
|
||
prev2 = prev1;
|
||
prev1 = *ptr;
|
||
}
|
||
|
||
// We've run out of data (ptr == es->data_end) - get some more
|
||
err = get_more_data(es);
|
||
if (err == EOF)
|
||
{
|
||
// Reaching the end of file is a legitimate way of stopping!
|
||
es->data_ptr = ptr; // remember where we've got to
|
||
es->prev2_byte = prev2;
|
||
es->prev1_byte = prev1;
|
||
es->cur_byte = 0xFF; // the notional byte off the end of the file
|
||
//es->cur_byte = *ptr;
|
||
|
||
// Pretend there's a "next byte"
|
||
if (es->reading_ES)
|
||
{
|
||
es->posn_of_next_byte.infile = es->read_ahead_posn + (ptr - es->data);
|
||
}
|
||
else
|
||
{
|
||
es->posn_of_next_byte.inpacket = (ptr - es->data);
|
||
}
|
||
return 0;
|
||
}
|
||
else if (err)
|
||
return err;
|
||
|
||
if (!es->reading_ES)
|
||
{
|
||
// If we update this now, it will be correct when we return,
|
||
// even if we return because of a later EOF
|
||
es->posn_of_next_byte.infile = es->reader->packet->posn;
|
||
|
||
// Does the PES packet that we have just read in have a PTS?
|
||
// If it does, then there's a very good chance (subject to a 00 00 01
|
||
// being split between PES packets) that our ES unit has a PTS "around"
|
||
// it
|
||
if (es->reader->packet->has_PTS)
|
||
unit->PES_had_PTS = TRUE;
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Find and read in the next ES unit.
|
||
*
|
||
* In general, unless there are compelling reasons, use
|
||
* `find_and_build_next_ES_unit()` instead.
|
||
*
|
||
* - `es` is the elementary stream we're reading from.
|
||
* - `unit` is the datastructure into which to read the ES unit
|
||
* - any previous content will be lost.
|
||
*
|
||
* Returns 0 if it succeeds, EOF if the end-of-file is read (i.e., there
|
||
* is no next ES unit), otherwise 1 if some error occurs.
|
||
*/
|
||
extern int find_next_ES_unit(ES_p es,
|
||
ES_unit_p unit)
|
||
{
|
||
int err;
|
||
|
||
err = find_ES_unit_start(es,unit);
|
||
if (err) return err; // 1 or EOF
|
||
|
||
err = find_ES_unit_end(es,unit);
|
||
if (err) return err;
|
||
|
||
// The first byte after the 00 00 01 prefix tells us what sort of thing
|
||
// we've found - we'll be friendly and extract it for the user
|
||
unit->start_code = unit->data[3];
|
||
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Find and read the next ES unit into a new datastructure.
|
||
*
|
||
* - `es` is the elementary stream we're reading from.
|
||
* - `unit` is the datastructure containing the ES unit found, or NULL
|
||
* if there was none.
|
||
*
|
||
* Returns 0 if it succeeds, EOF if the end-of-file is read (i.e., there
|
||
* is no next ES unit), otherwise 1 if some error occurs.
|
||
*/
|
||
extern int find_and_build_next_ES_unit(ES_p es,
|
||
ES_unit_p *unit)
|
||
{
|
||
int err;
|
||
|
||
err = build_ES_unit(unit);
|
||
if (err) return 1;
|
||
|
||
err = find_next_ES_unit(es,*unit);
|
||
if (err)
|
||
{
|
||
free_ES_unit(unit);
|
||
return err;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Write (copy) the current ES unit to the output stream.
|
||
*
|
||
* Note that it writes out all of the data for this ES unit,
|
||
* including its 00 00 01 start code prefix.
|
||
*
|
||
* - `output` is the output stream (file descriptor) to write to
|
||
* - `unit` is the ES unit to write
|
||
*
|
||
* Returns 0 if all went well, 1 if something went wrong.
|
||
*/
|
||
extern int write_ES_unit(FILE *output,
|
||
ES_unit_p unit)
|
||
{
|
||
size_t written = fwrite(unit->data,1,unit->data_len,output);
|
||
if (written != unit->data_len)
|
||
{
|
||
fprintf(stderr,"### Error writing out ES unit data: %s\n"
|
||
" Wrote %ld bytes instead of %d\n",
|
||
strerror(errno),(long int)written,unit->data_len);
|
||
return 1;
|
||
}
|
||
else
|
||
return 0;
|
||
}
|
||
|
||
// ------------------------------------------------------------
|
||
// Arbitrary reading from ES data
|
||
// ------------------------------------------------------------
|
||
/*
|
||
* Seek within PES underlying ES.
|
||
*
|
||
* This should only be used to seek to data that starts with 00 00 01.
|
||
*
|
||
* "Unsets" the triple byte context.
|
||
*
|
||
* Returns 0 if all goes well, 1 if something goes wrong.
|
||
*/
|
||
static int seek_in_PES(ES_p es,
|
||
ES_offset where)
|
||
{
|
||
int err;
|
||
|
||
if (es->reader == NULL)
|
||
{
|
||
fprintf(stderr,"### Attempt to seek in PES for an ES reader that"
|
||
" is not attached to a PES reader\n");
|
||
return 1;
|
||
}
|
||
|
||
// Force the reader to forget its current packet
|
||
if (es->reader->packet != NULL)
|
||
free_PES_packet_data(&es->reader->packet);
|
||
|
||
// Seek to the right packet in the PES data
|
||
err = set_PES_reader_position(es->reader,where.infile);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error seeking for PES packet at " OFFSET_T_FORMAT
|
||
"\n",where.infile);
|
||
return 1;
|
||
}
|
||
// Read the PES packet containing ES (ignoring packets we don't care about)
|
||
err = get_next_pes_packet(es);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error reading PES packet at " OFFSET_T_FORMAT "/%d\n",
|
||
where.infile,where.inpacket);
|
||
return 1;
|
||
}
|
||
|
||
// Now sort out the byte offset
|
||
if (where.inpacket > es->reader->packet->es_data_len)
|
||
{
|
||
fprintf(stderr,"### Error seeking PES packet at " OFFSET_T_FORMAT "/%d: "
|
||
" packet ES data is only %d bytes long\n",where.infile,
|
||
where.inpacket,es->reader->packet->es_data_len);
|
||
return 1;
|
||
}
|
||
es->posn_of_next_byte = where;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Update our current position information after a seek or direct read.
|
||
*/
|
||
static inline void deduce_correct_position(ES_p es)
|
||
{
|
||
// We don't know what the previous three bytes were, but we (strongly)
|
||
// assume that they were not 00 00 01
|
||
es->cur_byte = 0xff;
|
||
es->prev1_byte = 0xff;
|
||
es->prev2_byte = 0xff;
|
||
|
||
if (es->reading_ES)
|
||
{
|
||
// For ES data, we want to force new data to be read in from the file
|
||
es->data_ptr = es->data_end = NULL;
|
||
es->read_ahead_len = 0; // to stop the read ahead posn being incremented
|
||
es->read_ahead_posn = es->posn_of_next_byte.infile;
|
||
}
|
||
else
|
||
{
|
||
// For PES data, we have whatever is left in the current packet
|
||
PES_packet_data_p packet = es->reader->packet;
|
||
es->data = packet->es_data;
|
||
es->data_ptr = packet->es_data + es->posn_of_next_byte.inpacket;
|
||
es->data_end = packet->es_data + packet->es_data_len;
|
||
// And, of course, we have no idea about the *previous* packet in the file
|
||
es->last_packet_posn = es->last_packet_es_data_len = 0;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* "Seek" to the given position in the ES data, which is assumed to
|
||
* be an offset ready to read a 00 00 01 sequence.
|
||
*
|
||
* If the ES reader is using PES to read its data, then both fields
|
||
* of `where` are significant, but if the underlying file *is* just a file,
|
||
* only `where.infile` is used.
|
||
*
|
||
* Returns 0 if all went well, 1 is something went wrong
|
||
*/
|
||
extern int seek_ES(ES_p es,
|
||
ES_offset where)
|
||
{
|
||
int err;
|
||
if (es->reading_ES)
|
||
{
|
||
err = seek_file(es->input,where.infile);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error seeking within ES file\n");
|
||
return 1;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
err = seek_in_PES(es,where);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,
|
||
"### Error seeking within ES over PES (offset " OFFSET_T_FORMAT
|
||
"/%d)\n",where.infile,where.inpacket);
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
// And make it look as if we reached this position sensibly
|
||
es->posn_of_next_byte = where;
|
||
deduce_correct_position(es);
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Retrieve ES bytes from PES as requested
|
||
*
|
||
* Leaves the PES reader set to read on after this data.
|
||
*
|
||
* Returns 0 if all goes well, 1 if something goes wrong.
|
||
*/
|
||
static int read_bytes_from_PES(ES_p es,
|
||
byte *data,
|
||
uint32_t num_bytes)
|
||
{
|
||
int err;
|
||
int offset = 0;
|
||
int num_bytes_wanted = num_bytes;
|
||
int32_t from = es->posn_of_next_byte.inpacket;
|
||
int32_t num_bytes_left = es->reader->packet->es_data_len - from;
|
||
|
||
for (;;)
|
||
{
|
||
if (num_bytes_left < num_bytes_wanted)
|
||
{
|
||
memcpy(&(data[offset]),&(es->reader->packet->es_data[from]),
|
||
num_bytes_left);
|
||
offset += num_bytes_left;
|
||
num_bytes_wanted -= num_bytes_left;
|
||
err = get_next_pes_packet(es);
|
||
if (err) return err;
|
||
from = 0;
|
||
num_bytes_left = es->reader->packet->es_data_len;
|
||
}
|
||
else
|
||
{
|
||
memcpy(&(data[offset]),&(es->reader->packet->es_data[from]),
|
||
num_bytes_wanted);
|
||
from += num_bytes_wanted;
|
||
break;
|
||
}
|
||
}
|
||
es->posn_of_next_byte.inpacket = from;
|
||
//es->posn_of_next_byte.infile = es->reader->packet->posn;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Read in some ES data from disk.
|
||
*
|
||
* Suitable for use when reading in a set of ES units whose bounds
|
||
* (start offset and total number of bytes) have been remembered.
|
||
*
|
||
* "Seeks" to the given position in the ES data, which is assumed to
|
||
* be an offset ready to read a 00 00 01 sequence, and reads data thereafter.
|
||
*
|
||
* After this function, the triple byte context is set to FF FF FF, and the
|
||
* position of said bytes are undefined, but the next position to read a byte
|
||
* from *is* defined.
|
||
*
|
||
* The intent is to allow the caller to have a data array (`data`) that
|
||
* always contains the last data read, and is of the required size, and
|
||
* need only be freed when no more data is needed.
|
||
*
|
||
* - `es` is where to read our data from
|
||
* - `start_posn` is the file offset to start reading at
|
||
* - `num_bytes` is how many bytes we want to read
|
||
* - `data_len` may be NULL or a pointer to a value.
|
||
* If it is NULL, then the data array will be reallocated to size
|
||
* `num_bytes` regardless. If it is non-NULL, it should be passed *in*
|
||
* as the size that `data` *was*, and will be returned as the size
|
||
* that `data` is when the function returns.
|
||
* - `data` is the data array to read into. If this is NULL, or if `num_bytes`
|
||
* is NULL, or if `num_bytes` is greater than `data_len`, then it will be
|
||
* reallocated to size `num_bytes`.
|
||
*
|
||
* Returns 0 if all went well, 1 if something went wrong.
|
||
*/
|
||
extern int read_ES_data(ES_p es,
|
||
ES_offset start_posn,
|
||
uint32_t num_bytes,
|
||
uint32_t *data_len,
|
||
byte **data)
|
||
{
|
||
int err;
|
||
if (*data == NULL || data_len == NULL || num_bytes > *data_len)
|
||
{
|
||
*data = realloc(*data,num_bytes);
|
||
if (*data == NULL)
|
||
{
|
||
fprintf(stderr,"### Unable to reallocate data space\n");
|
||
return 1;
|
||
}
|
||
if (data_len != NULL)
|
||
*data_len = num_bytes;
|
||
}
|
||
err = seek_ES(es,start_posn);
|
||
if (err) return err;
|
||
if (es->reading_ES)
|
||
{
|
||
err = read_bytes(es->input,num_bytes,*data);
|
||
if (err)
|
||
{
|
||
if (err == EOF)
|
||
{
|
||
fprintf(stderr,"### Error (EOF) reading %d bytes\n",num_bytes);
|
||
return 1;
|
||
}
|
||
else
|
||
return err;
|
||
}
|
||
es->posn_of_next_byte.infile = start_posn.infile + num_bytes;
|
||
}
|
||
else
|
||
{
|
||
err = read_bytes_from_PES(es,*data,num_bytes);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error reading %d bytes from PES\n",num_bytes);
|
||
return 1;
|
||
}
|
||
}
|
||
// Make it look as if we "read" to this position by the normal means,
|
||
// but ensure that we have no data left "in hand"
|
||
//
|
||
// We could leave it up to our caller to do this, on the assumption that
|
||
// they're likely to call us several times when, for example, reversing,
|
||
// without wanting to read onwards on all but the last of those occasions.
|
||
// That would, indeed, save some time each time we are called, but it would
|
||
// also allow our caller to forget to do this, with rather bad results.
|
||
//
|
||
// So, since it shouldn't really take very long...
|
||
deduce_correct_position(es);
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Retrieve ES data from the end of a PES packet. It is assumed (i.e, things
|
||
* will go wrong if it is not true) that at least one ES unit has been read
|
||
* from the PES data stream via the ES reader.
|
||
*
|
||
* - `es` is our ES reader. It must be reading ES from PES packets.
|
||
* - `data` is the ES data remaining (to be read) in the current PES packet.
|
||
* It is up to the caller to free this data.
|
||
* - `data_len` is the length of said data. If this is 0, then `data`
|
||
* will be NULL.
|
||
*
|
||
* Returns 0 if all goes well, 1 if an error occurs.
|
||
*/
|
||
extern int get_end_of_underlying_PES_packet(ES_p es,
|
||
byte **data,
|
||
int *data_len)
|
||
{
|
||
int32_t offset;
|
||
|
||
if (es->reading_ES)
|
||
{
|
||
fprintf(stderr,"### Cannot retrieve end of PES packet - the ES data"
|
||
" is direct ES, not ES read from PES\n");
|
||
return 1;
|
||
}
|
||
if (es->reader->packet == NULL)
|
||
{
|
||
// This is naughty, but we'll pretend to cope
|
||
*data = NULL;
|
||
*data_len = 0;
|
||
return 0;
|
||
}
|
||
|
||
// The offset (in this packet) of the next ES byte to read.
|
||
// We assume that this must also be the offset of the first byte
|
||
// of the next ES unit (or, at least, of one of the 00 bytes that
|
||
// come before it).
|
||
offset = es->posn_of_next_byte.inpacket;
|
||
|
||
// The way that we read (using our "triple byte" mechanism) means that
|
||
// we will generally already have read the start of the next ES unit.
|
||
// Life gets interesting if the 00 00 01 triplet (or, possibly, 00 00 00
|
||
// triplet - but we're not supporting that option for the moment - see
|
||
// find_ES_unit_end for details) is split over a PES packet boundary.
|
||
|
||
// So we know we 00 00 01 to "start" a new ES unit and end the previous
|
||
// one. (In fact, even if it was 00 00 00, the relevant values are held in
|
||
// our triple byte memory, so we don't particularly care which it is.)
|
||
//
|
||
// If offset is 0, then then next byte to read is the first byte of
|
||
// this packet's ES data, so we need to "pretend" to have all three
|
||
// of the triple bytes "in front of" the actual ES data for this PES
|
||
// packet.
|
||
//
|
||
// If offset is 1, then presumably the cur_byte was at offset 0, and
|
||
// we have two "dangling" bytes in the previous packet.
|
||
//
|
||
// If offset is 2, then there would only be one "dangling" byte.
|
||
//
|
||
// Finally, if offset is 3 or more, we know there was room for the
|
||
// 00 00 01 or 00 00 00 before the next byte we'll read, so we don't
|
||
// need to bluff at all.
|
||
|
||
// So, to calculation - we must remember to leave room for those
|
||
// three bytes at the start of the data we return
|
||
*data_len = es->reader->packet->es_data_len - offset + 3;
|
||
*data = malloc(*data_len);
|
||
if (*data == NULL)
|
||
{
|
||
fprintf(stderr,"### Cannot allocate space for rest of PES packet\n");
|
||
return 1;
|
||
}
|
||
(*data)[0] = es->prev2_byte; // Hmm - should be 0x00
|
||
(*data)[1] = es->prev1_byte; // Hmm - should be 0x00
|
||
(*data)[2] = es->cur_byte; // Hmm - should be 0x01 (see above)
|
||
memcpy( &((*data)[3]),
|
||
&(es->reader->packet->es_data[offset]),
|
||
(*data_len) - 3);
|
||
|
||
return 0;
|
||
}
|
||
|
||
// ------------------------------------------------------------
|
||
// Lists of ES units
|
||
// ------------------------------------------------------------
|
||
/*
|
||
* Build a new list-of-ES-units datastructure.
|
||
*
|
||
* Returns 0 if it succeeds, 1 if some error occurs.
|
||
*/
|
||
extern int build_ES_unit_list(ES_unit_list_p *list)
|
||
{
|
||
ES_unit_list_p new = malloc(SIZEOF_ES_UNIT_LIST);
|
||
if (new == NULL)
|
||
{
|
||
fprintf(stderr,"### Unable to allocate ES unit list datastructure\n");
|
||
return 1;
|
||
}
|
||
|
||
new->length = 0;
|
||
new->size = ES_UNIT_LIST_START_SIZE;
|
||
new->array = malloc(SIZEOF_ES_UNIT*ES_UNIT_LIST_START_SIZE);
|
||
if (new->array == NULL)
|
||
{
|
||
free(new);
|
||
fprintf(stderr,
|
||
"### Unable to allocate array in ES unit list datastructure\n");
|
||
return 1;
|
||
}
|
||
*list = new;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Add a copy of an ES unit to the end of the ES unit list
|
||
*
|
||
* Note that since this takes a copy of the ES unit's data, it is safe
|
||
* to free the original ES unit.
|
||
*
|
||
* Returns 0 if it succeeds, 1 if some error occurs.
|
||
*/
|
||
extern int append_to_ES_unit_list(ES_unit_list_p list,
|
||
ES_unit_p unit)
|
||
{
|
||
ES_unit_p ptr;
|
||
if (list->length == list->size)
|
||
{
|
||
int newsize = list->size + ES_UNIT_LIST_INCREMENT;
|
||
list->array = realloc(list->array,newsize*SIZEOF_ES_UNIT);
|
||
if (list->array == NULL)
|
||
{
|
||
fprintf(stderr,"### Unable to extend ES unit list array\n");
|
||
return 1;
|
||
}
|
||
list->size = newsize;
|
||
}
|
||
ptr = &list->array[list->length++];
|
||
// Some things can be copied directly
|
||
*ptr = *unit;
|
||
// But some need adjusting
|
||
ptr->data = malloc(unit->data_len);
|
||
if (ptr->data == NULL)
|
||
{
|
||
fprintf(stderr,"### Unable to copy ES unit data array\n");
|
||
return 1;
|
||
}
|
||
memcpy(ptr->data,unit->data,unit->data_len);
|
||
ptr->data_size = unit->data_len;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Tidy up an ES unit list datastructure after we've finished with it.
|
||
*/
|
||
static inline void clear_ES_unit_list(ES_unit_list_p list)
|
||
{
|
||
if (list->array != NULL)
|
||
{
|
||
int ii;
|
||
for (ii=0; ii<list->length; ii++)
|
||
{
|
||
clear_ES_unit(&list->array[ii]);
|
||
}
|
||
free(list->array);
|
||
list->array = NULL;
|
||
}
|
||
list->length = 0;
|
||
list->size = 0;
|
||
}
|
||
|
||
/*
|
||
* Reset (empty) an ES unit list.
|
||
*/
|
||
extern void reset_ES_unit_list(ES_unit_list_p list)
|
||
{
|
||
if (list->array != NULL)
|
||
{
|
||
int ii;
|
||
for (ii=0; ii<list->length; ii++)
|
||
{
|
||
clear_ES_unit(&list->array[ii]);
|
||
}
|
||
// We *could* also shrink it - as it is, it will never get smaller
|
||
// than its maximum size. Is that likely to be a problem?
|
||
}
|
||
list->length = 0;
|
||
}
|
||
|
||
/*
|
||
* Tidy up and free an ES unit list datastructure after we've finished with it.
|
||
*
|
||
* Clears the datastructure, frees it and returns `list` as NULL.
|
||
*
|
||
* Does nothing if `list` is already NULL.
|
||
*/
|
||
extern void free_ES_unit_list(ES_unit_list_p *list)
|
||
{
|
||
if (*list == NULL)
|
||
return;
|
||
clear_ES_unit_list(*list);
|
||
free(*list);
|
||
*list = NULL;
|
||
}
|
||
|
||
/*
|
||
* Report on an ES unit list's contents.
|
||
*
|
||
* - `stream` is where to write the information
|
||
* - `name` is the name of the list (used in the header)
|
||
* - `list` is the list to report on
|
||
*/
|
||
extern void report_ES_unit_list(FILE *stream,
|
||
char *name,
|
||
ES_unit_list_p list)
|
||
{
|
||
fprintf(stream,"ES unit list '%s': ",name);
|
||
if (list->array == NULL)
|
||
fprintf(stream,"<empty>\n");
|
||
else
|
||
{
|
||
int ii;
|
||
fprintf(stream,"%d item%s (size %d)\n",list->length,
|
||
(list->length==1?"":"s"),list->size);
|
||
for (ii=0; ii<list->length; ii++)
|
||
{
|
||
fprintf(stream," ");
|
||
report_ES_unit(stream,&(list->array[ii]));
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Retrieve the bounds of this ES unit list in the file it was read from.
|
||
*
|
||
* - `list` is the ES unit list we're interested in
|
||
* - `start` is its start position (i.e., the location at which to start
|
||
* reading to retrieve all of the data for the list)
|
||
* - `length` is the total length of the ES units within this list
|
||
*
|
||
* Returns 0 if all goes well, 1 if the ES unit list has no content.
|
||
*/
|
||
extern int get_ES_unit_list_bounds(ES_unit_list_p list,
|
||
ES_offset *start,
|
||
uint32_t *length)
|
||
{
|
||
int ii;
|
||
if (list->array == NULL || list->length == 0)
|
||
{
|
||
fprintf(stderr,
|
||
"### Cannot determine bounds of an ES unit list with no content\n");
|
||
return 1;
|
||
}
|
||
|
||
*start = list->array[0].start_posn;
|
||
*length = 0;
|
||
|
||
// Maybe we should precalculate, or even cache, the total length...
|
||
for (ii=0; ii<list->length; ii++)
|
||
(*length) += list->array[ii].data_len;
|
||
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Compare two ES unit lists. The comparison does not include the start
|
||
* position of the unit data, but just the actual data - i.e., two unit lists
|
||
* read from different locations in the input stream may be considered the
|
||
* same if their data content is identical.
|
||
*
|
||
* - `list1` and `list2` are the two ES unit lists to compare.
|
||
*
|
||
* Returns TRUE if the lists contain identical content, FALSE otherwise.
|
||
*/
|
||
extern int same_ES_unit_list(ES_unit_list_p list1,
|
||
ES_unit_list_p list2)
|
||
{
|
||
int ii;
|
||
if (list1 == list2)
|
||
return TRUE;
|
||
|
||
if (list1->array == NULL)
|
||
return (list2->array == NULL);
|
||
|
||
if (list1->length != list2->length)
|
||
return FALSE;
|
||
|
||
for (ii = 0; ii < list1->length; ii++)
|
||
{
|
||
ES_unit_p unit1 = &list1->array[ii];
|
||
ES_unit_p unit2 = &list2->array[ii];
|
||
|
||
if (unit1->data_len != unit2->data_len)
|
||
return FALSE;
|
||
|
||
if (memcmp(unit1->data,unit2->data,unit1->data_len))
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/*
|
||
* Compare two ES offsets
|
||
*
|
||
* Returns -1 if offset1 < offset2, 0 if they are the same, and 1 if
|
||
* offset1 > offset2.
|
||
*/
|
||
extern int compare_ES_offsets(ES_offset offset1,
|
||
ES_offset offset2)
|
||
{
|
||
if (offset1.infile < offset2.infile)
|
||
return -1;
|
||
else if (offset1.infile > offset2.infile)
|
||
return 1;
|
||
else if (offset1.inpacket < offset2.inpacket)
|
||
return -1;
|
||
else if (offset1.inpacket > offset2.inpacket)
|
||
return 1;
|
||
else
|
||
return 0;
|
||
}
|
||
|
||
// ============================================================
|
||
// Simple file type guessing
|
||
// ============================================================
|
||
/*
|
||
* Is an ES unit H.262 or H.264 (or not sure?)
|
||
*
|
||
* Return 0 if all goes well, 1 if things go wrong.
|
||
*/
|
||
static int try_to_guess_video_type(ES_unit_p unit,
|
||
int show_reasoning,
|
||
int *maybe_h264,
|
||
int *maybe_h262,
|
||
int *maybe_avs)
|
||
{
|
||
|
||
byte nal_ref_idc = 0;
|
||
byte nal_unit_type = 0;
|
||
|
||
if (show_reasoning)
|
||
printf("Looking at ES unit with start code %02X\n",unit->start_code);
|
||
|
||
// The following are *not allowed*
|
||
//
|
||
// - In AVS: B4, B8 and B9..FF are system start codes
|
||
// - In H.262: B0, B1, B6 and B9..FF are system start codes
|
||
// - In H.264: Anything with top bit set
|
||
|
||
if (unit->start_code == 0xBA) // PS pack header
|
||
{
|
||
fprintf(stderr,
|
||
"### ES unit start code is 0xBA, which looks like a PS pack"
|
||
" header\n i.e., data may be PS\n");
|
||
return 1;
|
||
}
|
||
|
||
if (unit->start_code >= 0xB9) // system start code - probably PES
|
||
{
|
||
fprintf(stderr,
|
||
"### ES unit start code %02X is more than 0xB9, which is probably"
|
||
" a PES system start code\n i.e., data may be PES, "
|
||
"and is thus probably PS or TS\n",
|
||
unit->start_code);
|
||
return 1;
|
||
}
|
||
|
||
if (unit->start_code & 0x80) // top bit set means not H.264
|
||
{
|
||
if (*maybe_h264)
|
||
{
|
||
if (show_reasoning) printf(" %02X has top bit set, so not H.264,\n",unit->start_code);
|
||
*maybe_h264 = FALSE;
|
||
}
|
||
|
||
if (unit->start_code == 0xB0 ||
|
||
unit->start_code == 0xB1 ||
|
||
unit->start_code == 0xB6)
|
||
{
|
||
*maybe_h262 = FALSE;
|
||
if (show_reasoning)
|
||
printf(" Start code %02X is reserved in H.262, so not H.262\n",
|
||
unit->start_code);
|
||
}
|
||
else if (unit->start_code == 0xB4 ||
|
||
unit->start_code == 0xB8)
|
||
{
|
||
*maybe_avs = FALSE;
|
||
if (show_reasoning)
|
||
printf(" Start code %02X is reserved in AVS, so not AVS\n",
|
||
unit->start_code);
|
||
}
|
||
}
|
||
else if (*maybe_h264)
|
||
{
|
||
if (show_reasoning)
|
||
printf(" Top bit not set, so might be H.264\n");
|
||
|
||
// If we don't have that top bit set, then we need to work a bit harder
|
||
nal_ref_idc = (unit->start_code & 0x60) >> 5;
|
||
nal_unit_type = (unit->start_code & 0x1F);
|
||
|
||
if (show_reasoning)
|
||
printf(" Interpreting it as nal_ref_idc %d, nal_unit_type %d\n",
|
||
nal_ref_idc,nal_unit_type);
|
||
|
||
if (nal_unit_type > 12 && nal_unit_type < 24)
|
||
{
|
||
if (show_reasoning)
|
||
printf(" H.264 reserves nal_unit_type %02X,"
|
||
" so not H.264\n",nal_unit_type);
|
||
*maybe_h264 = FALSE;
|
||
}
|
||
else if (nal_unit_type > 23)
|
||
{
|
||
if (show_reasoning)
|
||
printf(" H.264 does not specify nal_unit_type %02X,"
|
||
" so not H.264\n",nal_unit_type);
|
||
*maybe_h264 = FALSE;
|
||
}
|
||
else if (nal_ref_idc == 0)
|
||
{
|
||
if (nal_unit_type == 5 || // IDR picture
|
||
nal_unit_type == 7 || // sequence parameter set
|
||
nal_unit_type == 8) // picture parameter set
|
||
{
|
||
if (show_reasoning)
|
||
printf(" H.264 does not allow nal_ref_idc 0 and nal_unit_type %d,"
|
||
" so not H.264\n",nal_unit_type);
|
||
*maybe_h264 = FALSE;
|
||
}
|
||
}
|
||
else // nal_ref_idc is NOT 0
|
||
{
|
||
// Which means it should *not* be:
|
||
if (nal_unit_type == 6 || // SEI
|
||
nal_unit_type == 9 || // access unit delimiter
|
||
nal_unit_type == 10 || // end of sequence
|
||
nal_unit_type == 11 || // end of stream
|
||
nal_unit_type == 12) // fille
|
||
{
|
||
if (show_reasoning)
|
||
printf(" H.264 insists nal_ref_idc shall be 0 for nal_unit_type %d,"
|
||
" so not H.264\n",nal_unit_type);
|
||
*maybe_h264 = FALSE;
|
||
}
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Look at the start of an elementary stream to try to determine its
|
||
* video type.
|
||
*
|
||
* "Eats" the ES units that it looks at, and doesn't rewind the stream
|
||
* afterwards.
|
||
*
|
||
* - `es` is the ES file
|
||
* - if `print_dots` is true, print a dot for each ES unit that is inspected
|
||
* - if `show_reasoning` is true, then output messages explaining how the
|
||
* decision is being made
|
||
* - `video_type` is the final decision -- one of VIDEO_H264, VIDEO_H262,
|
||
* VIDEO_AVS, or VIDEO_UNKNOWN.
|
||
*
|
||
* Returns 0 if all goes well, 1 if something goes wrong
|
||
*/
|
||
extern int decide_ES_video_type(ES_p es,
|
||
int print_dots,
|
||
int show_reasoning,
|
||
int *video_type)
|
||
{
|
||
int err;
|
||
int ii;
|
||
int maybe_h262 = TRUE;
|
||
int maybe_h264 = TRUE;
|
||
int maybe_avs = TRUE;
|
||
int decided = FALSE;
|
||
|
||
struct ES_unit unit;
|
||
|
||
*video_type = VIDEO_UNKNOWN;
|
||
|
||
err = setup_ES_unit(&unit);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error trying to setup ES unit before"
|
||
" working out video type\n");
|
||
return 1;
|
||
}
|
||
|
||
// Otherwise, look at the first 500 packets to see if we can tell
|
||
//
|
||
// Basically, if we find anything with the top byte as B, then it is
|
||
// not H.264. Since H.262 allows up to AF (175) slices (which start with
|
||
// 01..AF), and AVS the same (or perhaps one more) and each "surrounds" those
|
||
// with entities with top byte B, it's rather hard to see how we could go
|
||
// very far without finding something with the top bit of the high byte set
|
||
// (certainly not as far as 500 units). So if we *do* go that far, we can be
|
||
// *very* sure it is not H.262 or AVS. And if the only other choice is H.264,
|
||
// then...
|
||
if (show_reasoning)
|
||
printf("Looking through first 500 ES units to try to decide video type\n");
|
||
for (ii=0; ii<500; ii++)
|
||
{
|
||
if (print_dots)
|
||
{
|
||
printf(".");
|
||
fflush(stdout);
|
||
}
|
||
else if (show_reasoning)
|
||
printf("%d: ",ii+1);
|
||
|
||
err = find_next_ES_unit(es,&unit);
|
||
if (err == EOF)
|
||
{
|
||
if (print_dots) printf("\n");
|
||
if (show_reasoning) printf("End of file, trying to read ES unit %d\n",ii+2);
|
||
break;
|
||
}
|
||
else if (err)
|
||
{
|
||
if (print_dots) printf("\n");
|
||
fprintf(stderr,
|
||
"### Error trying to find 'unit' %d in ES whilst"
|
||
" working out video type\n",ii+2);
|
||
clear_ES_unit(&unit);
|
||
return 1;
|
||
}
|
||
err = try_to_guess_video_type(&unit,show_reasoning,
|
||
&maybe_h264,&maybe_h262,&maybe_avs);
|
||
if (err)
|
||
{
|
||
if (print_dots) printf("\n");
|
||
fprintf(stderr,
|
||
"### Whilst trying to work out video_type\n");
|
||
clear_ES_unit(&unit);
|
||
return 1;
|
||
}
|
||
|
||
if (maybe_h264 && !maybe_h262 && !maybe_avs)
|
||
{
|
||
if (show_reasoning) printf(" Which leaves only H.264\n");
|
||
*video_type = VIDEO_H264;
|
||
decided = TRUE;
|
||
}
|
||
else if (!maybe_h264 && maybe_h262 && !maybe_avs)
|
||
{
|
||
if (show_reasoning) printf(" Which leaves only H.262\n");
|
||
*video_type = VIDEO_H262;
|
||
decided = TRUE;
|
||
}
|
||
else if (!maybe_h264 && !maybe_h262 && maybe_avs)
|
||
{
|
||
if (show_reasoning) printf(" Which leaves only AVS\n");
|
||
*video_type = VIDEO_AVS;
|
||
decided = TRUE;
|
||
}
|
||
else
|
||
{
|
||
if (show_reasoning)
|
||
printf(" It is not possible to decide from that start code\n");
|
||
}
|
||
if (decided)
|
||
break;
|
||
}
|
||
if (print_dots) printf("\n");
|
||
clear_ES_unit(&unit);
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Look at the start of an elementary stream to try to determine it's
|
||
* video type.
|
||
*
|
||
* Note that it is easier to prove something is H.262 (or AVS) than to prove
|
||
* that it is H.264, and that the result of this routine is a best-guess, not a
|
||
* guarantee.
|
||
*
|
||
* Rewinds back to the original position in the file after it has finished.
|
||
*
|
||
* - `input` is the file to look at
|
||
* - if `print_dots` is true, print a dot for each ES unit that is inspected
|
||
* - if `show_reasoning` is true, then output messages explaining how the
|
||
* decision is being made
|
||
* - `video_type` is the final decision -- one of VIDEO_H264, VIDEO_H262,
|
||
* VIDEO_AVS, or VIDEO_UNKNOWN.
|
||
*
|
||
* Returns 0 if all goes well, 1 if something goes wrong
|
||
*/
|
||
extern int decide_ES_file_video_type(int input,
|
||
int print_dots,
|
||
int show_reasoning,
|
||
int *video_type)
|
||
{
|
||
offset_t start_posn;
|
||
int err;
|
||
ES_p es = NULL;
|
||
|
||
start_posn = tell_file(input);
|
||
if (start_posn == -1)
|
||
{
|
||
fprintf(stderr,"### Error remembering start position in file before"
|
||
" working out video type\n");
|
||
return 1;
|
||
}
|
||
|
||
err = seek_file(input,0);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error rewinding file before"
|
||
" working out video type\n");
|
||
return 1;
|
||
}
|
||
|
||
err = build_elementary_stream_file(input,&es);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error starting elementary stream before"
|
||
" working out video type\n");
|
||
return 1;
|
||
}
|
||
|
||
err = decide_ES_video_type(es,print_dots,show_reasoning,video_type);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error deciding video type of file\n");
|
||
free_elementary_stream(&es);
|
||
return 1;
|
||
}
|
||
|
||
free_elementary_stream(&es);
|
||
|
||
err = seek_file(input,start_posn);
|
||
if (err)
|
||
{
|
||
fprintf(stderr,"### Error returning to start position in file after"
|
||
" working out video type\n");
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
// Local Variables:
|
||
// tab-width: 8
|
||
// indent-tabs-mode: nil
|
||
// c-basic-offset: 2
|
||
// End:
|
||
// vim: set tabstop=8 shiftwidth=2 expandtab:
|