F5OEO-tstools/es.c

1665 wiersze
47 KiB
C

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*
* 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 "printing_fns.h"
#include "misc_fns.h"
#include "pes_fns.h"
#include "tswrite_fns.h"
#include "es_fns.h"
#include "printing_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)
{
fprint_err("### 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)
{
fprint_err("### 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)
{
fprint_err("### 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)
fprint_msg("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)
{
print_err("### 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)
{
print_err("### 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)
{
print_err("### 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)
{
print_err("### 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)
{
print_err("### Unable to allocate ES unit datastructure\n");
return 1;
}
new->data = malloc(data_len);
if (new->data == NULL)
{
print_err("### 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 normal or error output
*/
extern void report_ES_unit(int is_msg,
ES_unit_p unit)
{
byte s = unit->start_code;
fprint_msg_or_err(is_msg,
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);
fprint_msg_or_err(is_msg," %6d:",data_len);
for (ii = 0; ii < show_len; ii++)
fprint_msg_or_err(is_msg," %02x",unit->data[4+ii]);
if (show_len < data_len)
fprint_msg_or_err(is_msg,"...");
}
fprint_msg_or_err(is_msg,"\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)
{
fprint_err("### 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)
{
print_err("### 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)
{
fprint_err("### 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)
{
print_err("### 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)
{
fprint_err("### 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)
{
fprint_err("### 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)
{
fprint_err("### 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)
{
print_err("### Error seeking within ES file\n");
return 1;
}
}
else
{
err = seek_in_PES(es,where);
if (err)
{
fprint_err("### 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)
{
print_err("### 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)
{
fprint_err("### 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)
{
fprint_err("### 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)
{
fprint_err("### 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)
{
print_err("### 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)
{
print_err("### 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);
print_err("### 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)
{
print_err("### 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)
{
print_err("### 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.
*
* - `name` is the name of the list (used in the header)
* - `list` is the list to report on
*/
extern void report_ES_unit_list(char *name,
ES_unit_list_p list)
{
fprint_msg("ES unit list '%s': ",name);
if (list->array == NULL)
print_msg("<empty>\n");
else
{
int ii;
fprint_msg("%d item%s (size %d)\n",list->length,
(list->length==1?"":"s"),list->size);
for (ii=0; ii<list->length; ii++)
{
print_msg(" ");
report_ES_unit(TRUE,&(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)
{
print_err("### 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)
fprint_msg("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
{
print_err("### 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
{
fprint_err("### 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) fprint_msg(" %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)
fprint_msg(" 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)
fprint_msg(" Start code %02X is reserved in AVS, so not AVS\n",
unit->start_code);
}
}
else if (*maybe_h264)
{
if (show_reasoning)
print_msg(" 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)
fprint_msg(" 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)
fprint_msg(" 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)
fprint_msg(" 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)
fprint_msg(" 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)
fprint_msg(" 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)
{
print_err("### 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)
print_msg("Looking through first 500 ES units to try to decide video type\n");
for (ii=0; ii<500; ii++)
{
if (print_dots)
{
print_msg(".");
fflush(stdout);
}
else if (show_reasoning)
fprint_msg("%d: ",ii+1);
err = find_next_ES_unit(es,&unit);
if (err == EOF)
{
if (print_dots) print_msg("\n");
if (show_reasoning) fprint_msg("End of file, trying to read ES unit %d\n",ii+2);
break;
}
else if (err)
{
if (print_dots) print_msg("\n");
fprint_err("### 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) print_msg("\n");
print_err("### Whilst trying to work out video_type\n");
clear_ES_unit(&unit);
return 1;
}
if (maybe_h264 && !maybe_h262 && !maybe_avs)
{
if (show_reasoning) print_msg(" Which leaves only H.264\n");
*video_type = VIDEO_H264;
decided = TRUE;
}
else if (!maybe_h264 && maybe_h262 && !maybe_avs)
{
if (show_reasoning) print_msg(" Which leaves only H.262\n");
*video_type = VIDEO_H262;
decided = TRUE;
}
else if (!maybe_h264 && !maybe_h262 && maybe_avs)
{
if (show_reasoning) print_msg(" Which leaves only AVS\n");
*video_type = VIDEO_AVS;
decided = TRUE;
}
else
{
if (show_reasoning)
print_msg(" It is not possible to decide from that start code\n");
}
if (decided)
break;
}
if (print_dots) print_msg("\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)
{
print_err("### Error remembering start position in file before"
" working out video type\n");
return 1;
}
err = seek_file(input,0);
if (err)
{
print_err("### Error rewinding file before working out video type\n");
return 1;
}
err = build_elementary_stream_file(input,&es);
if (err)
{
print_err("### 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)
{
print_err("### 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)
{
print_err("### 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: