Changeset 6bd540727c8cc58e0f9fbec615c606e381eaabaa

Show
Ignore:
Timestamp:
30/05/07 21:30:07 (2 years ago)
Author:
Laurent Aimar <fenrir@videolan.org>
git-committer:
Laurent Aimar <fenrir@videolan.org> 1180553407 +0000
git-parent:

[b2ef4197fb788dcbc660f904d45782f52fad0ed4]

git-author:
Laurent Aimar <fenrir@videolan.org> 1180553407 +0000
Message:

Implemented (close #1194):

  • time/duration display
  • meta info parsing.
  • seek (precise seek if SEEKTABLE presents)

Resampling can still happen but I think it has to do with the
decoder(at least with our ffmpeg wrapper, I haven't tested the
native one).

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • modules/demux/flac.c

    rd3fe7f2 r6bd5407  
    66 * 
    77 * Authors: Gildas Bazin <gbazin@netcourrier.com> 
     8 *          Laurent Aimar <fenrir@via.ecp.fr> 
    89 * 
    910 * This program is free software; you can redistribute it and/or modify 
     
    2829#include <vlc_demux.h> 
    2930#include <vlc_meta.h> 
     31#include <vlc_input.h> 
    3032#include <vlc_codec.h> 
     33#include <assert.h> 
    3134 
    3235/***************************************************************************** 
     
    5154static int Control( demux_t *, int, va_list ); 
    5255 
     56static int  ReadMeta( demux_t *, uint8_t **pp_streaminfo, int *pi_streaminfo ); 
     57 
    5358struct demux_sys_t 
    5459{ 
     
    5964    decoder_t *p_packetizer; 
    6065    vlc_meta_t *p_meta; 
     66 
     67    int64_t i_time_offset; 
     68    int64_t i_pts; 
     69    int64_t i_pts_start; 
     70 
     71    int64_t i_length; /* Length from stream info */ 
     72    int64_t i_data_pos; 
     73 
     74    /* */ 
     75    int         i_seekpoint; 
     76    seekpoint_t **seekpoint; 
    6177}; 
    6278 
     
    7288    module_t    *p_id3; 
    7389    demux_sys_t *p_sys; 
    74     int          i_peek; 
    7590    byte_t      *p_peek; 
    76     es_format_t  fmt; 
     91    uint8_t     *p_streaminfo; 
     92    int         i_streaminfo; 
    7793 
    7894    /* Have a peep at the show. */ 
     
    91107    p_demux->pf_control = Control; 
    92108    p_demux->p_sys      = p_sys = malloc( sizeof( demux_sys_t ) ); 
    93     es_format_Init( &fmt, AUDIO_ES, VLC_FOURCC( 'f', 'l', 'a', 'c' ) ); 
    94109    p_sys->b_start = VLC_TRUE; 
    95110    p_sys->p_meta = NULL; 
     111    p_sys->i_length = 0; 
     112    p_sys->i_time_offset = 0; 
     113    p_sys->i_pts = 0; 
     114    p_sys->i_pts_start = 0; 
     115    p_sys->p_es = NULL; 
     116    TAB_INIT( p_sys->i_seekpoint, p_sys->seekpoint ); 
    96117 
    97118    /* We need to read and store the STREAMINFO metadata */ 
    98     i_peek = stream_Peek( p_demux->s, &p_peek, 8 ); 
    99     if( p_peek[4] & 0x7F ) 
    100     { 
    101         msg_Err( p_demux, "this isn't a STREAMINFO metadata block" ); 
    102         return VLC_EGENERIC; 
    103     } 
    104  
    105     if( ((p_peek[5]<<16)+(p_peek[6]<<8)+p_peek[7]) != (STREAMINFO_SIZE - 4) ) 
    106     { 
    107         msg_Err( p_demux, "invalid size for a STREAMINFO metadata block" ); 
     119    if( ReadMeta( p_demux, &p_streaminfo, &i_streaminfo ) ) 
     120    { 
     121        free( p_sys ); 
    108122        return VLC_EGENERIC; 
    109123    } 
     
    113127 
    114128    /* Store STREAMINFO for the decoder and packetizer */ 
    115     p_sys->p_packetizer->fmt_in.i_extra = fmt.i_extra = STREAMINFO_SIZE + 4; 
    116     p_sys->p_packetizer->fmt_in.p_extra = malloc( STREAMINFO_SIZE + 4 ); 
    117     stream_Read( p_demux->s, p_sys->p_packetizer->fmt_in.p_extra, 
    118                  STREAMINFO_SIZE + 4 ); 
    119  
    120     /* Fake this as the last metadata block */ 
    121     ((uint8_t*)p_sys->p_packetizer->fmt_in.p_extra)[4] |= 0x80; 
    122     fmt.p_extra = malloc( STREAMINFO_SIZE + 4 ); 
    123     memcpy( fmt.p_extra, p_sys->p_packetizer->fmt_in.p_extra, 
    124             STREAMINFO_SIZE + 4 ); 
     129    p_streaminfo[4] |= 0x80; /* Fake this as the last metadata block */ 
     130    p_sys->p_packetizer->fmt_in.i_extra = i_streaminfo; 
     131    p_sys->p_packetizer->fmt_in.p_extra = p_streaminfo; 
    125132 
    126133    p_sys->p_packetizer->p_module = 
     
    136143    } 
    137144 
    138     p_sys->p_es = es_out_Add( p_demux->out, &fmt ); 
    139  
    140145    /* Parse possible id3 header */ 
    141146    if( ( p_id3 = module_Need( p_demux, "meta reader", NULL, 0 ) ) ) 
    142147    { 
    143         p_sys->p_meta = (vlc_meta_t *)p_demux->p_private; 
     148        vlc_meta_t *p_meta = (vlc_meta_t *)p_demux->p_private; 
     149 
     150        if( !p_sys->p_meta ) 
     151        { 
     152            p_sys->p_meta = p_meta; 
     153        } 
     154        else if( p_meta ) 
     155        { 
     156            vlc_meta_Merge( p_sys->p_meta, p_meta ); 
     157            vlc_meta_Delete( p_meta ); 
     158        } 
    144159        p_demux->p_private = NULL; 
    145160        module_Unneed( p_demux, p_id3 ); 
     
    165180    /* Delete the decoder */ 
    166181    vlc_object_destroy( p_sys->p_packetizer ); 
    167     if( p_sys->p_meta ) vlc_meta_Delete( p_sys->p_meta ); 
     182    if( p_sys->p_meta ) 
     183        vlc_meta_Delete( p_sys->p_meta ); 
    168184    free( p_sys ); 
    169185} 
     
    180196 
    181197    if( !( p_block_in = stream_Block( p_demux->s, FLAC_PACKET_SIZE ) ) ) 
    182     { 
    183198        return 0; 
    184     } 
    185  
    186     if( p_sys->b_start ) 
    187     { 
    188         p_block_in->i_pts = p_block_in->i_dts = 1; 
    189         p_sys->b_start = VLC_FALSE; 
    190     } 
    191     else 
    192     { 
    193         p_block_in->i_pts = p_block_in->i_dts = 0; 
    194     } 
     199 
     200    p_block_in->i_pts = p_block_in->i_dts = p_sys->b_start ? 1 : 0; 
     201    p_sys->b_start = VLC_FALSE; 
    195202 
    196203    while( (p_block_out = p_sys->p_packetizer->pf_packetize( 
     
    201208            block_t *p_next = p_block_out->p_next; 
    202209 
     210            p_block_out->p_next = NULL; 
     211 
     212            if( p_sys->p_es == NULL ) 
     213            { 
     214                p_sys->p_packetizer->fmt_out.b_packetized = VLC_TRUE; 
     215                p_sys->p_es = es_out_Add( p_demux->out, &p_sys->p_packetizer->fmt_out); 
     216            } 
     217 
    203218            /* set PCR */ 
    204             es_out_Control( p_demux->out, ES_OUT_SET_PCR, p_block_out->i_dts ); 
    205  
     219            if( p_block_out->i_dts >= p_sys->i_pts_start ) 
     220                es_out_Control( p_demux->out, ES_OUT_SET_PCR, p_block_out->i_dts ); 
     221            else 
     222                es_out_Control( p_demux->out, ES_OUT_RESET_PCR ); 
     223 
     224            p_sys->i_pts = p_block_out->i_dts; 
    206225            es_out_Send( p_demux->out, p_sys->p_es, p_block_out ); 
    207226 
     
    216235 * Control: 
    217236 *****************************************************************************/ 
     237static int64_t ControlGetLength( demux_t *p_demux ) 
     238{ 
     239    demux_sys_t *p_sys = p_demux->p_sys; 
     240    const int64_t i_size = stream_Size(p_demux->s) - p_sys->i_data_pos; 
     241    int64_t i_length = p_sys->i_length; 
     242    int i; 
     243 
     244    /* Try to fix length using seekpoint and current size for truncated file */ 
     245    for( i = p_sys->i_seekpoint-1; i >= 0; i-- ) 
     246    { 
     247        seekpoint_t *s = p_sys->seekpoint[i]; 
     248        if( s->i_byte_offset <= i_size ) 
     249        { 
     250            if( i+1 < p_sys->i_seekpoint ) 
     251            { 
     252                /* Broken file */ 
     253                seekpoint_t *n = p_sys->seekpoint[i+1]; 
     254                assert( n->i_byte_offset != s->i_byte_offset); /* Should be ensured by ParseSeekTable */ 
     255                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); 
     256            } 
     257            break; 
     258        } 
     259    } 
     260    return i_length; 
     261} 
     262static int64_t ControlGetTime( demux_t *p_demux ) 
     263{ 
     264    demux_sys_t *p_sys = p_demux->p_sys; 
     265    return __MAX(p_sys->i_pts, p_sys->i_pts_start) + p_sys->i_time_offset; 
     266} 
     267static int ControlSetTime( demux_t *p_demux, int64_t i_time ) 
     268{ 
     269    demux_sys_t *p_sys = p_demux->p_sys; 
     270    int64_t i_next_time; 
     271    int64_t i_next_offset; 
     272    int64_t i_delta_time; 
     273    int64_t i_delta_offset; 
     274    vlc_bool_t b_seekable; 
     275    int i; 
     276 
     277    /* */ 
     278    stream_Control( p_demux->s, STREAM_CAN_SEEK, &b_seekable ); 
     279    if( !b_seekable ) 
     280        return VLC_EGENERIC; 
     281 
     282    /* */ 
     283    assert( p_sys->i_seekpoint > 0 );   /* ReadMeta ensure at least (0,0) */ 
     284    for( i = p_sys->i_seekpoint-1; i >= 0; i-- ) 
     285    { 
     286        if( p_sys->seekpoint[i]->i_time_offset <= i_time ) 
     287            break; 
     288    } 
     289    if( i+1 < p_sys->i_seekpoint ) 
     290    { 
     291        i_next_time   = p_sys->seekpoint[i+1]->i_time_offset; 
     292        i_next_offset = p_sys->seekpoint[i+1]->i_byte_offset; 
     293    } 
     294    else 
     295    { 
     296        i_next_time   = p_sys->i_length; 
     297        i_next_offset = stream_Size(p_demux->s)-p_sys->i_data_pos; 
     298    } 
     299    i_delta_time = i_time - p_sys->seekpoint[i]->i_time_offset; 
     300    i_delta_offset = (i_next_offset - p_sys->seekpoint[i]->i_byte_offset) * i_delta_time /  
     301                            (p_sys->seekpoint[i+1]->i_time_offset-p_sys->seekpoint[i]->i_time_offset); 
     302 
     303    /* XXX We do exact seek if it's not too far away(45s) */ 
     304    if( i_delta_time < 45*I64C(1000000) ) 
     305    { 
     306        if( stream_Seek( p_demux->s, p_sys->seekpoint[i]->i_byte_offset+p_sys->i_data_pos ) ) 
     307            return VLC_EGENERIC; 
     308        p_sys->i_time_offset = p_sys->seekpoint[i]->i_time_offset - p_sys->i_pts; 
     309        p_sys->i_pts_start = p_sys->i_pts+i_delta_time; 
     310        es_out_Control( p_demux->out, ES_OUT_SET_NEXT_DISPLAY_TIME, p_sys->p_es, p_sys->i_pts_start ); 
     311    } 
     312    else 
     313    { 
     314        if( stream_Seek( p_demux->s, p_sys->seekpoint[i]->i_byte_offset+p_sys->i_data_pos + i_delta_offset ) ) 
     315            return VLC_EGENERIC; 
     316        p_sys->i_pts_start = p_sys->i_pts; 
     317        p_sys->i_time_offset = (p_sys->seekpoint[i]->i_time_offset+i_delta_time) - p_sys->i_pts; 
     318    } 
     319    return VLC_SUCCESS; 
     320} 
     321 
    218322static int Control( demux_t *p_demux, int i_query, va_list args ) 
    219323{ 
    220     /* demux_sys_t *p_sys  = p_demux->p_sys; */ 
    221     /* FIXME bitrate */ 
    222     if( i_query == DEMUX_SET_TIME ) return VLC_EGENERIC; 
    223     else if( i_query == DEMUX_GET_META ) 
     324    demux_sys_t *p_sys = p_demux->p_sys; 
     325 
     326    if( i_query == DEMUX_GET_META ) 
    224327    { 
    225328        vlc_meta_t *p_meta = (vlc_meta_t *)va_arg( args, vlc_meta_t* ); 
     
    228331        return VLC_SUCCESS; 
    229332    } 
    230     else return demux2_vaControlHelper( p_demux->s, 0, -1, 
    231                                         8*0, 1, i_query, args ); 
    232 
    233  
     333    else if( i_query == DEMUX_GET_LENGTH ) 
     334    { 
     335        int64_t *pi64 = (int64_t*)va_arg( args, int64_t * ); 
     336        *pi64 = ControlGetLength( p_demux ); 
     337        return VLC_SUCCESS; 
     338    } 
     339    else if( i_query == DEMUX_SET_TIME ) 
     340    { 
     341        int64_t i_time = (int64_t)va_arg( args, int64_t ); 
     342        return ControlSetTime( p_demux, i_time ); 
     343    } 
     344    else if( i_query == DEMUX_SET_POSITION ) 
     345    { 
     346        const double f = (double)va_arg( args, double ); 
     347        int64_t i_time = f * ControlGetLength( p_demux ); 
     348        return ControlSetTime( p_demux, i_time ); 
     349    } 
     350    else if( i_query == DEMUX_GET_TIME ) 
     351    { 
     352        int64_t *pi64 = (int64_t*)va_arg( args, int64_t * ); 
     353        *pi64 = ControlGetTime( p_demux ); 
     354        return VLC_SUCCESS; 
     355    } 
     356    else if( i_query == DEMUX_GET_POSITION ) 
     357    { 
     358        double *pf = (double*)va_arg( args, double * ); 
     359        const int64_t i_length = ControlGetLength(p_demux); 
     360        if( i_length > 0 ) 
     361            *pf = (double)ControlGetTime(p_demux) / (double)i_length; 
     362        else 
     363            *pf= 0.0; 
     364        return VLC_SUCCESS; 
     365    } 
     366 
     367    return demux2_vaControlHelper( p_demux->s, p_sys->i_data_pos, -1, 
     368                                   8*0, 1, i_query, args ); 
     369
     370 
     371enum 
     372
     373    META_STREAMINFO = 0, 
     374    META_SEEKTABLE = 3, 
     375    META_COMMENT = 4, 
     376}; 
     377 
     378static inline int Get24bBE( uint8_t *p ) 
     379
     380    return (p[0] << 16)|(p[1] << 8)|(p[2]); 
     381
     382 
     383static void ParseStreamInfo( demux_t *p_demux, int *pi_rate, int64_t *pi_count, uint8_t *p_data, int i_data ); 
     384static void ParseSeekTable( demux_t *p_demux, uint8_t *p_data, int i_data, int i_sample_rate ); 
     385static void ParseComment( demux_t *, uint8_t *p_data, int i_data ); 
     386 
     387static int  ReadMeta( demux_t *p_demux, uint8_t **pp_streaminfo, int *pi_streaminfo ) 
     388
     389    demux_sys_t *p_sys = p_demux->p_sys; 
     390    int     i_peek; 
     391    uint8_t *p_peek; 
     392    vlc_bool_t b_last; 
     393    int i_sample_rate; 
     394    int64_t i_sample_count; 
     395    seekpoint_t *s; 
     396 
     397    /* Read STREAMINFO */ 
     398    i_peek = stream_Peek( p_demux->s, &p_peek, 8 ); 
     399    if( (p_peek[4] & 0x7F) != META_STREAMINFO ) 
     400    { 
     401        msg_Err( p_demux, "this isn't a STREAMINFO metadata block" ); 
     402        return VLC_EGENERIC; 
     403    } 
     404    if( Get24bBE(&p_peek[5]) != (STREAMINFO_SIZE - 4) ) 
     405    { 
     406        msg_Err( p_demux, "invalid size for a STREAMINFO metadata block" ); 
     407        return VLC_EGENERIC; 
     408    } 
     409 
     410    *pi_streaminfo = 4 + STREAMINFO_SIZE; 
     411    *pp_streaminfo = malloc( 4 + STREAMINFO_SIZE ); 
     412    if( *pp_streaminfo == NULL ) 
     413        return VLC_EGENERIC; 
     414 
     415    if( stream_Read( p_demux->s, *pp_streaminfo, 4+STREAMINFO_SIZE ) != 4+STREAMINFO_SIZE ) 
     416    { 
     417        msg_Err( p_demux, "failed to read STREAMINFO metadata block" ); 
     418        free( *pp_streaminfo ); 
     419        return VLC_EGENERIC; 
     420    } 
     421 
     422    /* */ 
     423    ParseStreamInfo( p_demux, &i_sample_rate, &i_sample_count,  *pp_streaminfo, *pi_streaminfo ); 
     424    if( i_sample_rate > 0 ) 
     425        p_sys->i_length = i_sample_count * I64C(1000000)/i_sample_rate; 
     426 
     427    /* Be sure we have seekpoint 0 */ 
     428    s = vlc_seekpoint_New(); 
     429    s->i_time_offset = 0; 
     430    s->i_byte_offset = 0; 
     431    TAB_APPEND( p_sys->i_seekpoint, p_sys->seekpoint, s ); 
     432 
     433 
     434 
     435    b_last = (*pp_streaminfo)[4]&0x80; 
     436    while( !b_last ) 
     437    { 
     438        int i_len; 
     439        int i_type; 
     440 
     441        i_peek = stream_Peek( p_demux->s, &p_peek, 4 ); 
     442        if( i_peek < 4 ) 
     443            break; 
     444        b_last = p_peek[0]&0x80; 
     445        i_type = p_peek[0]&0x7f; 
     446        i_len  = Get24bBE( &p_peek[1] ); 
     447 
     448        if( i_type == META_SEEKTABLE ) 
     449        { 
     450            i_peek = stream_Peek( p_demux->s, &p_peek, 4+i_len ); 
     451            if( i_peek == 4+i_len ) 
     452                ParseSeekTable( p_demux, p_peek, i_peek, i_sample_rate ); 
     453        } 
     454        else if( i_type == META_COMMENT ) 
     455        { 
     456            i_peek = stream_Peek( p_demux->s, &p_peek, 4+i_len ); 
     457            if( i_peek == 4+i_len ) 
     458                ParseComment( p_demux, p_peek, i_peek ); 
     459        } 
     460 
     461        if( stream_Read( p_demux->s, NULL, 4+i_len ) < 4+i_len ) 
     462            break; 
     463    } 
     464 
     465    /* */ 
     466    p_sys->i_data_pos = stream_Tell( p_demux->s ); 
     467 
     468    return VLC_SUCCESS; 
     469
     470static void ParseStreamInfo( demux_t *p_demux, int *pi_rate, int64_t *pi_count, uint8_t *p_data, int i_data ) 
     471
     472    const int i_skip = 4+4; 
     473 
     474    *pi_rate = GetDWBE(&p_data[i_skip+4+6]) >> 12; 
     475    *pi_count = GetQWBE(&p_data[i_skip+4+6]) &  ((I64C(1)<<36)-1); 
     476
     477static void ParseSeekTable( demux_t *p_demux, uint8_t *p_data, int i_data, int i_sample_rate ) 
     478
     479    demux_sys_t *p_sys = p_demux->p_sys; 
     480    seekpoint_t *s; 
     481    int i; 
     482 
     483    if( i_sample_rate <= 0 ) 
     484        return; 
     485 
     486    /* */ 
     487    for( i = 0; i < (i_data-4)/18; i++ ) 
     488    { 
     489        const int64_t i_sample = GetQWBE( &p_data[4+18*i+0] ); 
     490        int j; 
     491 
     492        if( i_sample < 0 || i_sample >= INT64_MAX ) 
     493            continue; 
     494 
     495        s = vlc_seekpoint_New(); 
     496        s->i_time_offset = i_sample * I64C(1000000)/i_sample_rate; 
     497        s->i_byte_offset = GetQWBE( &p_data[4+18*i+8] ); 
     498 
     499        /* Check for duplicate entry */ 
     500        for( j = 0; j < p_sys->i_seekpoint; j++ ) 
     501        { 
     502            if( p_sys->seekpoint[j]->i_time_offset == s->i_time_offset || 
     503                p_sys->seekpoint[j]->i_byte_offset == s->i_byte_offset ) 
     504            { 
     505                vlc_seekpoint_Delete( s ); 
     506                s = NULL; 
     507                break; 
     508            } 
     509        } 
     510        if( s ) 
     511        { 
     512            TAB_APPEND( p_sys->i_seekpoint, p_sys->seekpoint, s ); 
     513        } 
     514    } 
     515    /* TODO sort it by size and remove wrong seek entry (time not increasing) */ 
     516
     517static inline void astrcat( char **ppsz_dst, const char *psz_src ) 
     518
     519    char *psz_old = *ppsz_dst; 
     520 
     521    if( !psz_src || !psz_src[0] ) 
     522        return; 
     523 
     524    if( psz_old ) 
     525    { 
     526        asprintf( ppsz_dst, "%s,%s", psz_old, psz_src ); 
     527        free( psz_old ); 
     528    } 
     529    else 
     530    { 
     531        *ppsz_dst = strdup( psz_src ); 
     532    } 
     533
     534 
     535static void ParseComment( demux_t *p_demux, uint8_t *p_data, int i_data ) 
     536
     537    demux_sys_t *p_sys = p_demux->p_sys; 
     538    int n; 
     539    int i_comment; 
     540 
     541    if( i_data < 8 ) 
     542        return; 
     543 
     544#define RM(x) do { i_data -= (x); p_data += (x); } while(0) 
     545    RM(4); 
     546 
     547    n = GetDWLE(p_data); RM(4); 
     548    if( n < 0 || n > i_data ) 
     549        return; 
     550#if 0 
     551    if( n > 0 ) 
     552    { 
     553        /* TODO report vendor string ? */ 
     554        char *psz_vendor = psz_vendor = strndup( p_data, n ); 
     555        msg_Dbg( p_demux, "FLAC: COMMENT vendor length=%d vendor=%s\n", n, psz_vendor ); 
     556        free( psz_vendor ); 
     557    } 
     558#endif 
     559    RM(n); 
     560 
     561    if( i_data < 4 ) 
     562        return; 
     563 
     564    i_comment = GetDWLE(p_data); RM(4); 
     565    if( i_comment <= 0 ) 
     566        return; 
     567 
     568    p_sys->p_meta = vlc_meta_New(); 
     569 
     570    for( ; i_comment > 0; i_comment-- ) 
     571    { 
     572        char *psz; 
     573        if( i_data < 4 ) 
     574            break; 
     575        n = GetDWLE(p_data); RM(4); 
     576        if( n > i_data ) 
     577            break; 
     578        if( n <= 0 ) 
     579            continue; 
     580 
     581        psz = strndup( p_data, n ); 
     582        RM(n); 
     583 
     584        EnsureUTF8( psz ); 
     585 
     586#define IF_EXTRACT(txt,var) if( !strncasecmp(psz, txt, strlen(txt)) ) { astrcat( &p_sys->p_meta->var, &psz[strlen(txt)] ); } 
     587        IF_EXTRACT("TITLE=", psz_title ) 
     588        else IF_EXTRACT("ALBUM=", psz_album ) 
     589        else IF_EXTRACT("TRACKNUMBER=", psz_tracknum ) 
     590        else IF_EXTRACT("ARTIST=", psz_artist ) 
     591        else IF_EXTRACT("COPYRIGHT=", psz_copyright ) 
     592        else IF_EXTRACT("DESCRIPTION=", psz_description ) 
     593        else IF_EXTRACT("GENRE=", psz_genre ) 
     594        else IF_EXTRACT("DATE=", psz_date ) 
     595        else if( strchr( psz, '=' ) ) 
     596        { 
     597            /* generic (PERFORMER/LICENSE/ORGANIZATION/LOCATION/CONTACT/ISRC and undocumented tags) */ 
     598            char *p = strchr( psz, '=' ); 
     599            *p++ = '\0'; 
     600            vlc_meta_AddExtra( p_sys->p_meta, psz, p ); 
     601        } 
     602#undef IF_EXTRACT 
     603        free( psz ); 
     604    } 
     605#undef RM 
     606
     607