flac.c 20.6 KB
Newer Older
1
/*****************************************************************************
Gildas Bazin's avatar
 
Gildas Bazin committed
2
 * flac.c : FLAC demux module for vlc
3
 *****************************************************************************
4
 * Copyright (C) 2001-2008 the VideoLAN team
5
 * $Id$
6
 *
Gildas Bazin's avatar
 
Gildas Bazin committed
7
 * Authors: Gildas Bazin <gbazin@netcourrier.com>
Laurent Aimar's avatar
Laurent Aimar committed
8
 *          Laurent Aimar <fenrir@via.ecp.fr>
9 10 11 12 13
 *
 * 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, or
 * (at your option) any later version.
14
 *
15 16 17 18 19 20 21
 * 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
Antoine Cellerier's avatar
Antoine Cellerier committed
22
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 24 25 26 27
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
28 29 30 31
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

32
#include <vlc_common.h>
33
#include <vlc_plugin.h>
Clément Stenac's avatar
Clément Stenac committed
34
#include <vlc_demux.h>
35 36 37 38 39
#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 */

Laurent Aimar's avatar
Laurent Aimar committed
40
#include <assert.h>
41
#include "vorbis.h"                   /* vorbis comments */
42 43

/*****************************************************************************
44
 * Module descriptor
45
 *****************************************************************************/
Gildas Bazin's avatar
 
Gildas Bazin committed
46 47
static int  Open  ( vlc_object_t * );
static void Close ( vlc_object_t * );
48

49 50 51 52 53 54 55 56
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 ()
57 58 59 60 61 62

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static int Demux  ( demux_t * );
static int Control( demux_t *, int, va_list );
63

Laurent Aimar's avatar
Laurent Aimar committed
64 65
static int  ReadMeta( demux_t *, uint8_t **pp_streaminfo, int *pi_streaminfo );

Gildas Bazin's avatar
 
Gildas Bazin committed
66 67
struct demux_sys_t
{
68
    bool  b_start;
Gildas Bazin's avatar
 
Gildas Bazin committed
69 70 71 72
    es_out_id_t *p_es;

    /* Packetizer */
    decoder_t *p_packetizer;
73

74
    vlc_meta_t *p_meta;
75
    audio_replay_gain_t replay_gain;
Laurent Aimar's avatar
Laurent Aimar committed
76 77 78 79 80 81 82 83 84 85 86

    int64_t i_time_offset;
    int64_t i_pts;
    int64_t i_pts_start;

    int64_t i_length; /* Length from stream info */
    int64_t i_data_pos;

    /* */
    int         i_seekpoint;
    seekpoint_t **seekpoint;
Laurent Aimar's avatar
Laurent Aimar committed
87 88

    /* */
89 90
    int                i_attachments;
    input_attachment_t **attachments;
Laurent Aimar's avatar
Laurent Aimar committed
91 92
    int                i_cover_idx;
    int                i_cover_score;
Gildas Bazin's avatar
 
Gildas Bazin committed
93 94
};

95 96
#define STREAMINFO_SIZE 38
#define FLAC_PACKET_SIZE 16384
97 98

/*****************************************************************************
Gildas Bazin's avatar
 
Gildas Bazin committed
99
 * Open: initializes ES structures
100
 *****************************************************************************/
Gildas Bazin's avatar
 
Gildas Bazin committed
101
static int Open( vlc_object_t * p_this )
102
{
103 104
    demux_t     *p_demux = (demux_t*)p_this;
    demux_sys_t *p_sys;
105
    const uint8_t *p_peek;
Laurent Aimar's avatar
Laurent Aimar committed
106 107
    uint8_t     *p_streaminfo;
    int         i_streaminfo;
108
    es_format_t fmt;
109

110
    /* Have a peep at the show. */
111
    if( stream_Peek( p_demux->s, &p_peek, 4 ) < 4 ) return VLC_EGENERIC;
112

Gildas Bazin's avatar
 
Gildas Bazin committed
113
    if( p_peek[0]!='f' || p_peek[1]!='L' || p_peek[2]!='a' || p_peek[3]!='C' )
114
    {
115 116
        if( !p_demux->b_force ) return VLC_EGENERIC;

117 118 119
        /* User forced */
        msg_Err( p_demux, "this doesn't look like a flac stream, "
                 "continuing anyway" );
120 121
    }

122 123 124 125
    p_sys = malloc( sizeof( demux_sys_t ) );
    if( unlikely(p_sys == NULL) )
        return VLC_ENOMEM;

126 127
    p_demux->pf_demux   = Demux;
    p_demux->pf_control = Control;
128
    p_demux->p_sys      = p_sys;
129
    p_sys->b_start = true;
130
    p_sys->p_meta = NULL;
131
    memset( &p_sys->replay_gain, 0, sizeof(p_sys->replay_gain) );
Laurent Aimar's avatar
Laurent Aimar committed
132 133 134 135 136 137
    p_sys->i_length = 0;
    p_sys->i_time_offset = 0;
    p_sys->i_pts = 0;
    p_sys->i_pts_start = 0;
    p_sys->p_es = NULL;
    TAB_INIT( p_sys->i_seekpoint, p_sys->seekpoint );
138
    TAB_INIT( p_sys->i_attachments, p_sys->attachments);
Laurent Aimar's avatar
Laurent Aimar committed
139 140
    p_sys->i_cover_idx = 0;
    p_sys->i_cover_score = 0;
Gildas Bazin's avatar
 
Gildas Bazin committed
141 142

    /* We need to read and store the STREAMINFO metadata */
Laurent Aimar's avatar
Laurent Aimar committed
143
    if( ReadMeta( p_demux, &p_streaminfo, &i_streaminfo ) )
144
    {
Laurent Aimar's avatar
Laurent Aimar committed
145
        free( p_sys );
146
        return VLC_EGENERIC;
147
    }
Gildas Bazin's avatar
 
Gildas Bazin committed
148

Clément Stenac's avatar
Clément Stenac committed
149
    /* Load the FLAC packetizer */
150
    /* Store STREAMINFO for the decoder and packetizer */
Laurent Aimar's avatar
Laurent Aimar committed
151
    p_streaminfo[4] |= 0x80; /* Fake this as the last metadata block */
152
    es_format_Init( &fmt, AUDIO_ES, VLC_CODEC_FLAC );
153 154
    fmt.i_extra = i_streaminfo;
    fmt.p_extra = p_streaminfo;
Gildas Bazin's avatar
 
Gildas Bazin committed
155

156 157
    p_sys->p_packetizer = demux_PacketizerNew( p_demux, &fmt, "flac" );
    if( !p_sys->p_packetizer )
Gildas Bazin's avatar
 
Gildas Bazin committed
158
    {
159
        free( p_sys );
160
        return VLC_EGENERIC;
Gildas Bazin's avatar
 
Gildas Bazin committed
161
    }
162

163
    if( p_sys->i_cover_idx < p_sys->i_attachments )
Laurent Aimar's avatar
Laurent Aimar committed
164 165 166 167 168
    {
        char psz_url[128];
        if( !p_sys->p_meta )
            p_sys->p_meta = vlc_meta_New();
        snprintf( psz_url, sizeof(psz_url), "attachment://%s",
169
                  p_sys->attachments[p_sys->i_cover_idx]->psz_name );
170
        vlc_meta_Set( p_sys->p_meta, vlc_meta_ArtworkURL, psz_url );
Laurent Aimar's avatar
Laurent Aimar committed
171
    }
172
    vlc_audio_replay_gain_MergeFromMeta( &p_sys->replay_gain, p_sys->p_meta );
173
    return VLC_SUCCESS;
Gildas Bazin's avatar
 
Gildas Bazin committed
174 175 176 177 178 179 180
}

/*****************************************************************************
 * Close: frees unused data
 *****************************************************************************/
static void Close( vlc_object_t * p_this )
{
181 182
    demux_t     *p_demux = (demux_t*)p_this;
    demux_sys_t *p_sys = p_demux->p_sys;
Gildas Bazin's avatar
 
Gildas Bazin committed
183

184
    TAB_CLEAN( p_sys->i_seekpoint, p_sys->seekpoint );
185 186 187 188

    int i;
    for( i = 0; i < p_sys->i_attachments; i++ )
        free( p_sys->attachments[i] );
189 190
    TAB_CLEAN( p_sys->i_attachments, p_sys->attachments);

Gildas Bazin's avatar
 
Gildas Bazin committed
191
    /* Delete the decoder */
192 193
    demux_PacketizerDestroy( p_sys->p_packetizer );

Laurent Aimar's avatar
Laurent Aimar committed
194 195
    if( p_sys->p_meta )
        vlc_meta_Delete( p_sys->p_meta );
Gildas Bazin's avatar
 
Gildas Bazin committed
196
    free( p_sys );
197 198 199 200 201 202 203
}

/*****************************************************************************
 * Demux: reads and demuxes data packets
 *****************************************************************************
 * Returns -1 in case of error, 0 in case of EOF, 1 otherwise
 *****************************************************************************/
204
static int Demux( demux_t *p_demux )
205
{
206 207
    demux_sys_t *p_sys = p_demux->p_sys;
    block_t     *p_block_in, *p_block_out;
208

209
    if( !( p_block_in = stream_Block( p_demux->s, FLAC_PACKET_SIZE ) ) )
Gildas Bazin's avatar
 
Gildas Bazin committed
210
        return 0;
211

212
    p_block_in->i_pts = p_block_in->i_dts = p_sys->b_start ? VLC_TS_0 : VLC_TS_INVALID;
213
    p_sys->b_start = false;
214

Gildas Bazin's avatar
 
Gildas Bazin committed
215 216
    while( (p_block_out = p_sys->p_packetizer->pf_packetize(
                p_sys->p_packetizer, &p_block_in )) )
217
    {
Gildas Bazin's avatar
 
Gildas Bazin committed
218 219 220
        while( p_block_out )
        {
            block_t *p_next = p_block_out->p_next;
221

Laurent Aimar's avatar
Laurent Aimar committed
222 223 224 225
            p_block_out->p_next = NULL;

            if( p_sys->p_es == NULL )
            {
226
                p_sys->p_packetizer->fmt_out.b_packetized = true;
227
                p_sys->p_packetizer->fmt_out.audio_replay_gain = p_sys->replay_gain;
Laurent Aimar's avatar
Laurent Aimar committed
228 229 230
                p_sys->p_es = es_out_Add( p_demux->out, &p_sys->p_packetizer->fmt_out);
            }

231
            p_sys->i_pts = p_block_out->i_dts - VLC_TS_0;
232 233 234 235 236

            /* Correct timestamp */
            p_block_out->i_pts += p_sys->i_time_offset;
            p_block_out->i_dts += p_sys->i_time_offset;

237
            /* set PCR */
238
            es_out_Control( p_demux->out, ES_OUT_SET_PCR, p_block_out->i_dts );
Gildas Bazin's avatar
 
Gildas Bazin committed
239

240
            es_out_Send( p_demux->out, p_sys->p_es, p_block_out );
241

Gildas Bazin's avatar
 
Gildas Bazin committed
242 243 244
            p_block_out = p_next;
        }
    }
245 246
    return 1;
}
247 248 249 250

/*****************************************************************************
 * Control:
 *****************************************************************************/
Laurent Aimar's avatar
Laurent Aimar committed
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
static int64_t ControlGetLength( demux_t *p_demux )
{
    demux_sys_t *p_sys = p_demux->p_sys;
    const int64_t i_size = stream_Size(p_demux->s) - p_sys->i_data_pos;
    int64_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-- )
    {
        seekpoint_t *s = p_sys->seekpoint[i];
        if( s->i_byte_offset <= i_size )
        {
            if( i+1 < p_sys->i_seekpoint )
            {
                /* Broken file */
                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;
}
276

Laurent Aimar's avatar
Laurent Aimar committed
277 278 279 280 281
static int64_t ControlGetTime( demux_t *p_demux )
{
    demux_sys_t *p_sys = p_demux->p_sys;
    return __MAX(p_sys->i_pts, p_sys->i_pts_start) + p_sys->i_time_offset;
}
282

Laurent Aimar's avatar
Laurent Aimar committed
283 284 285 286
static int ControlSetTime( demux_t *p_demux, int64_t i_time )
{
    demux_sys_t *p_sys = p_demux->p_sys;
    int64_t i_delta_time;
287
    bool b_seekable;
Laurent Aimar's avatar
Laurent Aimar committed
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
    int i;

    /* */
    stream_Control( p_demux->s, STREAM_CAN_SEEK, &b_seekable );
    if( !b_seekable )
        return VLC_EGENERIC;

    /* */
    assert( p_sys->i_seekpoint > 0 );   /* ReadMeta ensure at least (0,0) */
    for( i = p_sys->i_seekpoint-1; i >= 0; i-- )
    {
        if( p_sys->seekpoint[i]->i_time_offset <= i_time )
            break;
    }
    i_delta_time = i_time - p_sys->seekpoint[i]->i_time_offset;

    /* XXX We do exact seek if it's not too far away(45s) */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
305
    if( i_delta_time < 45*INT64_C(1000000) )
Laurent Aimar's avatar
Laurent Aimar committed
306 307 308
    {
        if( stream_Seek( p_demux->s, p_sys->seekpoint[i]->i_byte_offset+p_sys->i_data_pos ) )
            return VLC_EGENERIC;
309

Laurent Aimar's avatar
Laurent Aimar committed
310 311
        p_sys->i_time_offset = p_sys->seekpoint[i]->i_time_offset - p_sys->i_pts;
        p_sys->i_pts_start = p_sys->i_pts+i_delta_time;
312
        es_out_Control( p_demux->out, ES_OUT_SET_NEXT_DISPLAY_TIME, p_sys->i_pts_start + p_sys->i_time_offset );
Laurent Aimar's avatar
Laurent Aimar committed
313 314 315
    }
    else
    {
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
        int64_t i_delta_offset;
        int64_t i_next_time;
        int64_t i_next_offset;

        if( i+1 < p_sys->i_seekpoint )
        {
            i_next_time   = p_sys->seekpoint[i+1]->i_time_offset;
            i_next_offset = p_sys->seekpoint[i+1]->i_byte_offset;
        }
        else
        {
            i_next_time   = p_sys->i_length;
            i_next_offset = stream_Size(p_demux->s)-p_sys->i_data_pos;
        }

        i_delta_offset = 0;
        if( i_next_time-p_sys->seekpoint[i]->i_time_offset > 0 )
            i_delta_offset = (i_next_offset - p_sys->seekpoint[i]->i_byte_offset) * i_delta_time /
                             (i_next_time-p_sys->seekpoint[i]->i_time_offset);

Laurent Aimar's avatar
Laurent Aimar committed
336 337
        if( stream_Seek( p_demux->s, p_sys->seekpoint[i]->i_byte_offset+p_sys->i_data_pos + i_delta_offset ) )
            return VLC_EGENERIC;
338

Laurent Aimar's avatar
Laurent Aimar committed
339 340 341 342 343 344
        p_sys->i_pts_start = p_sys->i_pts;
        p_sys->i_time_offset = (p_sys->seekpoint[i]->i_time_offset+i_delta_time) - p_sys->i_pts;
    }
    return VLC_SUCCESS;
}

345 346
static int Control( demux_t *p_demux, int i_query, va_list args )
{
Laurent Aimar's avatar
Laurent Aimar committed
347 348 349
    demux_sys_t *p_sys = p_demux->p_sys;

    if( i_query == DEMUX_GET_META )
350
    {
351
        vlc_meta_t *p_meta = (vlc_meta_t *)va_arg( args, vlc_meta_t* );
352
        if( p_demux->p_sys->p_meta )
353
            vlc_meta_Merge( p_meta, p_demux->p_sys->p_meta );
354 355
        return VLC_SUCCESS;
    }
356 357
    else if( i_query == DEMUX_HAS_UNSUPPORTED_META )
    {
358 359
        bool *pb_bool = (bool*)va_arg( args, bool* );
        *pb_bool = true;
360 361
        return VLC_SUCCESS;
    }
Laurent Aimar's avatar
Laurent Aimar committed
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
    else if( i_query == DEMUX_GET_LENGTH )
    {
        int64_t *pi64 = (int64_t*)va_arg( args, int64_t * );
        *pi64 = ControlGetLength( p_demux );
        return VLC_SUCCESS;
    }
    else if( i_query == DEMUX_SET_TIME )
    {
        int64_t i_time = (int64_t)va_arg( args, int64_t );
        return ControlSetTime( p_demux, i_time );
    }
    else if( i_query == DEMUX_SET_POSITION )
    {
        const double f = (double)va_arg( args, double );
        int64_t i_time = f * ControlGetLength( p_demux );
        return ControlSetTime( p_demux, i_time );
    }
    else if( i_query == DEMUX_GET_TIME )
    {
        int64_t *pi64 = (int64_t*)va_arg( args, int64_t * );
        *pi64 = ControlGetTime( p_demux );
        return VLC_SUCCESS;
    }
    else if( i_query == DEMUX_GET_POSITION )
    {
        double *pf = (double*)va_arg( args, double * );
        const int64_t i_length = ControlGetLength(p_demux);
        if( i_length > 0 )
390 391 392 393
        {
            double current = ControlGetTime(p_demux);
            *pf = current / (double)i_length;
        }
Laurent Aimar's avatar
Laurent Aimar committed
394 395 396 397
        else
            *pf= 0.0;
        return VLC_SUCCESS;
    }
Laurent Aimar's avatar
Laurent Aimar committed
398 399 400 401 402 403 404
    else if( i_query == DEMUX_GET_ATTACHMENTS )
    {
        input_attachment_t ***ppp_attach =
            (input_attachment_t***)va_arg( args, input_attachment_t*** );
        int *pi_int = (int*)va_arg( args, int * );
        int i;

405
        if( p_sys->i_attachments <= 0 )
Laurent Aimar's avatar
Laurent Aimar committed
406 407
            return VLC_EGENERIC;

408
        *pi_int = p_sys->i_attachments;;
409
        *ppp_attach = xmalloc( sizeof(input_attachment_t**) * p_sys->i_attachments );
410 411
        for( i = 0; i < p_sys->i_attachments; i++ )
            (*ppp_attach)[i] = vlc_input_attachment_Duplicate( p_sys->attachments[i] );
Laurent Aimar's avatar
Laurent Aimar committed
412 413
        return VLC_SUCCESS;
    }
Laurent Aimar's avatar
Laurent Aimar committed
414

415
    return demux_vaControlHelper( p_demux->s, p_sys->i_data_pos, -1,
Laurent Aimar's avatar
Laurent Aimar committed
416 417 418 419 420 421 422 423
                                   8*0, 1, i_query, args );
}

enum
{
    META_STREAMINFO = 0,
    META_SEEKTABLE = 3,
    META_COMMENT = 4,
Laurent Aimar's avatar
Laurent Aimar committed
424
    META_PICTURE = 6,
Laurent Aimar's avatar
Laurent Aimar committed
425 426
};

427
static inline int Get24bBE( const uint8_t *p )
Laurent Aimar's avatar
Laurent Aimar committed
428 429 430 431
{
    return (p[0] << 16)|(p[1] << 8)|(p[2]);
}

432
static void ParseStreamInfo( int *pi_rate, int64_t *pi_count, uint8_t *p_data );
433 434 435 436
static void ParseSeekTable( demux_t *p_demux, const uint8_t *p_data, int i_data,
                            int i_sample_rate );
static void ParseComment( demux_t *, const uint8_t *p_data, int i_data );
static void ParsePicture( demux_t *, const uint8_t *p_data, int i_data );
Laurent Aimar's avatar
Laurent Aimar committed
437 438 439 440 441

static int  ReadMeta( demux_t *p_demux, uint8_t **pp_streaminfo, int *pi_streaminfo )
{
    demux_sys_t *p_sys = p_demux->p_sys;
    int     i_peek;
442
    const uint8_t *p_peek;
443
    bool b_last;
Laurent Aimar's avatar
Laurent Aimar committed
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
    int i_sample_rate;
    int64_t i_sample_count;
    seekpoint_t *s;

    /* Read STREAMINFO */
    i_peek = stream_Peek( p_demux->s, &p_peek, 8 );
    if( (p_peek[4] & 0x7F) != META_STREAMINFO )
    {
        msg_Err( p_demux, "this isn't a STREAMINFO metadata block" );
        return VLC_EGENERIC;
    }
    if( Get24bBE(&p_peek[5]) != (STREAMINFO_SIZE - 4) )
    {
        msg_Err( p_demux, "invalid size for a STREAMINFO metadata block" );
        return VLC_EGENERIC;
    }

    *pi_streaminfo = 4 + STREAMINFO_SIZE;
    *pp_streaminfo = malloc( 4 + STREAMINFO_SIZE );
    if( *pp_streaminfo == NULL )
        return VLC_EGENERIC;

    if( stream_Read( p_demux->s, *pp_streaminfo, 4+STREAMINFO_SIZE ) != 4+STREAMINFO_SIZE )
    {
        msg_Err( p_demux, "failed to read STREAMINFO metadata block" );
        free( *pp_streaminfo );
        return VLC_EGENERIC;
    }

    /* */
474
    ParseStreamInfo( &i_sample_rate, &i_sample_count, *pp_streaminfo );
Laurent Aimar's avatar
Laurent Aimar committed
475
    if( i_sample_rate > 0 )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
476
        p_sys->i_length = i_sample_count * INT64_C(1000000)/i_sample_rate;
Laurent Aimar's avatar
Laurent Aimar committed
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508

    /* Be sure we have seekpoint 0 */
    s = vlc_seekpoint_New();
    s->i_time_offset = 0;
    s->i_byte_offset = 0;
    TAB_APPEND( p_sys->i_seekpoint, p_sys->seekpoint, s );

    b_last = (*pp_streaminfo)[4]&0x80;
    while( !b_last )
    {
        int i_len;
        int i_type;

        i_peek = 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_SEEKTABLE )
        {
            i_peek = stream_Peek( p_demux->s, &p_peek, 4+i_len );
            if( i_peek == 4+i_len )
                ParseSeekTable( p_demux, p_peek, i_peek, i_sample_rate );
        }
        else if( i_type == META_COMMENT )
        {
            i_peek = stream_Peek( p_demux->s, &p_peek, 4+i_len );
            if( i_peek == 4+i_len )
                ParseComment( p_demux, p_peek, i_peek );
        }
Laurent Aimar's avatar
Laurent Aimar committed
509 510 511 512 513 514
        else if( i_type == META_PICTURE )
        {
            i_peek = stream_Peek( p_demux->s, &p_peek, 4+i_len );
            if( i_peek == 4+i_len )
                ParsePicture( p_demux, p_peek, i_peek );
        }
Laurent Aimar's avatar
Laurent Aimar committed
515 516 517 518 519 520 521 522 523 524

        if( stream_Read( p_demux->s, NULL, 4+i_len ) < 4+i_len )
            break;
    }

    /* */
    p_sys->i_data_pos = stream_Tell( p_demux->s );

    return VLC_SUCCESS;
}
525
static void ParseStreamInfo( int *pi_rate, int64_t *pi_count, uint8_t *p_data )
Laurent Aimar's avatar
Laurent Aimar committed
526 527 528 529
{
    const int i_skip = 4+4;

    *pi_rate = GetDWBE(&p_data[i_skip+4+6]) >> 12;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
530
    *pi_count = GetQWBE(&p_data[i_skip+4+6]) &  ((INT64_C(1)<<36)-1);
Laurent Aimar's avatar
Laurent Aimar committed
531
}
532 533 534

static void ParseSeekTable( demux_t *p_demux, const uint8_t *p_data, int i_data,
                            int i_sample_rate )
Laurent Aimar's avatar
Laurent Aimar committed
535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
{
    demux_sys_t *p_sys = p_demux->p_sys;
    seekpoint_t *s;
    int 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 )
            continue;

        s = vlc_seekpoint_New();
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
553
        s->i_time_offset = i_sample * INT64_C(1000000)/i_sample_rate;
Laurent Aimar's avatar
Laurent Aimar committed
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
        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 )
            {
                vlc_seekpoint_Delete( 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) */
}
574

Laurent Aimar's avatar
Laurent Aimar committed
575
#define RM(x) do { i_data -= (x); p_data += (x); } while(0)
576
static void ParseComment( demux_t *p_demux, const uint8_t *p_data, int i_data )
Laurent Aimar's avatar
Laurent Aimar committed
577 578 579 580 581 582
{
    demux_sys_t *p_sys = p_demux->p_sys;

    if( i_data < 4 )
        return;

583
    vorbis_ParseComment( &p_sys->p_meta, &p_data[4], i_data - 4 );
Laurent Aimar's avatar
Laurent Aimar committed
584

585 586
}

587
static void ParsePicture( demux_t *p_demux, const uint8_t *p_data, int i_data )
Laurent Aimar's avatar
Laurent Aimar committed
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615
{
    static const int pi_cover_score[] = {
        0,      /* other */
        2, 1,   /* icons */
        10,     /* front cover */
        9,      /* back cover */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        6,      /* movie/video screen capture */
        0,
        7,      /* Illustration */
        8,      /* Band/Artist logotype */
        0,      /* Publisher/Studio */
    };
    demux_sys_t *p_sys = p_demux->p_sys;
    int i_type;
    int i_len;
    char *psz_mime = NULL;
    char *psz_description = NULL;
    input_attachment_t *p_attachment;
    char psz_name[128];

    if( i_data < 4 + 3*4 )
        return;
#define RM(x) do { i_data -= (x); p_data += (x); } while(0)
    RM(4);

    i_type = GetDWBE( p_data ); RM(4);
    i_len = GetDWBE( p_data ); RM(4);
616
    if( i_len < 0 || i_data < i_len + 4 )
Laurent Aimar's avatar
Laurent Aimar committed
617
        goto error;
618
    psz_mime = strndup( (const char*)p_data, i_len ); RM(i_len);
Laurent Aimar's avatar
Laurent Aimar committed
619
    i_len = GetDWBE( p_data ); RM(4);
620
    if( i_len < 0 || i_data < i_len + 4*4 + 4)
Laurent Aimar's avatar
Laurent Aimar committed
621
        goto error;
622
    psz_description = strndup( (const char*)p_data, i_len ); RM(i_len);
Laurent Aimar's avatar
Laurent Aimar committed
623 624 625
    EnsureUTF8( psz_description );
    RM(4*4);
    i_len = GetDWBE( p_data ); RM(4);
626
    if( i_len < 0 || i_len > i_data )
Laurent Aimar's avatar
Laurent Aimar committed
627 628
        goto error;

629
    msg_Dbg( p_demux, "Picture type=%d mime=%s description='%s' file length=%d",
Laurent Aimar's avatar
Laurent Aimar committed
630 631
             i_type, psz_mime, psz_description, i_len );

632
    snprintf( psz_name, sizeof(psz_name), "picture%d", p_sys->i_attachments );
Laurent Aimar's avatar
Laurent Aimar committed
633 634 635 636 637 638 639
    if( !strcasecmp( psz_mime, "image/jpeg" ) )
        strcat( psz_name, ".jpg" );
    else if( !strcasecmp( psz_mime, "image/png" ) )
        strcat( psz_name, ".png" );

    p_attachment = vlc_input_attachment_New( psz_name, psz_mime, psz_description,
                                             p_data, i_data );
640
    TAB_APPEND( p_sys->i_attachments, p_sys->attachments, p_attachment );
Laurent Aimar's avatar
Laurent Aimar committed
641

642
    if( i_type >= 0 && (unsigned int)i_type < sizeof(pi_cover_score)/sizeof(pi_cover_score[0]) &&
Laurent Aimar's avatar
Laurent Aimar committed
643 644
        p_sys->i_cover_score < pi_cover_score[i_type] )
    {
645
        p_sys->i_cover_idx = p_sys->i_attachments-1;
Laurent Aimar's avatar
Laurent Aimar committed
646 647 648
        p_sys->i_cover_score = pi_cover_score[i_type];
    }
error:
649 650
    free( psz_mime );
    free( psz_description );
Laurent Aimar's avatar
Laurent Aimar committed
651 652 653
}
#undef RM