2002-09-10 21:50:03 +00:00
/*
* demuxer for PVA files , such as the ones produced by software to manage DVB boards
* like the Hauppauge WinTV DVBs , for MPlayer .
*
* Uses info from the PVA file specifications found at
*
* http : //www.technotrend.de/download/av_format_v1.pdf
*
2002-10-07 22:13:34 +00:00
* WARNING : Quite a hack was required in order to get files by MultiDec played back correctly .
* If it breaks anything else , just comment out the " #define DEMUX_PVA_MULTIDEC_HACK " below
* and it will not be compiled in .
2002-09-10 21:50:03 +00:00
*
2002-10-07 22:13:34 +00:00
* Feedback is appreciated .
2002-09-10 21:50:03 +00:00
*
* written by Matteo Giani
*/
2002-10-07 22:13:34 +00:00
# define DEMUX_PVA_MULTIDEC_HACK
# define PVA_NEW_PREBYTES_CODE
2002-09-10 21:50:03 +00:00
2002-09-22 02:33:28 +00:00
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
2002-09-10 21:50:03 +00:00
# include "config.h"
# include "mp_msg.h"
# include "help_mp.h"
# include "stream.h"
# include "demuxer.h"
# include "stheader.h"
# include "bswap.h"
/*
* # defines below taken from PVA spec ( see URL above )
*/
# define PVA_MAX_VIDEO_PACK_LEN 6*1024
# define VIDEOSTREAM 0x01
# define MAINAUDIOSTREAM 0x02
typedef struct {
2002-10-07 22:13:34 +00:00
off_t offset ;
2002-09-10 21:50:03 +00:00
long size ;
uint8_t type ;
uint8_t is_packet_start ;
2002-10-07 22:13:34 +00:00
float pts ;
2002-09-10 21:50:03 +00:00
} pva_payload_t ;
typedef struct {
float last_audio_pts ;
float last_video_pts ;
2002-10-07 22:13:34 +00:00
# ifdef PVA_NEW_PREBYTES_CODE
float video_pts_after_prebytes ;
long video_size_after_prebytes ;
uint8_t prebytes_delivered ;
# endif
2002-09-10 21:50:03 +00:00
uint8_t just_synced ;
uint8_t synced_stream_id ;
} pva_priv_t ;
2002-10-07 22:13:34 +00:00
2002-09-10 21:50:03 +00:00
int pva_sync ( demuxer_t * demuxer )
{
uint8_t buffer [ 5 ] = { 0 , 0 , 0 , 0 , 0 } ;
int count ;
pva_priv_t * priv = ( pva_priv_t * ) demuxer - > priv ;
2002-10-07 22:13:34 +00:00
2002-09-10 21:50:03 +00:00
2002-10-07 22:13:34 +00:00
/* This function is used to find the next nearest PVA packet start after a seek, since a PVA file
* is not indexed .
* The just_synced field is in the priv structure so that pva_get_payload knows pva_sync
* has already read ( part of ) the PVA header . This way we can avoid to seek back and ( hopefully )
* be able to read from pipes and such .
*/
2002-09-10 21:50:03 +00:00
2002-10-07 22:13:34 +00:00
for ( count = 0 ; count < PVA_MAX_VIDEO_PACK_LEN & & ! demuxer - > stream - > eof & & ! priv - > just_synced ; count + + )
2002-09-10 21:50:03 +00:00
{
buffer [ 0 ] = buffer [ 1 ] ;
buffer [ 1 ] = buffer [ 2 ] ;
buffer [ 2 ] = buffer [ 3 ] ;
buffer [ 3 ] = buffer [ 4 ] ;
buffer [ 4 ] = stream_read_char ( demuxer - > stream ) ;
/*
* Check for a PVA packet beginning sequence : we check both the " AV " word at the
* very beginning and the " 0x55 " reserved byte ( which is unused and set to 0x55 by spec )
*/
2002-10-07 22:13:34 +00:00
if ( buffer [ 0 ] = = ' A ' & & buffer [ 1 ] = = ' V ' & & buffer [ 4 ] = = 0x55 ) priv - > just_synced = 1 ;
//printf("demux_pva: pva_sync(): current offset= %ld\n",stream_tell(demuxer->stream));
2002-09-10 21:50:03 +00:00
}
2002-10-07 22:13:34 +00:00
if ( priv - > just_synced )
2002-09-10 21:50:03 +00:00
{
2006-04-16 13:25:14 +00:00
priv - > synced_stream_id = buffer [ 2 ] ;
2002-09-10 21:50:03 +00:00
return 1 ;
}
else
{
return 0 ;
}
}
2005-08-05 19:57:47 +00:00
static int pva_check_file ( demuxer_t * demuxer )
2002-09-10 21:50:03 +00:00
{
uint8_t buffer [ 5 ] = { 0 , 0 , 0 , 0 , 0 } ;
mp_msg ( MSGT_DEMUX , MSGL_V , " Checking for PVA \n " ) ;
stream_read ( demuxer - > stream , buffer , 5 ) ;
if ( buffer [ 0 ] = = ' A ' & & buffer [ 1 ] = = ' V ' & & buffer [ 4 ] = = 0x55 )
{
mp_msg ( MSGT_DEMUX , MSGL_DBG2 , " Success: PVA \n " ) ;
2005-08-05 19:57:47 +00:00
return DEMUXER_TYPE_PVA ;
2002-09-10 21:50:03 +00:00
}
else
{
mp_msg ( MSGT_DEMUX , MSGL_DBG2 , " Failed: PVA \n " ) ;
return 0 ;
}
}
2005-08-05 19:57:47 +00:00
static demuxer_t * demux_open_pva ( demuxer_t * demuxer )
2002-09-10 21:50:03 +00:00
{
sh_video_t * sh_video = new_sh_video ( demuxer , 0 ) ;
2002-10-07 22:13:34 +00:00
sh_audio_t * sh_audio = new_sh_audio ( demuxer , 0 ) ;
2002-09-10 21:50:03 +00:00
pva_priv_t * priv ;
stream_reset ( demuxer - > stream ) ;
stream_seek ( demuxer - > stream , 0 ) ;
priv = malloc ( sizeof ( pva_priv_t ) ) ;
if ( demuxer - > stream - > type ! = STREAMTYPE_FILE ) demuxer - > seekable = 0 ;
else demuxer - > seekable = 1 ;
demuxer - > priv = priv ;
memset ( demuxer - > priv , 0 , sizeof ( pva_priv_t ) ) ;
if ( ! pva_sync ( demuxer ) )
{
mp_msg ( MSGT_DEMUX , MSGL_ERR , " Not a PVA file. \n " ) ;
return NULL ;
}
//printf("priv->just_synced %s after initial sync!\n",priv->just_synced?"set":"UNSET");
demuxer - > video - > sh = sh_video ;
2002-10-07 22:13:34 +00:00
//printf("demuxer->stream->end_pos= %d\n",demuxer->stream->end_pos);
2002-09-10 21:50:03 +00:00
mp_msg ( MSGT_DEMUXER , MSGL_INFO , " Opened PVA demuxer... \n " ) ;
/*
* Audio and Video codecs :
* the PVA spec only allows MPEG2 video and MPEG layer II audio . No need to check the formats then .
* Moreover , there would be no way to do that since the PVA stream format has no fields to describe
* the used codecs .
*/
sh_video - > format = 0x10000002 ;
sh_video - > ds = demuxer - > video ;
2002-10-07 22:13:34 +00:00
/*
printf ( " demuxer->video->id==%d \n " , demuxer - > video - > id ) ;
printf ( " demuxer->audio->id==%d \n " , demuxer - > audio - > id ) ;
*/
demuxer - > audio - > sh = sh_audio ;
2002-09-10 21:50:03 +00:00
sh_audio - > format = 0x50 ;
sh_audio - > ds = demuxer - > audio ;
2002-10-07 22:13:34 +00:00
demuxer - > movi_start = 0 ;
demuxer - > movi_end = demuxer - > stream - > end_pos ;
2002-09-10 21:50:03 +00:00
priv - > last_video_pts = - 1 ;
priv - > last_audio_pts = - 1 ;
return demuxer ;
}
2002-09-22 02:33:28 +00:00
int pva_get_payload ( demuxer_t * d , pva_payload_t * payload ) ;
2002-09-10 21:50:03 +00:00
// 0 = EOF or no stream found
// 1 = successfully read a packet
2005-08-05 19:57:47 +00:00
static int demux_pva_fill_buffer ( demuxer_t * demux , demux_stream_t * ds )
2002-09-10 21:50:03 +00:00
{
uint8_t done = 0 ;
demux_packet_t * dp ;
pva_priv_t * priv = demux - > priv ;
pva_payload_t current_payload ;
while ( ! done )
{
if ( ! pva_get_payload ( demux , & current_payload ) ) return 0 ;
switch ( current_payload . type )
{
case VIDEOSTREAM :
2002-10-07 22:13:34 +00:00
if ( demux - > video - > id = = - 1 ) demux - > video - > id = 0 ;
2002-09-10 21:50:03 +00:00
if ( ! current_payload . is_packet_start & & priv - > last_video_pts = = - 1 )
{
/* We should only be here at the beginning of a stream, when we have
* not yet encountered a valid Video PTS , or after a seek .
* So , skip these starting packets in order not to deliver the
* player a bogus PTS .
*/
done = 0 ;
}
else
{
/*
* In every other condition , we are delivering the payload . Set this
* so that the following code knows whether to skip it or read it .
*/
done = 1 ;
}
2002-10-07 22:13:34 +00:00
if ( demux - > video - > id ! = 0 ) done = 0 ;
2002-09-10 21:50:03 +00:00
if ( current_payload . is_packet_start )
{
2002-10-07 22:13:34 +00:00
priv - > last_video_pts = current_payload . pts ;
2002-09-10 21:50:03 +00:00
//mp_msg(MSGT_DEMUXER,MSGL_DBG2,"demux_pva: Video PTS=%llu , delivered %f\n",current_payload.pts,priv->last_video_pts);
}
if ( done )
{
dp = new_demux_packet ( current_payload . size ) ;
dp - > pts = priv - > last_video_pts ;
stream_read ( demux - > stream , dp - > buffer , current_payload . size ) ;
ds_add_packet ( demux - > video , dp ) ;
}
else
{
//printf("Skipping %u video bytes\n",current_payload.size);
stream_skip ( demux - > stream , current_payload . size ) ;
}
break ;
case MAINAUDIOSTREAM :
2002-10-07 22:13:34 +00:00
if ( demux - > audio - > id = = - 1 ) demux - > audio - > id = 0 ;
2002-09-10 21:50:03 +00:00
if ( ! current_payload . is_packet_start & & priv - > last_audio_pts = = - 1 )
{
/* Same as above for invalid video PTS, just for audio. */
done = 0 ;
}
else
{
done = 1 ;
}
if ( current_payload . is_packet_start )
{
2002-10-07 22:13:34 +00:00
priv - > last_audio_pts = current_payload . pts ;
2002-09-10 21:50:03 +00:00
}
2002-10-07 22:13:34 +00:00
if ( demux - > audio - > id ! = 0 ) done = 0 ;
2002-09-10 21:50:03 +00:00
if ( done )
{
dp = new_demux_packet ( current_payload . size ) ;
dp - > pts = priv - > last_audio_pts ;
2002-10-07 22:13:34 +00:00
if ( current_payload . offset ! = stream_tell ( demux - > stream ) )
stream_seek ( demux - > stream , current_payload . offset ) ;
2002-09-10 21:50:03 +00:00
stream_read ( demux - > stream , dp - > buffer , current_payload . size ) ;
ds_add_packet ( demux - > audio , dp ) ;
}
else
{
stream_skip ( demux - > stream , current_payload . size ) ;
}
break ;
}
}
return 1 ;
}
int pva_get_payload ( demuxer_t * d , pva_payload_t * payload )
{
uint8_t flags , pes_head_len ;
uint16_t pack_size ;
2002-10-07 22:13:34 +00:00
off_t next_offset , pva_payload_start ;
2002-09-10 21:50:03 +00:00
unsigned char buffer [ 256 ] ;
2002-11-23 10:58:14 +00:00
# ifndef PVA_NEW_PREBYTES_CODE
2002-09-10 21:50:03 +00:00
demux_packet_t * dp ; //hack to deliver the preBytes (see PVA doc)
2002-11-23 10:58:14 +00:00
# endif
2006-03-22 12:23:55 +00:00
pva_priv_t * priv ;
2002-09-10 21:50:03 +00:00
if ( d = = NULL )
{
2002-10-07 22:13:34 +00:00
mp_msg ( MSGT_DEMUX , MSGL_ERR , " demux_pva: pva_get_payload got passed a NULL pointer! \n " ) ;
2002-09-10 21:50:03 +00:00
return 0 ;
}
2006-03-22 12:23:55 +00:00
priv = ( pva_priv_t * ) d - > priv ;
2002-10-07 22:13:34 +00:00
d - > filepos = stream_tell ( d - > stream ) ;
2002-09-10 21:50:03 +00:00
if ( d - > stream - > eof )
{
mp_msg ( MSGT_DEMUX , MSGL_V , " demux_pva: pva_get_payload() detected stream->eof!!! \n " ) ;
return 0 ;
}
//printf("priv->just_synced %s\n",priv->just_synced?"SET":"UNSET");
2002-10-07 22:13:34 +00:00
# ifdef PVA_NEW_PREBYTES_CODE
if ( priv - > prebytes_delivered )
/* The previous call to this fn has delivered the preBytes. Then we are already inside
* the payload . Let ' s just deliver the video along with its right PTS , the one we stored
* in the priv structure and was in the PVA header before the PreBytes .
*/
{
//printf("prebytes_delivered=1. Resetting.\n");
payload - > size = priv - > video_size_after_prebytes ;
payload - > pts = priv - > video_pts_after_prebytes ;
payload - > is_packet_start = 1 ;
payload - > offset = stream_tell ( d - > stream ) ;
payload - > type = VIDEOSTREAM ;
priv - > prebytes_delivered = 0 ;
return 1 ;
}
# endif
2002-09-10 21:50:03 +00:00
if ( ! priv - > just_synced )
{
if ( stream_read_word ( d - > stream ) ! = ( ( ' A ' < < 8 ) | ' V ' ) )
{
2006-01-12 20:04:36 +00:00
mp_msg ( MSGT_DEMUX , MSGL_V , " demux_pva: pva_get_payload() missed a SyncWord at % " PRId64 " !! Trying to sync... \n " , ( int64_t ) stream_tell ( d - > stream ) ) ;
2002-10-07 22:13:34 +00:00
if ( ! pva_sync ( d ) )
{
if ( ! d - > stream - > eof )
{
mp_msg ( MSGT_DEMUX , MSGL_ERR , " demux_pva: couldn't sync! (broken file?) " ) ;
}
return 0 ;
}
2002-09-10 21:50:03 +00:00
}
}
if ( priv - > just_synced )
{
payload - > type = priv - > synced_stream_id ;
priv - > just_synced = 0 ;
}
else
{
payload - > type = stream_read_char ( d - > stream ) ;
stream_skip ( d - > stream , 2 ) ; //counter and reserved
}
flags = stream_read_char ( d - > stream ) ;
payload - > is_packet_start = flags & 0x10 ;
pack_size = le2me_16 ( stream_read_word ( d - > stream ) ) ;
2006-01-12 20:04:36 +00:00
mp_msg ( MSGT_DEMUX , MSGL_DBG2 , " demux_pva::pva_get_payload(): pack_size=%u field read at offset % " PRIu64 " \n " , pack_size , ( int64_t ) stream_tell ( d - > stream ) - 2 ) ;
2002-09-10 21:50:03 +00:00
pva_payload_start = stream_tell ( d - > stream ) ;
next_offset = pva_payload_start + pack_size ;
2002-10-07 22:13:34 +00:00
/*
* The code in the # ifdef directive below is a hack needed to get badly formatted PVA files
* such as the ones written by MultiDec played back correctly .
* Basically , it works like this : if the PVA packet does not signal a PES header , but the
* payload looks like one , let ' s assume it IS one . It has worked for me up to now .
* It can be disabled since it ' s quite an ugly hack and could potentially break things up
* if the PVA audio payload happens to start with 0x000001 even without being a non signalled
* PES header start .
* Though it ' s quite unlikely , it potentially could ( AFAIK ) .
*/
# ifdef DEMUX_PVA_MULTIDEC_HACK
if ( payload - > type = = MAINAUDIOSTREAM )
{
stream_read ( d - > stream , buffer , 3 ) ;
if ( buffer [ 0 ] = = 0x00 & & buffer [ 1 ] = = 0x00 & & buffer [ 2 ] = = 0x01 & & ! payload - > is_packet_start )
{
mp_msg ( MSGT_DEMUX , MSGL_V , " demux_pva: suspecting non signaled audio PES packet start. Maybe file by MultiDec? \n " ) ;
payload - > is_packet_start = 1 ;
}
stream_seek ( d - > stream , stream_tell ( d - > stream ) - 3 ) ;
}
# endif
2002-09-10 21:50:03 +00:00
if ( ! payload - > is_packet_start )
{
payload - > offset = stream_tell ( d - > stream ) ;
payload - > size = pack_size ;
}
else
{ //here comes the good part...
switch ( payload - > type )
{
case VIDEOSTREAM :
2002-10-07 22:13:34 +00:00
payload - > pts = ( float ) ( le2me_32 ( stream_read_dword ( d - > stream ) ) ) / 90000 ;
//printf("Video PTS: %f\n",payload->pts);
if ( ( flags & 0x03 )
# ifdef PVA_NEW_PREBYTES_CODE
& & ! priv - > prebytes_delivered
# endif
)
2002-09-10 21:50:03 +00:00
{
2002-10-07 22:13:34 +00:00
# ifndef PVA_NEW_PREBYTES_CODE
2002-09-10 21:50:03 +00:00
dp = new_demux_packet ( flags & 0x03 ) ;
stream_read ( d - > stream , dp - > buffer , flags & 0x03 ) ; //read PreBytes
ds_add_packet ( d - > video , dp ) ;
2002-10-07 22:13:34 +00:00
# else
//printf("Delivering prebytes. Setting prebytes_delivered.");
payload - > offset = stream_tell ( d - > stream ) ;
payload - > size = flags & 0x03 ;
priv - > video_pts_after_prebytes = payload - > pts ;
priv - > video_size_after_prebytes = pack_size - 4 - ( flags & 0x03 ) ;
payload - > pts = priv - > last_video_pts ;
payload - > is_packet_start = 0 ;
priv - > prebytes_delivered = 1 ;
return 1 ;
# endif
2002-09-10 21:50:03 +00:00
}
2002-10-07 22:13:34 +00:00
2002-09-10 21:50:03 +00:00
//now we are at real beginning of payload.
payload - > offset = stream_tell ( d - > stream ) ;
2002-10-07 22:13:34 +00:00
//size is pack_size minus PTS size minus PreBytes size.
2002-09-10 21:50:03 +00:00
payload - > size = pack_size - 4 - ( flags & 0x03 ) ;
break ;
case MAINAUDIOSTREAM :
2002-10-07 22:13:34 +00:00
stream_skip ( d - > stream , 3 ) ; //FIXME properly parse PES header.
//printf("StreamID in audio PES header: 0x%2X\n",stream_read_char(d->stream));
stream_skip ( d - > stream , 4 ) ;
buffer [ 255 ] = stream_read_char ( d - > stream ) ;
2002-09-10 21:50:03 +00:00
pes_head_len = stream_read_char ( d - > stream ) ;
stream_read ( d - > stream , buffer , pes_head_len ) ;
2002-10-07 22:13:34 +00:00
if ( ! buffer [ 255 ] & 0x80 ) //PES header does not contain PTS.
{
mp_msg ( MSGT_DEMUX , MSGL_V , " Audio PES packet does not contain PTS. (pes_head_len=%d) \n " , pes_head_len ) ;
payload - > pts = priv - > last_audio_pts ;
break ;
}
else //PES header DOES contain PTS
2002-09-10 21:50:03 +00:00
{
2002-10-07 22:13:34 +00:00
if ( ( buffer [ 0 ] & 0xf0 ) ! = 0x20 ) // PTS badly formatted
{
mp_msg ( MSGT_DEMUX , MSGL_V , " demux_pva: expected audio PTS but badly formatted... (read 0x%02X). Falling back to previous PTS (hack). \n " , buffer [ 0 ] ) ;
payload - > pts = priv - > last_audio_pts ;
// return 0;
}
else
{
uint64_t temp_pts ;
temp_pts = 0LL ;
temp_pts | = ( ( uint64_t ) ( buffer [ 0 ] & 0x0e ) < < 29 ) ;
temp_pts | = buffer [ 1 ] < < 22 ;
temp_pts | = ( buffer [ 2 ] & 0xfe ) < < 14 ;
temp_pts | = buffer [ 3 ] < < 7 ;
temp_pts | = ( buffer [ 4 ] & 0xfe ) > > 1 ;
/*
* PTS parsing is hopefully finished .
*/
payload - > pts = ( float ) le2me_64 ( temp_pts ) / 90000 ;
}
2002-09-10 21:50:03 +00:00
}
payload - > offset = stream_tell ( d - > stream ) ;
payload - > size = pack_size - stream_tell ( d - > stream ) + pva_payload_start ;
break ;
}
}
2002-09-22 02:33:28 +00:00
return 1 ;
2002-09-10 21:50:03 +00:00
}
2006-02-17 01:57:41 +00:00
static void demux_seek_pva ( demuxer_t * demuxer , float rel_seek_secs , float audio_delay , int flags )
2002-09-10 21:50:03 +00:00
{
int total_bitrate = 0 ;
2002-10-07 22:13:34 +00:00
off_t dest_offset ;
2002-09-10 21:50:03 +00:00
pva_priv_t * priv = demuxer - > priv ;
total_bitrate = ( ( sh_audio_t * ) demuxer - > audio - > sh ) - > i_bps + ( ( sh_video_t * ) demuxer - > video - > sh ) - > i_bps ;
/*
* Compute absolute offset inside the stream . Approximate total bitrate with sum of bitrates
* reported by the audio and video codecs . The seek is not accurate because , just like
* with MPEG streams , the bitrate is not constant . Moreover , we do not take into account
* the overhead caused by PVA and PES headers .
* If the calculated absolute offset is negative , seek to the beginning of the file .
*/
dest_offset = stream_tell ( demuxer - > stream ) + rel_seek_secs * total_bitrate ;
if ( dest_offset < 0 ) dest_offset = 0 ;
stream_seek ( demuxer - > stream , dest_offset ) ;
if ( ! pva_sync ( demuxer ) )
{
mp_msg ( MSGT_DEMUX , MSGL_V , " demux_pva: Couldn't seek! \n " ) ;
2005-12-11 17:21:02 +00:00
return ;
2002-09-10 21:50:03 +00:00
}
/*
* Reset the PTS info inside the pva_priv_t structure . This way we don ' t deliver
* data with the wrong PTSs ( the ones we had before seeking ) .
*
*/
priv - > last_video_pts = - 1 ;
priv - > last_audio_pts = - 1 ;
}
2005-08-05 19:57:47 +00:00
static void demux_close_pva ( demuxer_t * demuxer )
2002-09-10 21:50:03 +00:00
{
if ( demuxer - > priv )
{
free ( demuxer - > priv ) ;
demuxer - > priv = NULL ;
}
}
2005-08-05 19:57:47 +00:00
demuxer_desc_t demuxer_desc_pva = {
" PVA demuxer " ,
" pva " ,
" PVA " ,
" Matteo Giani " ,
" streams from DVB cards " ,
DEMUXER_TYPE_PVA ,
0 , // unsafe autodetect
pva_check_file ,
demux_pva_fill_buffer ,
demux_open_pva ,
demux_close_pva ,
demux_seek_pva ,
NULL
} ;