You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
834 lines
26 KiB
834 lines
26 KiB
/*****************************************************************************
|
|
* flac.c : FLAC demux module for vlc
|
|
*****************************************************************************
|
|
* Copyright (C) 2001-2008 VLC authors and VideoLAN
|
|
*
|
|
* Authors: Gildas Bazin <gbazin@netcourrier.com>
|
|
* Laurent Aimar <fenrir@via.ecp.fr>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 2.1 of the License, or
|
|
* (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 Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser 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.
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
* Preamble
|
|
*****************************************************************************/
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <vlc_common.h>
|
|
#include <vlc_plugin.h>
|
|
#include <vlc_demux.h>
|
|
#include <vlc_meta.h> /* vlc_meta_* */
|
|
#include <vlc_input.h> /* vlc_input_attachment, vlc_seekpoint */
|
|
#include <vlc_codec.h> /* decoder_t */
|
|
#include <vlc_charset.h> /* EnsureUTF8 */
|
|
|
|
#include <assert.h>
|
|
#include "xiph_metadata.h" /* vorbis comments */
|
|
#include "../packetizer/flac.h"
|
|
|
|
/*****************************************************************************
|
|
* Module descriptor
|
|
*****************************************************************************/
|
|
static int Open ( vlc_object_t * );
|
|
static void Close ( vlc_object_t * );
|
|
|
|
vlc_module_begin ()
|
|
set_description( N_("FLAC demuxer") )
|
|
set_capability( "demux", 155 )
|
|
set_category( CAT_INPUT )
|
|
set_subcategory( SUBCAT_INPUT_DEMUX )
|
|
set_callbacks( Open, Close )
|
|
add_shortcut( "flac" )
|
|
vlc_module_end ()
|
|
|
|
/*****************************************************************************
|
|
* Local prototypes
|
|
*****************************************************************************/
|
|
static int Demux ( demux_t * );
|
|
static int Control( demux_t *, int, va_list );
|
|
|
|
static int ParseHeaders( demux_t *, es_format_t * );
|
|
|
|
typedef struct
|
|
{
|
|
vlc_tick_t i_time_offset;
|
|
uint64_t i_byte_offset;
|
|
} flac_seekpoint_t;
|
|
|
|
typedef struct
|
|
{
|
|
bool b_start;
|
|
int i_next_block_flags;
|
|
es_out_id_t *p_es;
|
|
block_t *p_current_block;
|
|
|
|
/* Packetizer */
|
|
decoder_t *p_packetizer;
|
|
|
|
vlc_meta_t *p_meta;
|
|
|
|
vlc_tick_t i_pts;
|
|
struct flac_stream_info stream_info;
|
|
bool b_stream_info;
|
|
|
|
vlc_tick_t i_length; /* Length from stream info */
|
|
uint64_t i_data_pos;
|
|
|
|
/* */
|
|
int i_seekpoint;
|
|
flac_seekpoint_t **seekpoint;
|
|
|
|
/* title/chapters seekpoints */
|
|
int i_title_seekpoints;
|
|
seekpoint_t **pp_title_seekpoints;
|
|
|
|
/* */
|
|
int i_attachments;
|
|
input_attachment_t **attachments;
|
|
int i_cover_idx;
|
|
int i_cover_score;
|
|
} demux_sys_t;
|
|
|
|
#define FLAC_PACKET_SIZE 16384
|
|
#define FLAC_MAX_PREROLL VLC_TICK_FROM_SEC(4)
|
|
#define FLAC_MAX_SLOW_PREROLL VLC_TICK_FROM_SEC(45)
|
|
|
|
/*****************************************************************************
|
|
* Open: initializes ES structures
|
|
*****************************************************************************/
|
|
static int Open( vlc_object_t * p_this )
|
|
{
|
|
demux_t *p_demux = (demux_t*)p_this;
|
|
demux_sys_t *p_sys;
|
|
const uint8_t *p_peek;
|
|
es_format_t fmt;
|
|
|
|
/* Have a peep at the show. */
|
|
if( vlc_stream_Peek( p_demux->s, &p_peek, 4 ) < 4 ) return VLC_EGENERIC;
|
|
|
|
if( p_peek[0]!='f' || p_peek[1]!='L' || p_peek[2]!='a' || p_peek[3]!='C' )
|
|
{
|
|
if( !p_demux->obj.force
|
|
&& !demux_IsContentType( p_demux, "audio/flac" ) )
|
|
return VLC_EGENERIC;
|
|
|
|
/* User forced */
|
|
msg_Err( p_demux, "this doesn't look like a flac stream, "
|
|
"continuing anyway" );
|
|
}
|
|
|
|
p_sys = malloc( sizeof( demux_sys_t ) );
|
|
if( unlikely(p_sys == NULL) )
|
|
return VLC_ENOMEM;
|
|
|
|
p_demux->pf_demux = Demux;
|
|
p_demux->pf_control = Control;
|
|
p_demux->p_sys = p_sys;
|
|
p_sys->b_start = true;
|
|
p_sys->i_next_block_flags = 0;
|
|
p_sys->p_packetizer = NULL;
|
|
p_sys->p_meta = NULL;
|
|
p_sys->i_length = 0;
|
|
p_sys->i_pts = VLC_TICK_INVALID;
|
|
p_sys->b_stream_info = false;
|
|
p_sys->p_es = NULL;
|
|
p_sys->p_current_block = NULL;
|
|
TAB_INIT( p_sys->i_seekpoint, p_sys->seekpoint );
|
|
TAB_INIT( p_sys->i_attachments, p_sys->attachments);
|
|
TAB_INIT( p_sys->i_title_seekpoints, p_sys->pp_title_seekpoints );
|
|
p_sys->i_cover_idx = 0;
|
|
p_sys->i_cover_score = 0;
|
|
|
|
es_format_Init( &fmt, AUDIO_ES, VLC_CODEC_FLAC );
|
|
|
|
/* We need to read and store the STREAMINFO metadata into fmt extra */
|
|
if( ParseHeaders( p_demux, &fmt ) )
|
|
goto error;
|
|
|
|
/* Load the FLAC packetizer */
|
|
p_sys->p_packetizer = demux_PacketizerNew( p_demux, &fmt, "flac" );
|
|
if( !p_sys->p_packetizer )
|
|
goto error;
|
|
|
|
if( p_sys->i_cover_idx < p_sys->i_attachments )
|
|
{
|
|
char psz_url[128];
|
|
if( !p_sys->p_meta )
|
|
p_sys->p_meta = vlc_meta_New();
|
|
snprintf( psz_url, sizeof(psz_url), "attachment://%s",
|
|
p_sys->attachments[p_sys->i_cover_idx]->psz_name );
|
|
vlc_meta_Set( p_sys->p_meta, vlc_meta_ArtworkURL, psz_url );
|
|
}
|
|
|
|
p_sys->p_es = es_out_Add( p_demux->out, &p_sys->p_packetizer->fmt_in );
|
|
if( !p_sys->p_es )
|
|
goto error;
|
|
|
|
return VLC_SUCCESS;
|
|
error:
|
|
Close( p_this );
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Close: frees unused data
|
|
*****************************************************************************/
|
|
static void Close( vlc_object_t * p_this )
|
|
{
|
|
demux_t *p_demux = (demux_t*)p_this;
|
|
demux_sys_t *p_sys = p_demux->p_sys;
|
|
|
|
if( p_sys->p_current_block )
|
|
block_Release( p_sys->p_current_block );
|
|
|
|
for( int i = 0; i < p_sys->i_seekpoint; i++ )
|
|
free(p_sys->seekpoint[i]);
|
|
TAB_CLEAN( p_sys->i_seekpoint, p_sys->seekpoint );
|
|
|
|
for( int i = 0; i < p_sys->i_attachments; i++ )
|
|
vlc_input_attachment_Delete( p_sys->attachments[i] );
|
|
TAB_CLEAN( p_sys->i_attachments, p_sys->attachments);
|
|
|
|
for( int i = 0; i < p_sys->i_title_seekpoints; i++ )
|
|
vlc_seekpoint_Delete( p_sys->pp_title_seekpoints[i] );
|
|
TAB_CLEAN( p_sys->i_title_seekpoints, p_sys->pp_title_seekpoints );
|
|
|
|
/* Delete the decoder */
|
|
if( p_sys->p_packetizer )
|
|
demux_PacketizerDestroy( p_sys->p_packetizer );
|
|
|
|
if( p_sys->p_meta )
|
|
vlc_meta_Delete( p_sys->p_meta );
|
|
free( p_sys );
|
|
}
|
|
|
|
static block_t *GetPacketizedBlock( decoder_t *p_packetizer,
|
|
const struct flac_stream_info *streaminfo,
|
|
block_t **pp_current_block )
|
|
{
|
|
block_t *p_block = p_packetizer->pf_packetize( p_packetizer, pp_current_block );
|
|
if( p_block )
|
|
{
|
|
if( p_block->i_buffer >= FLAC_HEADER_SIZE_MAX )
|
|
{
|
|
struct flac_header_info headerinfo;
|
|
int i_ret = FLAC_ParseSyncInfo( p_block->p_buffer, streaminfo, NULL, &headerinfo );
|
|
assert( i_ret != 0 ); /* Same as packetizer */
|
|
/* Use Frame PTS, not the interpolated one */
|
|
p_block->i_dts = p_block->i_pts = headerinfo.i_pts;
|
|
}
|
|
}
|
|
return p_block;
|
|
}
|
|
|
|
static void FlushPacketizer( decoder_t *p_packetizer )
|
|
{
|
|
if( p_packetizer->pf_flush )
|
|
p_packetizer->pf_flush( p_packetizer );
|
|
else
|
|
{
|
|
block_t *p_block_out;
|
|
while( (p_block_out = p_packetizer->pf_packetize( p_packetizer, NULL )) )
|
|
block_Release( p_block_out );
|
|
}
|
|
}
|
|
|
|
static void Reset( demux_sys_t *p_sys )
|
|
{
|
|
p_sys->i_pts = VLC_TICK_INVALID;
|
|
|
|
FlushPacketizer( p_sys->p_packetizer );
|
|
if( p_sys->p_current_block )
|
|
{
|
|
block_Release( p_sys->p_current_block );
|
|
p_sys->p_current_block = NULL;
|
|
}
|
|
}
|
|
|
|
static int RefineSeek( demux_t *p_demux, vlc_tick_t i_time, double i_bytemicrorate,
|
|
uint64_t i_lowpos, uint64_t i_highpos )
|
|
{
|
|
demux_sys_t *p_sys = p_demux->p_sys;
|
|
bool b_found = false;
|
|
block_t *p_block_out;
|
|
block_t *p_block_in;
|
|
|
|
unsigned i_frame_size = FLAC_FRAME_SIZE_MIN;
|
|
|
|
bool b_canfastseek = false;
|
|
(int) vlc_stream_Control( p_demux->s, STREAM_CAN_FASTSEEK, &b_canfastseek );
|
|
|
|
uint64_t i_start_pos = vlc_stream_Tell( p_demux->s );
|
|
|
|
while( !b_found )
|
|
{
|
|
FlushPacketizer( p_sys->p_packetizer );
|
|
|
|
p_block_out = NULL;
|
|
p_block_in = NULL;
|
|
|
|
while( !p_block_out )
|
|
{
|
|
if( !p_block_in )
|
|
{
|
|
if( !(p_block_in = vlc_stream_Block( p_demux->s, i_frame_size )) )
|
|
break;
|
|
}
|
|
|
|
p_block_out = GetPacketizedBlock( p_sys->p_packetizer,
|
|
p_sys->b_stream_info ? &p_sys->stream_info : NULL,
|
|
&p_block_in );
|
|
}
|
|
|
|
if( !p_block_out )
|
|
{
|
|
if( p_block_in )
|
|
block_Release( p_block_in );
|
|
break;
|
|
}
|
|
|
|
if( p_block_out->i_buffer > i_frame_size )
|
|
i_frame_size = p_block_out->i_buffer;
|
|
|
|
/* If we are further than wanted block */
|
|
if( p_block_out->i_dts >= i_time )
|
|
{
|
|
vlc_tick_t i_diff = p_block_out->i_dts - i_time;
|
|
/* Not in acceptable approximation range */
|
|
if( i_diff > VLC_TICK_FROM_MS(100) && i_diff / i_bytemicrorate > i_frame_size )
|
|
{
|
|
i_highpos = i_start_pos;
|
|
i_start_pos -= ( i_diff / i_bytemicrorate );
|
|
i_start_pos = __MAX(i_start_pos, i_lowpos + i_frame_size);
|
|
}
|
|
else b_found = true;
|
|
}
|
|
else
|
|
{
|
|
vlc_tick_t i_diff = i_time - p_block_out->i_dts;
|
|
/* Not in acceptable NEXT_TIME demux range */
|
|
if( i_diff >= ((b_canfastseek) ? FLAC_MAX_PREROLL : FLAC_MAX_SLOW_PREROLL) &&
|
|
i_diff / i_bytemicrorate > i_frame_size )
|
|
{
|
|
i_lowpos = i_start_pos;
|
|
i_start_pos += ( i_diff / i_bytemicrorate );
|
|
i_start_pos = __MIN(i_start_pos, i_highpos - i_frame_size);
|
|
}
|
|
else b_found = true;
|
|
}
|
|
|
|
if( p_block_out )
|
|
block_Release( p_block_out );
|
|
if( p_block_in )
|
|
block_Release( p_block_in );
|
|
|
|
if( !b_found )
|
|
{
|
|
if( i_highpos < i_lowpos || i_highpos - i_lowpos < i_frame_size )
|
|
break;
|
|
|
|
if( VLC_SUCCESS != vlc_stream_Seek( p_demux->s, i_start_pos ) )
|
|
break;
|
|
}
|
|
}
|
|
|
|
return b_found ? VLC_SUCCESS : VLC_EGENERIC;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Demux: reads and demuxes data packets
|
|
*****************************************************************************
|
|
* Returns -1 in case of error, 0 in case of EOF, 1 otherwise
|
|
*****************************************************************************/
|
|
static int Demux( demux_t *p_demux )
|
|
{
|
|
demux_sys_t *p_sys = p_demux->p_sys;
|
|
block_t *p_block_out;
|
|
|
|
bool b_eof = false;
|
|
if( p_sys->p_current_block == NULL )
|
|
{
|
|
p_sys->p_current_block = vlc_stream_Block( p_demux->s, FLAC_PACKET_SIZE );
|
|
b_eof = (p_sys->p_current_block == NULL);
|
|
}
|
|
|
|
if ( p_sys->p_current_block )
|
|
{
|
|
p_sys->p_current_block->i_flags = p_sys->i_next_block_flags;
|
|
p_sys->i_next_block_flags = 0;
|
|
p_sys->p_current_block->i_pts =
|
|
p_sys->p_current_block->i_dts = p_sys->b_start ? VLC_TICK_0 : VLC_TICK_INVALID;
|
|
}
|
|
|
|
while( (p_block_out = GetPacketizedBlock( p_sys->p_packetizer,
|
|
p_sys->b_stream_info ? &p_sys->stream_info : NULL,
|
|
p_sys->p_current_block ? &p_sys->p_current_block : NULL ) ) )
|
|
{
|
|
/* Only clear on output when packet is accepted as sync #17111 */
|
|
p_sys->b_start = false;
|
|
while( p_block_out )
|
|
{
|
|
block_t *p_next = p_block_out->p_next;
|
|
|
|
p_block_out->p_next = NULL;
|
|
|
|
/* set PCR */
|
|
if( unlikely(p_sys->i_pts == VLC_TICK_INVALID) )
|
|
es_out_SetPCR( p_demux->out, __MAX(p_block_out->i_dts - 1, VLC_TICK_0) );
|
|
|
|
p_sys->i_pts = p_block_out->i_dts;
|
|
|
|
es_out_Send( p_demux->out, p_sys->p_es, p_block_out );
|
|
|
|
es_out_SetPCR( p_demux->out, p_sys->i_pts );
|
|
|
|
p_block_out = p_next;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return b_eof ? VLC_DEMUXER_EOF : VLC_DEMUXER_SUCCESS;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Control:
|
|
*****************************************************************************/
|
|
static vlc_tick_t ControlGetLength( demux_t *p_demux )
|
|
{
|
|
demux_sys_t *p_sys = p_demux->p_sys;
|
|
const uint64_t i_size = stream_Size(p_demux->s) - p_sys->i_data_pos;
|
|
vlc_tick_t i_length = p_sys->i_length;
|
|
int i;
|
|
|
|
/* Try to fix length using seekpoint and current size for truncated file */
|
|
for( i = p_sys->i_seekpoint-1; i >= 0; i-- )
|
|
{
|
|
flac_seekpoint_t *s = p_sys->seekpoint[i];
|
|
if( s->i_byte_offset <= i_size )
|
|
{
|
|
if( i+1 < p_sys->i_seekpoint )
|
|
{
|
|
/* Broken file */
|
|
flac_seekpoint_t *n = p_sys->seekpoint[i+1];
|
|
assert( n->i_byte_offset != s->i_byte_offset); /* Should be ensured by ParseSeekTable */
|
|
i_length = s->i_time_offset + (n->i_time_offset-s->i_time_offset) * (i_size-s->i_byte_offset) / (n->i_byte_offset-s->i_byte_offset);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return i_length;
|
|
}
|
|
|
|
static vlc_tick_t ControlGetTime( demux_t *p_demux )
|
|
{
|
|
demux_sys_t *p_sys = p_demux->p_sys;
|
|
return p_sys->i_pts;
|
|
}
|
|
|
|
static int ControlSetTime( demux_t *p_demux, vlc_tick_t i_time )
|
|
{
|
|
demux_sys_t *p_sys = p_demux->p_sys;
|
|
bool b_seekable;
|
|
int i;
|
|
|
|
/* */
|
|
vlc_stream_Control( p_demux->s, STREAM_CAN_SEEK, &b_seekable );
|
|
if( !b_seekable )
|
|
return VLC_EGENERIC;
|
|
|
|
const vlc_tick_t i_length = ControlGetLength( p_demux );
|
|
if( i_length <= 0 )
|
|
return VLC_EGENERIC;
|
|
|
|
const uint64_t i_stream_size = stream_Size( p_demux->s );
|
|
if( i_stream_size <= p_sys->i_data_pos )
|
|
return VLC_EGENERIC;
|
|
|
|
const double i_bytemicrorate = (double) i_length / (i_stream_size - p_sys->i_data_pos);
|
|
if( i_bytemicrorate == 0 )
|
|
return VLC_EGENERIC;
|
|
|
|
uint64_t i_lower = p_sys->i_data_pos;
|
|
uint64_t i_upper = i_stream_size;
|
|
uint64_t i_start_pos;
|
|
|
|
assert( p_sys->i_seekpoint > 0 ); /* ReadMeta ensure at least (0,0) */
|
|
if( p_sys->i_seekpoint > 1 )
|
|
{
|
|
/* lookup base offset */
|
|
for( i = p_sys->i_seekpoint-1; i >= 0; i-- )
|
|
{
|
|
if( p_sys->seekpoint[i]->i_time_offset <= i_time )
|
|
break;
|
|
}
|
|
|
|
i_lower = p_sys->seekpoint[0]->i_byte_offset + p_sys->i_data_pos;
|
|
if( i+1 < p_sys->i_seekpoint )
|
|
i_upper = p_sys->seekpoint[i+1]->i_byte_offset + p_sys->i_data_pos;
|
|
|
|
i_start_pos = i_lower;
|
|
}
|
|
else
|
|
{
|
|
i_start_pos = i_time / i_bytemicrorate;
|
|
}
|
|
|
|
if( VLC_SUCCESS != vlc_stream_Seek( p_demux->s, i_start_pos ) )
|
|
return VLC_EGENERIC;
|
|
|
|
int i_ret = RefineSeek( p_demux, i_time, i_bytemicrorate, i_lower, i_upper );
|
|
if( i_ret == VLC_SUCCESS )
|
|
{
|
|
p_sys->i_next_block_flags |= BLOCK_FLAG_DISCONTINUITY;
|
|
Reset( p_sys );
|
|
es_out_Control( p_demux->out, ES_OUT_SET_NEXT_DISPLAY_TIME, i_time );
|
|
}
|
|
|
|
return i_ret;
|
|
}
|
|
|
|
static int Control( demux_t *p_demux, int i_query, va_list args )
|
|
{
|
|
demux_sys_t *p_sys = p_demux->p_sys;
|
|
|
|
if( i_query == DEMUX_GET_META )
|
|
{
|
|
vlc_meta_t *p_meta = va_arg( args, vlc_meta_t * );
|
|
if( p_sys->p_meta )
|
|
vlc_meta_Merge( p_meta, p_sys->p_meta );
|
|
return VLC_SUCCESS;
|
|
}
|
|
else if( i_query == DEMUX_HAS_UNSUPPORTED_META )
|
|
{
|
|
bool *pb_bool = va_arg( args, bool* );
|
|
*pb_bool = true;
|
|
return VLC_SUCCESS;
|
|
}
|
|
else if( i_query == DEMUX_GET_LENGTH )
|
|
{
|
|
*va_arg( args, vlc_tick_t * ) = ControlGetLength( p_demux );
|
|
return VLC_SUCCESS;
|
|
}
|
|
else if( i_query == DEMUX_SET_TIME )
|
|
{
|
|
return ControlSetTime( p_demux, va_arg( args, vlc_tick_t ) );
|
|
}
|
|
else if( i_query == DEMUX_SET_POSITION )
|
|
{
|
|
const double f = va_arg( args, double );
|
|
vlc_tick_t i_length = ControlGetLength( p_demux );
|
|
int i_ret;
|
|
if( i_length > 0 )
|
|
{
|
|
i_ret = ControlSetTime( p_demux, i_length * f );
|
|
if( i_ret == VLC_SUCCESS )
|
|
return i_ret;
|
|
}
|
|
/* just byte pos seek */
|
|
i_ret = vlc_stream_Seek( p_demux->s, (int64_t) (f * stream_Size( p_demux->s )) );
|
|
if( i_ret == VLC_SUCCESS )
|
|
{
|
|
p_sys->i_next_block_flags |= BLOCK_FLAG_DISCONTINUITY;
|
|
Reset( p_sys );
|
|
}
|
|
return i_ret;
|
|
}
|
|
else if( i_query == DEMUX_GET_TIME )
|
|
{
|
|
*va_arg( args, vlc_tick_t * ) = ControlGetTime( p_demux );
|
|
return VLC_SUCCESS;
|
|
}
|
|
else if( i_query == DEMUX_GET_POSITION )
|
|
{
|
|
const vlc_tick_t i_length = ControlGetLength(p_demux);
|
|
if( i_length > 0 )
|
|
{
|
|
vlc_tick_t current = ControlGetTime(p_demux);
|
|
if( current <= i_length )
|
|
{
|
|
*(va_arg( args, double * )) = (double)current / (double)i_length;
|
|
return VLC_SUCCESS;
|
|
}
|
|
}
|
|
/* Else fallback on byte position */
|
|
}
|
|
else if( i_query == DEMUX_GET_ATTACHMENTS )
|
|
{
|
|
input_attachment_t ***ppp_attach =
|
|
va_arg( args, input_attachment_t *** );
|
|
int *pi_int = va_arg( args, int * );
|
|
|
|
if( p_sys->i_attachments <= 0 )
|
|
return VLC_EGENERIC;
|
|
|
|
*ppp_attach = vlc_alloc( p_sys->i_attachments, sizeof(input_attachment_t*) );
|
|
if( !*ppp_attach )
|
|
return VLC_EGENERIC;
|
|
*pi_int = p_sys->i_attachments;
|
|
for( int i = 0; i < p_sys->i_attachments; i++ )
|
|
(*ppp_attach)[i] = vlc_input_attachment_Duplicate( p_sys->attachments[i] );
|
|
return VLC_SUCCESS;
|
|
}
|
|
else if( i_query == DEMUX_GET_TITLE_INFO )
|
|
{
|
|
input_title_t ***ppp_title = va_arg( args, input_title_t *** );
|
|
int *pi_int = va_arg( args, int * );
|
|
int *pi_title_offset = va_arg( args, int * );
|
|
int *pi_seekpoint_offset = va_arg( args, int * );
|
|
|
|
if( !p_sys->i_title_seekpoints )
|
|
return VLC_EGENERIC;
|
|
|
|
*pi_int = 1;
|
|
*ppp_title = malloc( sizeof(input_title_t*) );
|
|
if(!*ppp_title)
|
|
return VLC_EGENERIC;
|
|
|
|
input_title_t *p_title = (*ppp_title)[0] = vlc_input_title_New();
|
|
if(!p_title)
|
|
{
|
|
free(*ppp_title);
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
p_title->seekpoint = vlc_alloc( p_sys->i_title_seekpoints, sizeof(seekpoint_t*) );
|
|
if(!p_title->seekpoint)
|
|
{
|
|
vlc_input_title_Delete(p_title);
|
|
free(*ppp_title);
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
p_title->i_seekpoint = p_sys->i_title_seekpoints;
|
|
for( int i = 0; i < p_title->i_seekpoint; i++ )
|
|
p_title->seekpoint[i] = vlc_seekpoint_Duplicate( p_sys->pp_title_seekpoints[i] );
|
|
|
|
*pi_title_offset = 0;
|
|
*pi_seekpoint_offset = 0;
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
else if( i_query == DEMUX_SET_TITLE )
|
|
{
|
|
const int i_title = va_arg( args, int );
|
|
if( i_title != 0 )
|
|
return VLC_EGENERIC;
|
|
return VLC_SUCCESS;
|
|
}
|
|
else if( i_query == DEMUX_SET_SEEKPOINT )
|
|
{
|
|
const int i_seekpoint = va_arg( args, int );
|
|
if( !p_sys->i_title_seekpoints || i_seekpoint >= p_sys->i_title_seekpoints )
|
|
return VLC_EGENERIC;
|
|
return ControlSetTime( p_demux, p_sys->pp_title_seekpoints[i_seekpoint]->i_time_offset );
|
|
}
|
|
|
|
return demux_vaControlHelper( p_demux->s, p_sys->i_data_pos, -1,
|
|
8*0, 1, i_query, args );
|
|
}
|
|
|
|
enum
|
|
{
|
|
META_STREAMINFO = 0,
|
|
META_SEEKTABLE = 3,
|
|
META_COMMENT = 4,
|
|
META_PICTURE = 6,
|
|
};
|
|
|
|
static inline int Get24bBE( const uint8_t *p )
|
|
{
|
|
return (p[0] << 16)|(p[1] << 8)|(p[2]);
|
|
}
|
|
|
|
static void ParseSeekTable( demux_t *p_demux, const uint8_t *p_data, size_t i_data,
|
|
unsigned i_sample_rate );
|
|
static void ParseComment( demux_t *, const uint8_t *p_data, size_t i_data );
|
|
static void ParsePicture( demux_t *, const uint8_t *p_data, size_t i_data );
|
|
|
|
static int ParseHeaders( demux_t *p_demux, es_format_t *p_fmt )
|
|
{
|
|
demux_sys_t *p_sys = p_demux->p_sys;
|
|
ssize_t i_peek;
|
|
const uint8_t *p_peek;
|
|
bool b_last;
|
|
|
|
/* Be sure we have seekpoint 0 */
|
|
flac_seekpoint_t *s = xmalloc( sizeof (*s) );
|
|
s->i_time_offset = 0;
|
|
s->i_byte_offset = 0;
|
|
TAB_APPEND( p_sys->i_seekpoint, p_sys->seekpoint, s );
|
|
|
|
uint8_t header[4];
|
|
if( vlc_stream_Read( p_demux->s, header, 4) < 4)
|
|
return VLC_EGENERIC;
|
|
|
|
if (memcmp(header, "fLaC", 4))
|
|
return VLC_EGENERIC;
|
|
|
|
b_last = 0;
|
|
while( !b_last )
|
|
{
|
|
int i_len;
|
|
int i_type;
|
|
|
|
i_peek = vlc_stream_Peek( p_demux->s, &p_peek, 4 );
|
|
if( i_peek < 4 )
|
|
break;
|
|
b_last = p_peek[0]&0x80;
|
|
i_type = p_peek[0]&0x7f;
|
|
i_len = Get24bBE( &p_peek[1] );
|
|
|
|
if( i_type == META_STREAMINFO && p_fmt->p_extra == NULL )
|
|
{
|
|
if( i_len != FLAC_STREAMINFO_SIZE ) {
|
|
msg_Err( p_demux, "invalid size %d for a STREAMINFO metadata block", i_len );
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
p_fmt->p_extra = malloc( FLAC_STREAMINFO_SIZE );
|
|
if( p_fmt->p_extra == NULL )
|
|
return VLC_EGENERIC;
|
|
|
|
if( vlc_stream_Read( p_demux->s, NULL, 4) < 4)
|
|
{
|
|
FREENULL( p_fmt->p_extra );
|
|
return VLC_EGENERIC;
|
|
}
|
|
if( vlc_stream_Read( p_demux->s, p_fmt->p_extra,
|
|
FLAC_STREAMINFO_SIZE ) != FLAC_STREAMINFO_SIZE )
|
|
{
|
|
msg_Err( p_demux, "failed to read STREAMINFO metadata block" );
|
|
FREENULL( p_fmt->p_extra );
|
|
return VLC_EGENERIC;
|
|
}
|
|
p_fmt->i_extra = FLAC_STREAMINFO_SIZE;
|
|
|
|
/* */
|
|
p_sys->b_stream_info = true;
|
|
FLAC_ParseStreamInfo( (uint8_t *) p_fmt->p_extra, &p_sys->stream_info );
|
|
|
|
p_fmt->audio.i_rate = p_sys->stream_info.sample_rate;
|
|
p_fmt->audio.i_channels = p_sys->stream_info.channels;
|
|
p_fmt->audio.i_bitspersample = p_sys->stream_info.bits_per_sample;
|
|
if( p_sys->stream_info.sample_rate > 0 )
|
|
p_sys->i_length = vlc_tick_from_samples(p_sys->stream_info.total_samples,
|
|
p_sys->stream_info.sample_rate);
|
|
|
|
continue;
|
|
}
|
|
else if( i_type == META_SEEKTABLE )
|
|
{
|
|
i_peek = vlc_stream_Peek( p_demux->s, &p_peek, 4+i_len );
|
|
if( i_peek == 4+i_len )
|
|
ParseSeekTable( p_demux, p_peek, i_peek, p_fmt->audio.i_rate );
|
|
}
|
|
else if( i_type == META_COMMENT )
|
|
{
|
|
i_peek = vlc_stream_Peek( p_demux->s, &p_peek, 4+i_len );
|
|
if( i_peek == 4+i_len )
|
|
ParseComment( p_demux, p_peek, i_peek );
|
|
}
|
|
else if( i_type == META_PICTURE )
|
|
{
|
|
i_peek = vlc_stream_Peek( p_demux->s, &p_peek, 4+i_len );
|
|
if( i_peek == 4+i_len )
|
|
ParsePicture( p_demux, p_peek, i_peek );
|
|
}
|
|
|
|
if( vlc_stream_Read( p_demux->s, NULL, 4+i_len ) < 4+i_len )
|
|
break;
|
|
}
|
|
|
|
/* */
|
|
p_sys->i_data_pos = vlc_stream_Tell( p_demux->s );
|
|
|
|
if ( p_fmt->p_extra == NULL )
|
|
return VLC_EGENERIC;
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
static void ParseSeekTable( demux_t *p_demux, const uint8_t *p_data, size_t i_data,
|
|
unsigned i_sample_rate )
|
|
{
|
|
demux_sys_t *p_sys = p_demux->p_sys;
|
|
flac_seekpoint_t *s;
|
|
size_t i;
|
|
|
|
if( i_sample_rate == 0 )
|
|
return;
|
|
|
|
/* */
|
|
for( i = 0; i < (i_data-4)/18; i++ )
|
|
{
|
|
const int64_t i_sample = GetQWBE( &p_data[4+18*i+0] );
|
|
int j;
|
|
|
|
if( i_sample < 0 || i_sample >= INT64_MAX ||
|
|
GetQWBE( &p_data[4+18*i+8] ) < FLAC_STREAMINFO_SIZE )
|
|
break;
|
|
|
|
s = xmalloc( sizeof (*s) );
|
|
s->i_time_offset = vlc_tick_from_samples(i_sample, i_sample_rate);
|
|
s->i_byte_offset = GetQWBE( &p_data[4+18*i+8] );
|
|
|
|
/* Check for duplicate entry */
|
|
for( j = 0; j < p_sys->i_seekpoint; j++ )
|
|
{
|
|
if( p_sys->seekpoint[j]->i_time_offset == s->i_time_offset ||
|
|
p_sys->seekpoint[j]->i_byte_offset == s->i_byte_offset )
|
|
{
|
|
free( s );
|
|
s = NULL;
|
|
break;
|
|
}
|
|
}
|
|
if( s )
|
|
{
|
|
TAB_APPEND( p_sys->i_seekpoint, p_sys->seekpoint, s );
|
|
}
|
|
}
|
|
/* TODO sort it by size and remove wrong seek entry (time not increasing) */
|
|
}
|
|
|
|
static void ParseComment( demux_t *p_demux, const uint8_t *p_data, size_t i_data )
|
|
{
|
|
demux_sys_t *p_sys = p_demux->p_sys;
|
|
|
|
if( i_data < 4 )
|
|
return;
|
|
|
|
vorbis_ParseComment( NULL, &p_sys->p_meta, &p_data[4], i_data - 4,
|
|
&p_sys->i_attachments, &p_sys->attachments,
|
|
&p_sys->i_cover_score, &p_sys->i_cover_idx,
|
|
&p_sys->i_title_seekpoints, &p_sys->pp_title_seekpoints, NULL, NULL );
|
|
}
|
|
|
|
static void ParsePicture( demux_t *p_demux, const uint8_t *p_data, size_t i_data )
|
|
{
|
|
demux_sys_t *p_sys = p_demux->p_sys;
|
|
|
|
i_data -= 4; p_data += 4;
|
|
|
|
input_attachment_t *p_attachment = ParseFlacPicture( p_data, i_data,
|
|
p_sys->i_attachments, &p_sys->i_cover_score, &p_sys->i_cover_idx );
|
|
if( p_attachment == NULL )
|
|
return;
|
|
|
|
TAB_APPEND( p_sys->i_attachments, p_sys->attachments, p_attachment );
|
|
}
|
|
|
|
|