Commit 56ffbdf4 authored by David Flynn's avatar David Flynn Committed by Laurent Aimar
Browse files

Packetizer is capable of:


 - repacketizing and reconstructing timestamps for a Dirac ES
   suitable for remultiplexing and decoding.
 - sanitizing an input stream that has non-signalled discontinuities
 - handling the basket case AVI fake-pts-is-dts method too:
   For a non well formed input (only has pts or dts), this is
   implemented by simulating the ReOrder Buffer model to resolve
   timestamps.

Updated 20090507 with:
 - use VLC_TS_INVALID rather than private block flags
 - removed useless i_sync_flags
 - add support for next_parse_offset == 0
 - eliminate dirac_FindEOS
 - use block_t's p_priv to avoid dirac_FindPictureNumber
 - remove b_picture_number, add parsecode helpers
 - when stream jumps backwards, generate correct EOS DU
 - dont use block_FindMaskedFlags
 - remove openloop fastsync
 - guarantee synchro sees EOS, remove duplicate EOS DUs
 - dont blindly believe that a data unit is > 1MB
 - fix compilation warning
 - fix issue with unsyncable p_extra
 - rearange file
 - add sanitization of prev_parse_offset
 - do duplicate EOS detection at the right place
 - move all operations on outqueue to Packetize
 - fix stupid offbyone on location of prev_parse_offset
 - sanity check output of sync at input to encapsualator
 - refactor dirac_BuildEncapsulationUnit
 - keep hold of precious timestamps
 - unfortunately block_t has no p_priv

Signed-off-by: default avatarDavid Flynn <davidf@rd.bbc.co.uk>
Signed-off-by: default avatarLaurent Aimar <fenrir@videolan.org>
parent b53cec09
......@@ -5,6 +5,7 @@ SOURCES_packetizer_mpeg4audio = mpeg4audio.c
SOURCES_packetizer_h264 = h264.c
SOURCES_packetizer_vc1 = vc1.c
SOURCES_packetizer_mlp = mlp.c
SOURCES_packetizer_dirac = dirac.c
libvlc_LTLIBRARIES += \
libpacketizer_mpegvideo_plugin.la \
......@@ -12,7 +13,9 @@ libvlc_LTLIBRARIES += \
libpacketizer_mpeg4audio_plugin.la \
libpacketizer_h264_plugin.la \
libpacketizer_vc1_plugin.la \
libpacketizer_mlp_plugin.la
libpacketizer_mlp_plugin.la \
libpacketizer_dirac_plugin.la \
$(NULL)
if ENABLE_SOUT
libvlc_LTLIBRARIES += libpacketizer_copy_plugin.la
endif
/*****************************************************************************
* dirac.c
*****************************************************************************
* Copyright (C) 2008 the VideoLAN team
* $Id$
*
* Authors: David Flynn <davidf@rd.bbc.co.uk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/* Dirac packetizer, formed of three parts:
* 1) Bitstream synchroniser (dirac_DoSync)
* - Given an arbitary sequence of bytes, extract whole Dirac Data Units
* - Maps timestamps in supplied block_t's to the extracted Data Unit
* A time stamp applies to the next Data Unit to commence at, or after
* the first byte of the block_t with the timestamp.
* 2) Encapsulation Unit generation (dirac_BuildEncapsulationUnit)
* - Takes multiple well formed Dirac Data Units and forms them into a
* single encapsulation unit, suitable for muxing.
* - Sorts out any time stamps so that they only apply to pictures.
* 3) Timestamp generator (dirac_TimeGenPush)
* - Many streams will not be correctly timestamped, ie, DTS&PTS for
* every encapsulation unit. Timestamp generator syncs to avaliable
* timestamps and produces DTS&PTS for each encapsulation unit.
* - For 'Occasional' missing PTS|DTS:
* Missing timestamp is generated using interpolation from last
* known good values.
* - for All PTS missing:
* It is assumed that DTS values are fake, and are actually
* in the sequence of the PTS values at the output of a decoder.
* Fill in PTS by copying from DTS (accounting for reordering,
* by simulating reorder buffer); adjust DTS to provide correct
* value. This is how demuxers like AVI work.
* - for All DTS missing:
* (Ie, PTS is present), reorder buffer is simulated to determine
* PTS for each encapsulation unit.
* - NB, doesn't handle all pts missing with real dts. (no way to
* distinguish from the fake dts case.)
*
* DIRAC_NON_DATED is used to show a block should not have a time stamp
* associated (ie, don't interpolate a counter). At the ouput, these
* blocks get dated with the last used timestamp (or are merged with
* another encapsulation unit).
*/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <assert.h>
#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_codec.h>
#include <vlc_block.h>
#include "vlc_bits.h"
#include "vlc_block_helper.h"
#define SANITIZE_PREV_PARSE_OFFSET 1
/*****************************************************************************
* Module descriptor
*****************************************************************************/
static int Open ( vlc_object_t * );
static void Close( vlc_object_t * );
vlc_module_begin()
set_category( CAT_SOUT )
set_subcategory( SUBCAT_SOUT_PACKETIZER )
set_description( N_("Dirac packetizer") )
set_capability( "packetizer", 50 )
set_callbacks( Open, Close )
vlc_module_end()
/*****************************************************************************
* Local prototypes
*****************************************************************************/
struct decoder_sys_t
{
/* sync state */
block_bytestream_t bytestream;
int i_state;
size_t i_offset;
uint32_t u_last_npo;
/* recovered timestamp from bytesteram for use
* by synchroniser: should only get reset by the
* synchronizer upon a discontinuity sentinel */
mtime_t i_sync_pts;
mtime_t i_sync_dts;
/* build encapsulation unit state */
block_t *p_eu; /*< Current encapsulation unit being built */
block_t **pp_eu_last;
uint32_t u_eu_last_npo; /* last next_parse_offset at input to encapsulation */
mtime_t i_eu_pts;
mtime_t i_eu_dts;
/* timestamp generator state */
date_t dts; /*< timegen decode clock, increments at picture rate */
bool b_dts; /*< timegen decode clock valid */
bool b_pts; /*< timegen presentation time valid */
mtime_t i_pts; /*< timegen presentation time of picture u_pts_picnum */
uint32_t u_pts_picnum; /*< picture number of timegen presentation time */
mtime_t i_pts_offset; /*< maximum time between pts and dts */
/* p_outqueue is the list of encapsulation units that have been
* fed to the timegenerator. the timegenerator stamps them in
* the order it solves the time. the main packetizer loop removes
* completed encapsulation units from the front */
block_t *p_outqueue;
block_t **pp_outqueue_last;
/* p_out_dts points to an element in p_outqueue. It is used for VLC's
* fake pts hidden in DTS hack, as used by AVI */
block_t *p_out_dts;
uint32_t u_tg_last_picnum; /*< most recent picturenumber output from RoB */
bool b_tg_last_picnum; /*< u_tg_last_picnum valid */
struct dirac_reorder_buffer {
int u_size_max;
int u_size;
struct dirac_reorder_entry {
struct dirac_reorder_entry *p_next;
block_t *p_eu;
uint32_t u_picnum;
} p_entries[32], *p_head, *p_empty;
} reorder_buf; /*< reorder buffer, used by timegenerator */
/* packetizer state */
mtime_t i_pts_last_out; /*< last output [from packetizer] pts */
mtime_t i_dts_last_out; /*< last output [from packetizer] dts */
struct seq_hdr_t {
uint32_t u_width;
uint32_t u_height;
uint32_t u_fps_num;
uint32_t u_fps_den;
enum picture_coding_mode_t {
DIRAC_FRAME_CODING=0,
DIRAC_FIELD_CODING=1,
} u_picture_coding_mode;
} seq_hdr; /*< sequence header */
bool b_seen_seq_hdr; /* sequence header valid */
bool b_seen_eos; /* last data unit to be handled was an EOS */
};
typedef struct {
uint32_t u_next_offset;
uint32_t u_prev_offset;
int i_parse_code;
} parse_info_t;
typedef struct {
block_free_t pf_blk_release;
/*> next_parse_offset of the final data unit in associated block_t */
uint32_t u_last_next_offset;
/*> picture number is invalid if block has flags DIRAC_NON_DATED */
uint32_t u_picture_number;
} dirac_block_encap_t;
enum {
NOT_SYNCED=0,
TRY_SYNC,
SYNCED,
SYNCED_INCOMPLETEDU,
};
enum {
DIRAC_EOS = 0x02000000,
DIRAC_NON_DATED = 0x04000000,
DIRAC_DISCARD = 0x08000000,
};
enum {
DIRAC_DU_IN_EU,
DIRAC_DU_ENDS_EU,
};
/***
* Block encapsulation functions.
* Things are greately simplified by associating some metadata
* with a block as it passes through the packetizer (saves having
* to determine it again)
*
* unfortunately p_block doesn't have a p_priv, so some fakage
* needs to happen:
* - Create a dummy block that has some extra storage, set up
* members to be identical to the actual block
* - Store private data there and pointer to orig block
* - modify block pointer to point to fake block
*
* NB, the add/new functions must not be used to blocks
* that are referenced in lists, etc., [in this code, this is ok]
* NB, don't call add on the same block multiple times (will leak)
*
* davidf has a patch that reverts this to use a p_priv in block_t.
*/
typedef struct {
block_t fake;
block_t *p_orig;
void *p_priv;
} fake_block_t;
static dirac_block_encap_t *dirac_RemoveBlockEncap( block_t *p_block )
{
fake_block_t *p_fake = (fake_block_t *)p_block;
dirac_block_encap_t *dbe = p_fake->p_priv;
if( !dbe ) return NULL;
p_fake->p_priv = NULL;
dbe->pf_blk_release = NULL;
return dbe;
}
static void dirac_ReleaseBlockAndEncap( block_t *p_block )
{
fake_block_t *p_fake = (fake_block_t *)p_block;
free( dirac_RemoveBlockEncap( p_block ) );
p_fake->p_orig->pf_release( p_fake->p_orig );
free( p_fake );
}
static void dirac_AddBlockEncap( block_t **pp_block, dirac_block_encap_t *p_dbe )
{
fake_block_t *p_fake = calloc( 1, sizeof( *p_fake ) );
assert( p_fake ); /* must not fail, fixby: adding a p_priv to block_t */
p_fake->p_orig = *pp_block;
memcpy( &p_fake->fake, *pp_block, sizeof( block_t ) );
*pp_block = &p_fake->fake;
p_fake->p_priv = p_dbe;
p_dbe->pf_blk_release = p_fake->p_orig->pf_release;
p_fake->fake.pf_release = dirac_ReleaseBlockAndEncap;
}
static dirac_block_encap_t *dirac_NewBlockEncap( block_t **pp_block )
{
dirac_block_encap_t *dbe = calloc( 1, sizeof( *dbe ) );
if( dbe ) dirac_AddBlockEncap( pp_block, dbe );
return dbe;
}
static dirac_block_encap_t *dirac_GetBlockEncap( block_t *p_block )
{
return (dirac_block_encap_t*) ((fake_block_t *)p_block)->p_priv;
}
/***
* General utility funcions
*/
/* decrement a date. opposite to date_Increment */
static mtime_t date_Decrement( date_t *p_date, uint32_t i_nb_samples )
{
mtime_t i_dividend = (mtime_t)i_nb_samples * 1000000 * p_date->i_divider_den;
p_date->date -= i_dividend / p_date->i_divider_num;
unsigned u_rem_adjust = i_dividend % p_date->i_divider_num;
if( p_date->i_remainder < u_rem_adjust )
{
/* This is Bresenham algorithm. */
assert( p_date->i_remainder > -p_date->i_divider_num);
p_date->date -= 1;
p_date->i_remainder += p_date->i_divider_num;
}
p_date->i_remainder -= u_rem_adjust;
return p_date->date;
}
/**
* given a chain of block_t, allocate and return an array containing
* pointers to all the blocks. (Acts as a replacement for the old p_prev
* member of block_t) */
static int block_ChainToArray( block_t *p_block, block_t ***ppp_array)
{
if( !ppp_array )
return 0;
int num_blocks;
block_ChainProperties( p_block, &num_blocks, NULL, NULL );
*ppp_array = malloc( sizeof( block_t* ) * num_blocks );
if( !ppp_array ) return 0;
for( int i = 0; i < num_blocks; i++ )
{
(*ppp_array)[i] = p_block;
p_block = p_block->p_next;
}
return num_blocks;
}
/**
* Destructively find and recover the earliest timestamp from start of
* bytestream, upto i_length.
*/
static void dirac_RecoverTimestamps ( decoder_t *p_dec, size_t i_length )
{
decoder_sys_t *p_sys = p_dec->p_sys;
block_t *p_block = p_sys->bytestream.p_block;
/* Find the block with first non-flushed data */
size_t i_offset = p_sys->bytestream.i_offset;
for(; p_block != NULL; p_block = p_block->p_next )
{
if( i_offset < p_block->i_buffer )
break;
i_offset -= p_block->i_buffer;
}
i_offset += i_length;
for(; p_block != NULL; p_block = p_block->p_next )
{
if( p_sys->i_sync_pts == VLC_TS_INVALID && p_sys->i_sync_dts == VLC_TS_INVALID )
{
/* oldest timestamp wins */
p_sys->i_sync_pts = p_block->i_pts;
p_sys->i_sync_dts = p_block->i_dts;
}
/* clear timestamps -- more than one data unit can come from a block */
p_block->i_flags = 0;
p_block->i_pts = p_block->i_dts = VLC_TS_INVALID;
if( i_offset < p_block->i_buffer )
break;
i_offset -= p_block->i_buffer;
}
}
/* backdate the list [p_block .. p_block->p_next where p_next == p_last] */
static void dirac_BackdateDTS( block_t *p_block, block_t *p_last, date_t *p_dts )
{
/* Transverse p_last backwards. (no p_prev anymore) */
block_t **pp_array = NULL;
int n = block_ChainToArray( p_block, &pp_array );
while( n ) if( pp_array[--n] == p_last ) break;
/* want to start at p_last->p_prev */
while( n-- )
{
if( pp_array[n]->i_flags & DIRAC_NON_DATED )
continue;
if( pp_array[n]->i_dts == VLC_TS_INVALID )
pp_array[n]->i_dts = date_Decrement( p_dts, 1 );
}
free( pp_array );
}
/* backdate the list [p_block .. p_block->p_next where p_next == p_last] */
static void dirac_BackdatePTS( block_t *p_block, block_t *p_last, date_t *p_pts, uint32_t u_pts_picnum )
{
/* Transverse p_last backwards. (no p_prev anymore) */
block_t **pp_array = NULL;
int n = block_ChainToArray( p_block, &pp_array );
while( n ) if( pp_array[--n] == p_last ) break;
/* want to start at p_last->p_prev */
while( n-- )
{
if( pp_array[n]->i_flags & DIRAC_NON_DATED )
continue;
if( pp_array[n]->i_dts != VLC_TS_INVALID )
continue;
dirac_block_encap_t *dbe = dirac_GetBlockEncap( pp_array[n] );
int32_t u_pic_num = dbe ? dbe->u_picture_number : 0;
int32_t i_dist = u_pic_num - u_pts_picnum;
date_t pts = *p_pts;
if( i_dist >= 0 )
pp_array[n]->i_pts = date_Increment( &pts, i_dist );
else
pp_array[n]->i_pts = date_Decrement( &pts, -i_dist );
}
free( pp_array );
}
/***
* Dirac spec defined relations
*/
static bool dirac_isEOS( uint8_t u_parse_code ) { return 0x10 == u_parse_code; }
static bool dirac_isSeqHdr( uint8_t u_parse_code ) { return 0 == u_parse_code; }
static bool dirac_isPicture( uint8_t u_parse_code ) { return 0x08 & u_parse_code; }
static int dirac_numRefs( uint8_t u_parse_code ) { return 0x3 & u_parse_code; }
static inline bool dirac_PictureNbeforeM( uint32_t u_n, uint32_t u_m )
{
/* specified as: u_n occurs before u_m if:
* (u_m - u_n) mod (1<<32) < D */
return (uint32_t)(u_m - u_n) < (1u<<31);
}
/***
* Reorder buffer model
*/
static void dirac_ReorderInit( struct dirac_reorder_buffer *p_rb )
{
memset( p_rb, 0, sizeof(*p_rb) );
p_rb->u_size_max = 2;
p_rb->p_empty = p_rb->p_entries;
p_rb->p_entries[31].p_next = NULL;
for( int i = 0; i < 31; i++ ) {
p_rb->p_entries[i].p_next = &p_rb->p_entries[i+1];
}
}
/* simulate the dirac picture reorder buffer */
static block_t *dirac_Reorder( decoder_t *p_dec, block_t *p_block_in, uint32_t u_picnum )
{
decoder_sys_t *p_sys = p_dec->p_sys;
if( !p_sys->reorder_buf.u_size_max )
/* reorder buffer disabled */
return p_block_in;
/* Modeling the reorder buffer:
* 1. If the reorder buffer is not full, insert picture for reordering.
* No picture is output by the system this picture period
* 2. If the reorder buffer is full:
* a. The picture decoded this period (u_picnum) bypasses the reorder
* buffer if it has a lower picture number than any entry in the
* reorder buffer. This picture is output by the system.
* b. Otherwise, the lowest picture number in the reorder buffer is
* removed from the buffer and output by the system. The current
* decoded picture (u_picnum) is inserted into the reorder buffer
*/
block_t *p_block = NULL;
/* Determine if the picture needs to be inserted */
if( p_sys->reorder_buf.u_size == p_sys->reorder_buf.u_size_max )
{
/* (2) reorder buffer is full */
if( !p_sys->reorder_buf.u_size_max ||
dirac_PictureNbeforeM( u_picnum, p_sys->reorder_buf.p_head->u_picnum ) )
{
/* (2a) current picture is first in order */
return p_block_in;
}
/* (2b) extract the youngest picture in the buffer */
p_block = p_sys->reorder_buf.p_head->p_eu;
struct dirac_reorder_entry *p_tmp = p_sys->reorder_buf.p_head;
p_sys->reorder_buf.p_head = p_tmp->p_next;
p_tmp->p_next = p_sys->reorder_buf.p_empty;
p_sys->reorder_buf.p_empty = p_tmp;
p_sys->reorder_buf.u_size--;
}
/* (1) and (2b) both require u_picnum to be inserted */
struct dirac_reorder_entry *p_current = p_sys->reorder_buf.p_empty;
p_sys->reorder_buf.p_empty = p_current->p_next;
p_sys->reorder_buf.u_size++;
/* insertion sort to keep p_head always sorted, earliest first */
struct dirac_reorder_entry **pp_at = &p_sys->reorder_buf.p_head;
for( ; *pp_at; pp_at = &(*pp_at)->p_next )
if( dirac_PictureNbeforeM( u_picnum, (*pp_at)->u_picnum ) )
break;
p_current->u_picnum = u_picnum;
p_current->p_eu = p_block_in;
p_current->p_next = *pp_at;
*pp_at = p_current;
return p_block;
}
/***
* bytestream parsing and unmarshalling functions
*/
static bool dirac_UnpackParseInfo( parse_info_t *p_pi, block_bytestream_t *p_bs,
size_t u_offset )
{
uint8_t p_d[13];
if( VLC_SUCCESS != block_PeekOffsetBytes( p_bs, u_offset, p_d, 13 ) )
return false;
if( p_d[0] != 'B' || p_d[1] != 'B' || p_d[2] != 'C' || p_d[3] != 'D' )
return false;
p_pi->i_parse_code = p_d[4];
p_pi->u_next_offset = p_d[5] << 24 | p_d[6] << 16 | p_d[7] << 8 | p_d[8];
p_pi->u_prev_offset = p_d[9] << 24 | p_d[10] << 16 | p_d[11] << 8 | p_d[12];
return true;
}
static uint32_t dirac_uint( bs_t *p_bs )
{
uint32_t count = 0, value = 0;
while( !bs_eof( p_bs ) && !bs_read( p_bs, 1 ) )
{
count++;
value <<= 1;
value |= bs_read( p_bs, 1 );
}
return (1 << count) - 1 + value;
}
static int dirac_bool( bs_t *p_bs )
{
return bs_read( p_bs, 1 );
}
/* read in useful bits from sequence header */
static bool dirac_UnpackSeqHdr( struct seq_hdr_t *p_sh, block_t *p_block )
{
bs_t bs;
bs_init( &bs, p_block->p_buffer, p_block->i_buffer );
bs_skip( &bs, 13*8 ); /* parse_info_header */
dirac_uint( &bs ); /* major_version */
dirac_uint( &bs ); /* minor_version */
dirac_uint( &bs ); /* profile */
dirac_uint( &bs ); /* level */
uint32_t u_video_format = dirac_uint( &bs ); /* index */
if( u_video_format > 20 )
{
/* dont know how to parse this header */
return false;
}
static const struct {
uint32_t u_w, u_h;
} dirac_size_tbl[] = {
{640,480}, {176,120}, {176,144}, {352,240}, {352,288}, {704,480},
{704,576}, {720,480}, {720,576}, {1280,720}, {1280,720}, {1920,1080},
{1920,1080}, {1920,1080}, {1920,1080}, {2048,1080}, {4096,2160},
{3840,2160}, {3840,2160}, {7680,4320}, {7680,4320},
};
p_sh->u_width = dirac_size_tbl[u_video_format].u_w;
p_sh->u_height = dirac_size_tbl[u_video_format].u_h;
if( dirac_bool( &bs ) )
{
p_sh->u_width = dirac_uint( &bs ); /* frame_width */
p_sh->u_height = dirac_uint( &bs ); /* frame_height */
}
if( dirac_bool( &bs ) )
{
dirac_uint( &bs ); /* chroma_format */
}
if( dirac_bool( &bs ) )
{
dirac_uint( &bs ); /* scan_format */
}
static const struct {
uint32_t u_n /* numerator */, u_d /* denominator */;
} dirac_frate_tbl[] = { /* table 10.3 */
{1, 1}, /* this value is not used */
{24000,1001}, {24,1}, {25,1}, {30000,1001}, {30,1},
{50,1}, {60000,1001}, {60,1}, {15000,1001}, {25,2},
};
const unsigned dirac_frate_tbl_size =
sizeof( dirac_frate_tbl ) / sizeof( *dirac_frate_tbl );
static const uint32_t dirac_vidfmt_frate[] = { /* table C.1 */
1, 9, 10, 9, 10, 9, 10, 4, 3, 7, 6, 4, 3, 7, 6, 2, 2, 7, 6, 7, 6,
};
p_sh->u_fps_num = dirac_frate_tbl[dirac_vidfmt_frate[u_video_format]].u_n;
p_sh->u_fps_den = dirac_frate_tbl[dirac_vidfmt_frate[u_video_format]].u_d;
if( dirac_bool( &bs ) )
{
uint32_t frame_rate_index = dirac_uint( &bs );
p_sh->u_fps_num = dirac_frate_tbl[frame_rate_index].u_n;
p_sh->u_fps_den = dirac_frate_tbl[frame_rate_index].u_d;
if( frame_rate_index >= dirac_frate_tbl_size )
{
/* invalid header */
return false;
}
if( frame_rate_index == 0 )