F5OEO-tstools/accessunit.c

1437 wiersze
46 KiB
C

/*
* Utilities for working with access units in 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 "compat.h"
#include "printing_fns.h"
#include "es_fns.h"
#include "ts_fns.h"
#include "nalunit_fns.h"
#include "accessunit_fns.h"
#include "reverse_fns.h"
#define DEBUG 0
/*
* Build a new access unit datastructure.
*
* Returns 0 if it succeeds, 1 if some error occurs.
*/
static inline int build_access_unit(access_unit_p *acc_unit,
uint32_t index)
{
int err;
access_unit_p new = malloc(SIZEOF_ACCESS_UNIT);
if (new == NULL)
{
print_err("### Unable to allocate access unit datastructure\n");
return 1;
}
err = build_nal_unit_list(&(new->nal_units));
if (err)
{
free(new);
*acc_unit = NULL;
return err;
}
new->index = index;
new->started_primary_picture = FALSE;
new->primary_start = NULL;
new->ignored_broken_NAL_units = 0;
new->frame_num = new->field_pic_flag = new->bottom_field_flag = 0;
*acc_unit = new;
return 0;
}
/*
* Tidy up an access unit datastructure after we've finished with it.
*
* If `deep` is TRUE, also frees all of the NAL units in the NAL unit
* list (which is normally what we want to do).
*/
static inline void clear_access_unit(access_unit_p acc_unit,
int deep)
{
free_nal_unit_list(&(acc_unit->nal_units),deep);
acc_unit->primary_start = NULL;
}
/*
* Tidy up and free an access unit datastructure after we've finished with it.
*
* Clears the datastructure, frees it, and returns `acc_unit` as NULL.
*
* Does nothing if `acc_unit` is already NULL.
*/
extern void free_access_unit(access_unit_p *acc_unit)
{
if (*acc_unit == NULL)
return;
clear_access_unit(*acc_unit,TRUE);
free(*acc_unit);
*acc_unit = NULL;
}
/*
* Report on this access unit
*/
extern void report_access_unit(access_unit_p access_unit)
{
int ii;
fprint_msg("Access unit %u",access_unit->index);
if (access_unit->started_primary_picture)
fprint_msg(" (%s)",access_unit->primary_start->start_reason);
print_msg(":\n");
if (access_unit->field_pic_flag)
fprint_msg(" %s field of frame %u\n",
(access_unit->bottom_field_flag==1?"Bottom":"Top"),
access_unit->frame_num);
else
fprint_msg(" Frame %u\n",access_unit->frame_num);
if (access_unit->ignored_broken_NAL_units)
fprint_msg(" Ignored %d broken NAL unit%s\n",
access_unit->ignored_broken_NAL_units,
(access_unit->ignored_broken_NAL_units==1?"":"s"));
for (ii=0; ii<access_unit->nal_units->length; ii++)
{
nal_unit_p nal = access_unit->nal_units->array[ii];
if (nal == NULL)
print_msg(" <null>\n");
else
{
fprint_msg(" %c",((access_unit->primary_start == nal)?'*':' '));
report_nal(TRUE,nal);
}
}
}
/*
* How many slices (VCL NAL units) are there in this access unit?
*/
static inline int num_slices(access_unit_p access_unit)
{
int count = 0;
int ii;
for (ii=0; ii<access_unit->nal_units->length; ii++)
{
if (nal_is_slice(access_unit->nal_units->array[ii]))
count ++;
}
return count;
}
/*
* Retrieve the bounds of this access unit in the file it was read from.
*
* - `access_unit` is the access unit 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 access unit, including
* the 00 00 01 prefix at the start of the first NAL unit therein)
* - `length` is the total length of the NAL units within this access unit
*
* Returns 0 if all goes well, 1 if the access unit has no content.
*/
extern int get_access_unit_bounds(access_unit_p access_unit,
ES_offset *start,
uint32_t *length)
{
int ii;
if (access_unit->primary_start == NULL)
{
print_err("### Cannot determine bounds of an access unit with no content\n");
return 1;
}
*start = access_unit->nal_units->array[0]->unit.start_posn;
*length = 0;
// Maybe we should precalculate, or even cache, the total length...
for (ii=0; ii<access_unit->nal_units->length; ii++)
(*length) += access_unit->nal_units->array[ii]->unit.data_len;
return 0;
}
/*
* Are all slices in this access unit I slices?
*/
extern int all_slices_I(access_unit_p access_unit)
{
int ii;
if (access_unit->primary_start == NULL)
return FALSE;
if (!nal_is_slice(access_unit->primary_start))
return FALSE;
// All I
if (access_unit->primary_start->u.slice.slice_type == ALL_SLICES_I)
return TRUE;
// Only one slice, and it's I
if (num_slices(access_unit) == 1 &&
access_unit->primary_start->u.slice.slice_type == SLICE_I)
return TRUE;
// Are any of the slices not I?
for (ii=0; ii<access_unit->nal_units->length; ii++)
{
nal_unit_p nal_unit = access_unit->nal_units->array[ii];
if (nal_is_slice(nal_unit) && nal_unit->u.slice.slice_type != SLICE_I)
return FALSE;
}
return TRUE;
}
/*
* Are all slices in this access unit P slices?
*/
extern int all_slices_P(access_unit_p access_unit)
{
int ii;
if (access_unit->primary_start == NULL)
return FALSE;
if (!nal_is_slice(access_unit->primary_start))
return FALSE;
// All P
if (access_unit->primary_start->u.slice.slice_type == ALL_SLICES_P)
return TRUE;
// Only one slice, and it's P
if (num_slices(access_unit) == 1 &&
access_unit->primary_start->u.slice.slice_type == SLICE_P)
return TRUE;
// Are any of the slices not P?
for (ii=0; ii<access_unit->nal_units->length; ii++)
{
nal_unit_p nal_unit = access_unit->nal_units->array[ii];
if (nal_is_slice(nal_unit) && nal_unit->u.slice.slice_type != SLICE_P)
return FALSE;
}
return TRUE;
}
/*
* Are all slices in this access unit I or P slices?
*/
extern int all_slices_I_or_P(access_unit_p access_unit)
{
int ii;
if (access_unit->primary_start == NULL)
return FALSE;
if (!nal_is_slice(access_unit->primary_start))
return FALSE;
// All P or all I
if (access_unit->primary_start->u.slice.slice_type == SLICE_I ||
access_unit->primary_start->u.slice.slice_type == SLICE_P)
return TRUE;
// Only one slice, and it's P or I
if (num_slices(access_unit) == 1 &&
(access_unit->primary_start->u.slice.slice_type == ALL_SLICES_I ||
access_unit->primary_start->u.slice.slice_type == ALL_SLICES_P))
return TRUE;
// Are any of the slices not either P or I?
for (ii=0; ii<access_unit->nal_units->length; ii++)
{
nal_unit_p nal_unit = access_unit->nal_units->array[ii];
if (nal_is_slice(nal_unit) &&
(nal_unit->u.slice.slice_type != SLICE_I &&
nal_unit->u.slice.slice_type != SLICE_P))
return FALSE;
}
return TRUE;
}
/*
* Are all slices in this access unit B slices?
*/
extern int all_slices_B(access_unit_p access_unit)
{
int ii;
if (access_unit->primary_start == NULL)
return FALSE;
if (!nal_is_slice(access_unit->primary_start))
return FALSE;
// All B
if (access_unit->primary_start->u.slice.slice_type == ALL_SLICES_B)
return TRUE;
// Only one slice, and it's B
if (num_slices(access_unit) == 1 &&
access_unit->primary_start->u.slice.slice_type == SLICE_B)
return TRUE;
// Are any of the slices not B?
for (ii=0; ii<access_unit->nal_units->length; ii++)
{
nal_unit_p nal_unit = access_unit->nal_units->array[ii];
if (nal_is_slice(nal_unit) && nal_unit->u.slice.slice_type != SLICE_B)
return FALSE;
}
return TRUE;
}
/*
* Append a NAL unit to the list of NAL units for this access unit
*
* NB: `pending` may be NULL
*
* Returns 0 if it succeeds, 1 if some error occurs.
*/
static int access_unit_append(access_unit_p access_unit,
nal_unit_p nal,
int starts_primary,
nal_unit_list_p pending)
{
int err;
if (starts_primary && access_unit->started_primary_picture)
{
// Our caller should have started a new access unit instead
fprint_err("### Already had a start of primary picture in access"
" unit %d\n",access_unit->index);
return 1;
}
if (starts_primary)
{
access_unit->primary_start = nal;
access_unit->started_primary_picture = TRUE;
access_unit->frame_num = nal->u.slice.frame_num;
access_unit->field_pic_flag = nal->u.slice.field_pic_flag;
access_unit->bottom_field_flag = nal->u.slice.bottom_field_flag;
}
if (pending != NULL && pending->length > 0)
{
int ii;
for (ii=0; ii<pending->length; ii++)
{
err = append_to_nal_unit_list(access_unit->nal_units,
pending->array[ii]);
if (err)
{
fprint_err("### Error extending access unit %d\n",
access_unit->index);
return err;
}
}
}
if (nal != NULL)
{
err = append_to_nal_unit_list(access_unit->nal_units,nal);
if (err)
{
fprint_err("### Error extending access unit %d\n",
access_unit->index);
return err;
}
}
return 0;
}
/*
* Merge the NAL units of the second access unit into the first, and then
* free the second access unit.
*
* Returns 0 if it succeeds, 1 if some error occurs.
*/
static int merge_access_unit_nals(access_unit_p access_unit1,
access_unit_p *access_unit2)
{
int err, ii;
for (ii = 0; ii < (*access_unit2)->nal_units->length; ii++)
{
err = append_to_nal_unit_list(access_unit1->nal_units,
(*access_unit2)->nal_units->array[ii]);
if (err)
{
print_err("### Error merging two access units\n");
return err;
}
}
// Don't forget that we're now "sharing" any ignored NAL units
access_unit1->ignored_broken_NAL_units +=
(*access_unit2)->ignored_broken_NAL_units;
// Take care not to free the individual NAL units in our second access
// unit, as they are still being used by the first
clear_access_unit(*access_unit2,FALSE);
free(*access_unit2);
*access_unit2 = NULL;
// Fake the flags in our remaining access unit to make us "look" like
// a frame
access_unit1->field_pic_flag = 0;
return 0;
}
/*
* Write out an access unit as ES.
*
* Also writes out any end of sequence or end of stream NAL unit found in the
* `context` (since they are assumed to have immediately followed this access
* unit).
*
* - `access_unit` is the access unit to write out
* - `context` may contain additional things to write (see above), but may
* legitimately be NULL if there is no context.
* - `output` is the ES file to write to
*
* Returns 0 if it succeeds, 1 if some error occurs.
*/
extern int write_access_unit_as_ES(access_unit_p access_unit,
access_unit_context_p context,
FILE *output)
{
int ii, err;
for (ii=0; ii<access_unit->nal_units->length; ii++)
{
err = write_ES_unit(output,&(access_unit->nal_units->array[ii]->unit));
if (err)
{
print_err("### Error writing NAL unit ");
report_nal(FALSE,access_unit->nal_units->array[ii]);
return err;
}
}
if (context != NULL && context->end_of_sequence)
{
err = write_ES_unit(output,&(context->end_of_sequence->unit));
if (err)
{
print_err("### Error writing end of sequence NAL unit ");
report_nal(FALSE,context->end_of_sequence);
return err;
}
free_nal_unit(&context->end_of_sequence);
}
if (context != NULL && context->end_of_stream)
{
err = write_ES_unit(output,&(context->end_of_stream->unit));
if (err)
{
print_err("### Error writing end of stream NAL unit ");
report_nal(FALSE,context->end_of_sequence);
return err;
}
free_nal_unit(&context->end_of_stream);
}
return 0;
}
/*
* Write out the (potential) trailing components of an access unit as TS.
*
* I.e., writes out any end of sequence or end of stream NAL unit found in the
* `context` (since they are assumed to have immediately followed this access
* unit).
*
* - `context` may contain additional things to write (see above), but may
* legitimately be NULL if there is no context.
* - `tswriter` is the TS context to write with
* - `video_pid` is the PID to use to write the data
*
* Returns 0 if it succeeds, 1 if some error occurs.
*/
static int write_access_unit_trailer_as_TS(access_unit_context_p context,
TS_writer_p tswriter,
uint32_t video_pid)
{
int err;
if (context != NULL && context->end_of_sequence)
{
nal_unit_p nal = context->end_of_sequence;
err = write_ES_as_TS_PES_packet(tswriter,nal->unit.data,nal->unit.data_len,
video_pid,DEFAULT_VIDEO_STREAM_ID);
if (err)
{
print_err("### Error writing end of sequence NAL unit ");
report_nal(FALSE,nal);
return err;
}
free_nal_unit(&context->end_of_sequence);
}
if (context != NULL && context->end_of_stream)
{
nal_unit_p nal = context->end_of_stream;
err = write_ES_as_TS_PES_packet(tswriter,nal->unit.data,nal->unit.data_len,
video_pid,DEFAULT_VIDEO_STREAM_ID);
if (err)
{
print_err("### Error writing end of stream NAL unit ");
report_nal(FALSE,nal);
return err;
}
free_nal_unit(&context->end_of_stream);
}
return 0;
}
/*
* Write out an access unit as TS.
*
* Also writes out any end of sequence or end of stream NAL unit found in the
* `context` (since they are assumed to have immediately followed this access
* unit).
*
* - `access_unit` is the access unit to write out
* - `context` may contain additional things to write (see above), but may
* legitimately be NULL if there is no context.
* - `tswriter` is the TS context to write with
* - `video_pid` is the PID to use to write the data
*
* Returns 0 if it succeeds, 1 if some error occurs.
*/
extern int write_access_unit_as_TS(access_unit_p access_unit,
access_unit_context_p context,
TS_writer_p tswriter,
uint32_t video_pid)
{
int ii, err;
for (ii=0; ii<access_unit->nal_units->length; ii++)
{
nal_unit_p nal = access_unit->nal_units->array[ii];
err = write_ES_as_TS_PES_packet(tswriter,
nal->unit.data,nal->unit.data_len,
video_pid,DEFAULT_VIDEO_STREAM_ID);
if (err)
{
print_err("### Error writing NAL unit ");
report_nal(FALSE,nal);
return err;
}
}
return write_access_unit_trailer_as_TS(context,tswriter,video_pid);
}
/*
* Write out an access unit as TS, with PTS timing in the first PES packet
* (and PCR timing in the first TS of the frame).
*
* Also writes out any end of sequence or end of stream NAL unit found in the
* `context` (since they are assumed to have immediately followed this access
* unit).
*
* - `access_unit` is the access unit to write out
* - `context` may contain additional things to write (see above), but may
* legitimately be NULL if there is no context.
* - `tswriter` is the TS context to write with
* - `video_pid` is the PID to use to write the data
* - `got_pts` is TRUE if we have a PTS value, in which case
* - `pts` is said PTS value
* - `got_dts` is TRUE if we also have DTS, in which case
* - `dts` is said DTS value.
*
* If we are given a DTS (which must, by definition, always go up) we will also
* use it as the value for PCR.
*
* Returns 0 if it succeeds, 1 if some error occurs.
*/
extern int write_access_unit_as_TS_with_pts_dts(access_unit_p access_unit,
access_unit_context_p context,
TS_writer_p tswriter,
uint32_t video_pid,
int got_pts,
uint64_t pts,
int got_dts,
uint64_t dts)
{
int ii, err;
for (ii=0; ii<access_unit->nal_units->length; ii++)
{
nal_unit_p nal = access_unit->nal_units->array[ii];
// Only write the first PES packet out with PTS
if (ii == 0)
err = write_ES_as_TS_PES_packet_with_pts_dts(tswriter,
nal->unit.data,
nal->unit.data_len,
video_pid,
DEFAULT_VIDEO_STREAM_ID,
got_pts,pts,
got_dts,dts);
else
err = write_ES_as_TS_PES_packet(tswriter,
nal->unit.data,nal->unit.data_len,
video_pid,DEFAULT_VIDEO_STREAM_ID);
if (err)
{
print_err("### Error writing NAL unit ");
report_nal(FALSE,nal);
return err;
}
}
return write_access_unit_trailer_as_TS(context,tswriter,video_pid);
}
/*
* Write out an access unit as TS, with PCR timing in the first TS of the
* frame.
*
* Also writes out any end of sequence or end of stream NAL unit found in the
* `context` (since they are assumed to have immediately followed this access
* unit).
*
* - `access_unit` is the access unit to write out
* - `context` may contain additional things to write (see above), but may
* legitimately be NULL if there is no context.
* - `tswriter` is the TS context to write with
* - `video_pid` is the PID to use to write the data
* - `pcr_base` and `pcr_extn` encode the PCR value.
*
* Returns 0 if it succeeds, 1 if some error occurs.
*/
extern int write_access_unit_as_TS_with_PCR(access_unit_p access_unit,
access_unit_context_p context,
TS_writer_p tswriter,
uint32_t video_pid,
uint64_t pcr_base,
uint32_t pcr_extn)
{
int ii, err;
for (ii=0; ii<access_unit->nal_units->length; ii++)
{
nal_unit_p nal = access_unit->nal_units->array[ii];
// Only write the first PES packet out with PCR
if (ii == 0)
err = write_ES_as_TS_PES_packet_with_pcr(tswriter,
nal->unit.data,
nal->unit.data_len,
video_pid,
DEFAULT_VIDEO_STREAM_ID,
pcr_base,pcr_extn);
else
err = write_ES_as_TS_PES_packet(tswriter,
nal->unit.data,nal->unit.data_len,
video_pid,DEFAULT_VIDEO_STREAM_ID);
if (err)
{
print_err("### Error writing NAL unit ");
report_nal(FALSE,nal);
return err;
}
}
return write_access_unit_trailer_as_TS(context,tswriter,video_pid);
}
/*
* End this access unit.
*
* - `access_unit` is the access unit to end.
* - if `show_details` is true, then a summary of its contents is printed
* out.
*
* Actually, with the current code scheme, this only does much if
* `show_details` is true. However, it may still be a useful hook
* for actual work later on.
*
* Returns 0 if it succeeds, 1 if some error occurs.
*/
static inline int end_access_unit(access_unit_context_p context,
access_unit_p access_unit,
int show_details)
{
if (show_details)
{
report_access_unit(access_unit);
if (context->pending_nal)
{
print_msg("... pending: ");
report_nal(TRUE,context->pending_nal);
}
if (context->end_of_sequence)
{
print_msg("--> EndOfSequence ");
report_nal(TRUE,context->end_of_sequence);
}
if (context->end_of_stream)
{
print_msg("--> EndOfStream ");
report_nal(TRUE,context->end_of_stream);
}
}
return 0;
}
/*
* Build a new access unit context datastructure.
*
* Returns 0 if it succeeds, 1 if some error occurs.
*/
extern int build_access_unit_context(ES_p es,
access_unit_context_p *context)
{
int err;
access_unit_context_p new = malloc(SIZEOF_ACCESS_UNIT_CONTEXT);
if (new == NULL)
{
print_err("### Unable to allocate access unit context datastructure\n");
return 1;
}
new->pending_nal = NULL;
new->end_of_stream = NULL;
new->end_of_sequence = NULL;
new->access_unit_index = 0;
new->reverse_data = NULL;
new->no_more_data = FALSE;
new->earlier_primary_start = NULL;
err = build_nal_unit_context(es,&new->nac);
if (err)
{
print_err("### Error building access unit context datastructure\n");
free(new);
return err;
}
err = build_nal_unit_list(&new->pending_list);
if (err)
{
print_err("### Error building access unit context datastructure\n");
free_nal_unit_context(&new->nac);
free(new);
return err;
}
*context = new;
return 0;
}
/*
* Free a new access unit context datastructure.
*
* Clears the datastructure, frees it, and returns `context` as NULL.
*
* Does not free any `reverse_data` datastructure.
*
* Does nothing if `context` is already NULL.
*/
extern void free_access_unit_context(access_unit_context_p *context)
{
access_unit_context_p cc = *context;
if (cc == NULL)
return;
// We assume no-one else has an interest in the NAL units in
// our "pending" list.
free_nal_unit_list(&cc->pending_list,TRUE);
// And similarly, we should be the only "person" holding on to these
free_nal_unit(&cc->earlier_primary_start); // although this is bluff
free_nal_unit(&cc->end_of_sequence);
free_nal_unit(&cc->end_of_stream);
free_nal_unit(&cc->pending_nal);
free_nal_unit_context(&cc->nac);
cc->reverse_data = NULL;
free(*context);
*context = NULL;
return;
}
/*
* Reset an acccess unit context, so it "forgets" its current information
* about what it is reading, etc.
*/
extern void reset_access_unit_context(access_unit_context_p context)
{
free_nal_unit(&context->earlier_primary_start);
free_nal_unit(&context->end_of_sequence);
free_nal_unit(&context->end_of_stream);
free_nal_unit(&context->pending_nal);
reset_nal_unit_list(context->pending_list,FALSE); // @@@ leak???
context->no_more_data = FALSE;
// We have to hope that the "previous" sequence parameter and picture
// parameter dictionaries are still applicable, since we don't still
// have a record of the ones that would have been in effect at this
// point.
}
/*
* Rewind a file being read as access units.
*
* This is a wrapper for `rewind_nal_unit_context` that also knows to
* unset things appropriate to the access unit context.
*
* If a reverse context is attached to this access unit, it also will
* be "rewound" appropriately.
*
* Returns 0 if all goes well, 1 if something goes wrong.
*/
extern int rewind_access_unit_context(access_unit_context_p context)
{
// First, forget where we are
reset_access_unit_context(context);
context->access_unit_index = 0; // no access units read from this file yet
// Next, take care of rewinding
if (context->reverse_data)
{
context->reverse_data->last_posn_added = -1; // next entry to be 0
}
// And then, do the relocation itself
return rewind_nal_unit_context(context->nac);
}
/*
* Remember the required information from the previous access unit's
* first VLC NAL unit (i.e., the one that starts its primary picture).
*
* If we just remembered the (address of the) NAL unit itself, we would
* have a problem if/when the access unit containing it was freed, since
* that would also free the NAL unit. Luckily, the information we want
* to remember is well defined, and does not require us to do anything
* other than copy data, so we can reuse the same "internal" NAL unit
* without needing to do lots of mallocing around.
*
* It *should* be obvious, given its intended use, but do not call this
* on a NAL unit that has not been decoded - things may fall apart
* messily later on...
*
* (NB: the "pseudo" NAL unit we use to remember the information is
* a true NAL unit except for not having any of the data/rbsp arrays
* filled in, so it *does* cause the NAL unit id to be incremented,
* which has confused me at least once when reading diagnostic output.)
*
* Returns 0 if it succeeds, 1 if some error occurs.
*/
static int remember_earlier_primary_start(access_unit_context_p context,
nal_unit_p nal)
{
nal_unit_p tgt = context->earlier_primary_start;
if (tgt == NULL)
{
int err = build_nal_unit(&tgt);
if (err)
{
print_err("### Error building NAL unit for 'earlier primary start'\n");
free(tgt);
return err;
}
context->earlier_primary_start = tgt;
}
tgt->starts_picture_decided = nal->starts_picture_decided;
tgt->starts_picture = nal->starts_picture;
tgt->start_reason = nal->start_reason;
tgt->decoded = nal->decoded;
tgt->nal_ref_idc = nal->nal_ref_idc;
tgt->nal_unit_type = nal->nal_unit_type;
tgt->u = nal->u;
// Lastly, we may not need the following, but they are sufficient to
// allow us to read the whole NAL unit back in if we should need to.
tgt->unit.start_posn = nal->unit.start_posn;
tgt->unit.data_len = nal->unit.data_len;
return 0;
}
/*
* Maybe remember an access unit for reversing - either an IDR or one with all
* frames I
*/
static int maybe_remember_access_unit(reverse_data_p reverse_data,
access_unit_p access_unit,
int verbose)
{
// Keep it if it is an IDR, or all of its contents are I slices
if (access_unit->primary_start != NULL &&
access_unit->primary_start->nal_ref_idc != 0 &&
(access_unit->primary_start->nal_unit_type == NAL_IDR ||
all_slices_I(access_unit)))
{
ES_offset start_posn = {0,0};
uint32_t num_bytes = 0;
int err = get_access_unit_bounds(access_unit,&start_posn,&num_bytes);
if (err)
{
fprint_err("### Error working out position/size of access unit %d"
" for reversing\n",access_unit->index);
return 1;
}
err = remember_reverse_h264_data(reverse_data,access_unit->index,
start_posn,num_bytes);
if (err)
{
fprint_err("### Error remembering access unit %d for reversing\n",
access_unit->index);
return 1;
}
if (verbose) fprint_msg("REMEMBER IDR %5d at " OFFSET_T_FORMAT_08
"/%04d for %5d\n",access_unit->index,
start_posn.infile,start_posn.inpacket,num_bytes);
}
return 0;
}
/*
* Retrieve the next access unit from the given elementary stream.
*
* - `context` is the context information needed to allow us to find
* successive access units.
* - `quiet` is true if we should try to be silent about it
* - `show_details` is true if we should output more info than normal
* - `ret_access_unit` is the next access unit.
*
* If the access unit was ended because an end of sequence or end of
* stream NAL unit was encountered, then said end of sequence/stream
* NAL unit will be remembered in the `context`.
*
* Note that it is possible to get back an *empty* access unit in
* certain situations - the most obvious of which is if we get two
* ``end of sequence`` NAL units with nothing betwen them.
*
* Because of this possibility, some care should be taken to allow for
* access units that do not contain a primary picture (no VCL NAL unit),
* and contain zero NAL units. Also, if one is trying for an accurate
* count of access units, such instances should probably be ignored.
*
* Returns 0 if it succeeds, EOF if there is no more data to read, or 1 if
* some error occurs.
*
* EOF can be returned because the end of file has been reached, or because an
* end of stream NAL unit has been encountered. The two may be distinguished
* by looking at `context->end_of_stream`, which will be NULL if it was a true
* EOF.
*
* Note that `ret_access_unit` will be NULL if EOF is returned.
*/
extern int get_next_access_unit(access_unit_context_p context,
int quiet,
int show_details,
access_unit_p *ret_access_unit)
{
int err;
nal_unit_p nal = NULL;
access_unit_p access_unit;
// Is there anything more to read from the input stream?
if (context->no_more_data)
{
*ret_access_unit = NULL;
return EOF;
}
// Since we're expecting to return a new access unit,
// we'd better build it...
err = build_access_unit(&access_unit,context->access_unit_index+1);
if (err) return err;
// Did we have any left over stuff to put at its start?
if (context->pending_nal != NULL)
{
err = access_unit_append(access_unit,
context->pending_nal,TRUE,context->pending_list);
if (err) goto give_up;
context->pending_nal = NULL;
reset_nal_unit_list(context->pending_list,FALSE);
}
for (;;)
{
err = find_next_NAL_unit(context->nac,FALSE,&nal);
if (err == EOF)
{
context->no_more_data = TRUE; // prevent future reads on this stream
break;
}
else if (err == 2)
{
// The NAL unit was broken. Should we:
// a) ignore it and pretend it never happened (i.e., ``continue``)
// b) ignore it and give up on the current access unit (i.e., unset
// our current status, and hunt for the start of the next access
// unit).
// Clearly, option (a) is the easiest to try, so let's see how that
// works for now...
print_err("!!! Ignoring broken NAL unit\n");
access_unit->ignored_broken_NAL_units ++;
continue;
}
else if (err)
{
print_err("### Error retrieving next NAL\n");
goto give_up;
}
if (nal_is_slice(nal))
{
if (!access_unit->started_primary_picture)
{
// We're in a new access unit, but we haven't had a slice
// yet, so we can be lazy and assume that this must be the
// first slice
// (What we're *not* checking is whether the first access
// unit in the bitstream starts with an IDR, which might be
// a good idea)
nal->start_reason = "First slice of new access unit";
err = access_unit_append(access_unit,nal,TRUE,context->pending_list);
if (err) goto give_up_free_nal;
reset_nal_unit_list(context->pending_list,FALSE);
err = remember_earlier_primary_start(context,nal);
if (err) goto give_up_free_nal;
}
else if (nal_is_first_VCL_NAL(nal,context->earlier_primary_start))
{
// Regardless of what we determine next, we need to remember that the
// NAL started (what may later be the previous) access unit
err = remember_earlier_primary_start(context,nal);
if (err) goto give_up_free_nal;
if (access_unit->started_primary_picture)
{
// We were already in an access unit with a primary
// picture, so this NAL unit must start a new access unit.
// Remember it for next time, and return the access unit so far.
context->pending_nal = nal;
break; // Ready to return the access unit
}
else
{
// This access unit was waiting for its primary picture
err = access_unit_append(access_unit,nal,TRUE,context->pending_list);
if (err) goto give_up_free_nal;
reset_nal_unit_list(context->pending_list,FALSE);
}
}
else if (!access_unit->started_primary_picture)
{
// But this is not a NAL unit that may start a new
// access unit. So what should we do? Ignore it?
if (!quiet)
{
print_err("!!! Ignoring VCL NAL that cannot start a picture:\n");
print_err(" ");
report_nal(FALSE,nal);
print_err("\n");
}
free_nal_unit(&nal);
}
else if (nal_is_redundant(nal))
{
// pass
// print_msg(" ignoring redundant NAL unit\n");
free_nal_unit(&nal);
}
else
{
// We're part of the same access unit, but not special
err = access_unit_append(access_unit,nal,FALSE,context->pending_list);
if (err) goto give_up_free_nal;
reset_nal_unit_list(context->pending_list,FALSE);
}
}
else if (nal->nal_unit_type == NAL_ACCESS_UNIT_DELIM)
{
// We always start an access unit...
if (access_unit->started_primary_picture)
{
err = append_to_nal_unit_list(context->pending_list,nal);
if (err) goto give_up_free_nal;
break; // Ready to return the "previous" access unit
}
else
{
// The current access unit doesn't yet have any VCL NALs
if (context->pending_list->length > 0 ||
access_unit->nal_units->length > 0)
{
print_err("!!! Ignoring incomplete access unit:\n");
if (access_unit->nal_units->length > 0)
{
report_nal_unit_list(FALSE," ",access_unit->nal_units);
reset_nal_unit_list(access_unit->nal_units,TRUE);
}
if (context->pending_list->length > 0)
{
report_nal_unit_list(FALSE," ",context->pending_list);
reset_nal_unit_list(context->pending_list,TRUE);
}
}
err = access_unit_append(access_unit,nal,FALSE,NULL);
if (err) goto give_up_free_nal;
}
}
else if (nal->nal_unit_type == NAL_SEI)
{
// SEI units always precede the primary coded picture
// - so they also implicitly end any access unit that has already
// started its primary picture
if (access_unit->started_primary_picture)
{
err = append_to_nal_unit_list(context->pending_list,nal);
if (err) goto give_up_free_nal;
break; // Ready to return the "previous" access unit
}
else
{
err = append_to_nal_unit_list(context->pending_list,nal);
if (err) goto give_up_free_nal;
}
}
else if (nal->nal_unit_type == NAL_SEQ_PARAM_SET ||
nal->nal_unit_type == NAL_PIC_PARAM_SET ||
nal->nal_unit_type == 13 ||
nal->nal_unit_type == 14 ||
nal->nal_unit_type == 15 ||
nal->nal_unit_type == 16 ||
nal->nal_unit_type == 17 ||
nal->nal_unit_type == 18)
{
// These start a new access unit *if* they come after the
// last VCL NAL of an access unit. But we can only *tell*
// that they are after the last VCL NAL of an access unit
// when we start the next access unit (!) - so we need to
// hold them in hand until we know that we need them.
// (i.e., they'll get added to an access unit just before
// the next "more determined" NAL unit we add to an access
// unit)
err = append_to_nal_unit_list(context->pending_list,nal);
if (err) goto give_up_free_nal;
}
else if (nal->nal_unit_type == NAL_END_OF_SEQ)
{
if (context->pending_list->length > 0)
{
print_err("!!! Ignoring items after last VCL NAL and"
" before End of Sequence:\n");
report_nal_unit_list(FALSE," ",context->pending_list);
reset_nal_unit_list(context->pending_list,TRUE);
}
// And remember this as the End of Sequence marker
context->end_of_sequence = nal;
break;
}
else if (nal->nal_unit_type == NAL_END_OF_STREAM)
{
if (context->pending_list->length > 0)
{
print_err("!!! Ignoring items after last VCL NAL and"
" before End of Stream:\n");
report_nal_unit_list(FALSE," ",context->pending_list);
reset_nal_unit_list(context->pending_list,TRUE);
}
// And remember this as the End of Stream marker
context->end_of_stream = nal;
// Which means there's no point in reading more from this stream
// (setting no_more_data like this means that *next* time this
// function is called, it will return EOF)
context->no_more_data = TRUE;
break;
}
else
{
// It's not a slice, or an access unit delimiter, or an
// end of sequence or stream, or a sequence or picture
// parameter set, or various other odds and ends, so it
// looks like we can ignore it.
free_nal_unit(&nal);
}
}
// Check for an immediate "end of file with no data"
// - i.e., we read EOF or end of stream, and there was nothing
// between the last access unit and such reading
if (context->no_more_data && access_unit->nal_units->length == 0)
{
free_access_unit(&access_unit);
*ret_access_unit = NULL;
return EOF;
}
// Otherwise, finish off and return the access unit we have in hand
err = end_access_unit(context,access_unit,show_details);
if (err) goto give_up;
// Remember to count it
context->access_unit_index ++;
*ret_access_unit = access_unit;
return 0;
give_up_free_nal:
free_nal_unit(&nal);
give_up:
free_access_unit(&access_unit);
return 1;
}
/*
* Retrieve the next non-empty access unit from the given elementary stream.
*
* - `context` is the context information needed to allow us to find
* successive access units.
* - `quiet` is true if we should try to be silent about it
* - `show_details` is true if we should output more info than normal
* - `frame` is an access unit datastructure representing the next
* frame.
*
* If the access unit was ended because an end of sequence or end of
* stream NAL unit was encountered, then said end of sequence/stream
* NAL unit will be remembered in the `context`.
*
* Returns 0 if it succeeds, EOF if there is no more data to read, or 1 if
* some error occurs.
*
* EOF can be returned because the end of file has been reached, or because an
* end of stream NAL unit has been encountered. The two may be distinguished
* by looking at `context->end_of_stream`, which will be NULL if it was a true
* EOF.
*
* Note that `ret_access_unit` will be NULL if EOF is returned.
*/
static int get_next_non_empty_access_unit(access_unit_context_p context,
int quiet,
int show_details,
access_unit_p *access_unit)
{
for (;;)
{
int err = get_next_access_unit(context,quiet,show_details,access_unit);
if (err) return err;
if ((*access_unit)->primary_start)
return 0;
}
}
/*
* Try for the next field of a pair, and return a frame formed therefrom
*
* - `context` is the context information needed to allow us to find
* successive access units.
* - `quiet` is true if we should try to be silent about it
* - `show_details` is true if we should output more info than normal
* - if `first_time` is true, then we will try to match a second field
* with a third, if the second field has a different temporal reference
* than the first. If it is false, we will not (thus stopping us from
* trying forever...)
* - `picture` starts out at the first field of our (hoped for) pair, and
* will end up as the merged result of our two fields. If the input stream
* is awry (or we are misaligned with respect to it), this might instead be
* replaced by a "proper" frame.
*
* Returns 0 if it succeeds, EOF if there is no more data to read, or 1 if
* some error occurs.
*/
static int get_next_field_of_pair(access_unit_context_p context,
int quiet,
int show_details,
int first_time,
access_unit_p *access_unit)
{
int err;
access_unit_p second;
if (show_details || context->nac->show_nal_details)
fprint_msg("@@ Looking for second field (%s time)\n",
(first_time?"first":"second"));
// We assume (hope) the next picture will be our second half
err = get_next_non_empty_access_unit(context,quiet,show_details,&second);
if (err)
{
if (err != EOF)
print_err("### Trying to read second field\n");
return err;
}
if (second->field_pic_flag == 0)
{
if (!quiet)
print_err("!!! Field followed by a frame - ignoring the field\n");
free_access_unit(access_unit);
*access_unit = second;
// and pretend to success
}
else if ((*access_unit)->frame_num == second->frame_num)
{
// They appear to be matching fields - make a frame from them
if (show_details || context->nac->show_nal_details)
print_msg("@@ Merging two field access units\n");
err = merge_access_unit_nals(*access_unit,&second); // (frees `second`)
if (err)
{
free_access_unit(&second);
return 1;
}
if (show_details)
report_access_unit(*access_unit);
}
else if (first_time)
{
if (!quiet)
fprint_err("!!! Field with frame number %d (%x) followed by"
" field with frame number %d (%x) - ignoring first field\n",
(*access_unit)->frame_num,(*access_unit)->frame_num,
second->frame_num,second->frame_num);
// Try again
free_access_unit(access_unit);
*access_unit = second;
err = get_next_field_of_pair(context,quiet,show_details,FALSE,access_unit);
if (err) return 1;
}
else
{
print_err("### Adjacent fields do not share frame numbers"
" - unable to match fields up\n");
return 1;
}
return 0;
}
/*
* Retrieve the next H.264 frame from the given elementary stream.
*
* The next access unit is retrieved from the input stream (using
* get_next_access_unit).
*
* If that access unit represents a frame, it is returned.
*
* If it represents a field, then the *following* access unit is retrieved,
* and if that is the second field of its frame, it is merged into the first,
* and the resultant frame is returned.
*
* If a field with frame number A is followed by a field with frame number B,
* it is assumed that synchronisation has been lost. In this case, the first
* field (frame A) will be discarded, and an attempt made to read the second
* field of frame B.
*
* Similarly, if a frame is found instead of the second field, the first
* field will be discarded and the frame returned.
*
* Note that if the context is associated with a reverse context,
* then appropriate frames will automatically be remembered therein.
*
* - `context` is the context information needed to allow us to find
* successive access units.
* - `quiet` is true if we should try to be silent about it
* - `show_details` is true if we should output more info than normal
* - `frame` is an access unit datastructure representing the next
* frame.
*
* If the access unit was ended because an end of sequence or end of
* stream NAL unit was encountered, then said end of sequence/stream
* NAL unit will be remembered in the `context`.
*
* Returns 0 if it succeeds, EOF if there is no more data to read, or 1 if
* some error occurs.
*
* EOF can be returned because the end of file has been reached, or because an
* end of stream NAL unit has been encountered. The two may be distinguished
* by looking at `context->end_of_stream`, which will be NULL if it was a true
* EOF.
*
* Note that `ret_access_unit` will be NULL if EOF is returned.
*/
extern int get_next_h264_frame(access_unit_context_p context,
int quiet,
int show_details,
access_unit_p *frame)
{
int err;
access_unit_p access_unit;
*frame = NULL;
err = get_next_non_empty_access_unit(context,quiet,show_details,
&access_unit);
if (err) return err;
if (access_unit->field_pic_flag == 1)
{
// We assume (hope) the next access_unit will be our second half
// - let's try to get it, and merge it into our current access unit
err = get_next_field_of_pair(context,quiet,show_details,TRUE,&access_unit);
if (err)
{
free_access_unit(&access_unit);
return 1;
}
}
if (context->reverse_data)
{
err = maybe_remember_access_unit(context->reverse_data,access_unit,
show_details);
if (err)
{
free_access_unit(&access_unit);
return 1;
}
}
*frame = access_unit;
return 0;
}
/*
* If this access unit was read from PES, did any of its PES packets contain
* a PTS?
*
* Returns TRUE if so, FALSE if not.
*/
extern int access_unit_has_PTS(access_unit_p access_unit)
{
// We need to look at each ES unit (within each NAL unit) of this access unit
int ii;
for (ii=0; ii<access_unit->nal_units->length; ii++)
{
if (access_unit->nal_units->array[ii]->unit.PES_had_PTS)
return TRUE;
}
return FALSE;
}
// Local Variables:
// tab-width: 8
// indent-tabs-mode: nil
// c-basic-offset: 2
// End:
// vim: set tabstop=8 shiftwidth=2 expandtab: