F5OEO-tstools/accessunit.c

1437 wiersze
46 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 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: