JNIEXPORT jint JNICALL Java_com_ssb_droidsound_utils_ID3Tag_checkForTag(JNIEnv *env, jobject obj, jbyteArray bArray, jint offset, jint size) { jbyte *ptr = env->GetByteArrayElements(bArray, NULL); int len = id3_tag_query((const id3_byte_t*)(ptr + offset), size); env->ReleaseByteArrayElements(bArray, ptr, 0); return len; }
/* * NAME: query_tag() * DESCRIPTION: check for a tag at a file's current position */ static signed long query_tag(FILE *iofile) { #ifdef __APPLE__ fpos_t save_position = 0; #else fpos_t save_position = {0}; #endif id3_byte_t query[ID3_TAG_QUERYSIZE+2]; signed long size = 0; memset(query,0, (ID3_TAG_QUERYSIZE+2) * sizeof(id3_byte_t) ); if (fgetpos(iofile, &save_position) == -1) { return 0; } size = id3_tag_query(query, fread(query, 1, ID3_TAG_QUERYSIZE * sizeof(id3_byte_t) , iofile)); if (fsetpos(iofile, &save_position) == -1) { return 0; } return size; }
static void CheckHeader( demux_meta_t *p_demux_meta ) { const uint8_t *p_peek; int i_size; demux_t *p_demux = (demux_t *)p_demux_meta->p_demux; if( stream_Seek( p_demux->s, 0 ) ) return; /* Test ID3v2 first */ if( stream_Peek( p_demux->s, &p_peek, ID3_TAG_QUERYSIZE ) != ID3_TAG_QUERYSIZE ) return; i_size = id3_tag_query( p_peek, ID3_TAG_QUERYSIZE ); if( i_size > 0 && stream_Peek( p_demux->s, &p_peek, i_size ) == i_size ) { msg_Dbg( p_demux, "found ID3v2 tag" ); ParseID3Tag( p_demux_meta, p_peek, i_size ); return; } /* Test APEv1 */ if( stream_Peek( p_demux->s, &p_peek, APE_TAG_HEADERSIZE ) != APE_TAG_HEADERSIZE ) return; i_size = GetAPEvXSize( p_peek, APE_TAG_HEADERSIZE ); if( i_size > 0 && stream_Peek( p_demux->s, &p_peek, i_size ) == i_size ) { msg_Dbg( p_demux, "found APEv1/2 tag" ); ParseAPEvXTag( p_demux_meta, p_peek, i_size ); } }
static struct id3_tag * tag_id3_read(FILE *stream, long offset, int whence) { struct id3_tag *tag; id3_byte_t query_buffer[ID3_TAG_QUERYSIZE]; id3_byte_t *tag_buffer; int tag_size; int query_buffer_size; int tag_buffer_size; /* It's ok if we get less than we asked for */ query_buffer_size = fill_buffer(query_buffer, ID3_TAG_QUERYSIZE, stream, offset, whence); if (query_buffer_size <= 0) return NULL; /* Look for a tag header */ tag_size = id3_tag_query(query_buffer, query_buffer_size); if (tag_size <= 0) return NULL; /* Found a tag. Allocate a buffer and read it in. */ tag_buffer = g_malloc(tag_size); if (!tag_buffer) return NULL; tag_buffer_size = fill_buffer(tag_buffer, tag_size, stream, offset, whence); if (tag_buffer_size < tag_size) { g_free(tag_buffer); return NULL; } tag = id3_tag_parse(tag_buffer, tag_buffer_size); g_free(tag_buffer); return tag; }
static enum mad_flow handle_error(void *data, struct mad_stream *stream, struct mad_frame *frame) { signed long tagsize; switch(stream->error) { case MAD_ERROR_BADDATAPTR: return MAD_FLOW_CONTINUE; case MAD_ERROR_LOSTSYNC: tagsize = id3_tag_query(stream->this_frame,stream->bufend - stream->this_frame); if(tagsize > 0) { mad_stream_skip(stream, tagsize); return MAD_FLOW_CONTINUE; } default: break; } if(stream->error == MAD_ERROR_BADCRC) { mad_frame_mute(frame); return MAD_FLOW_IGNORE; } return MAD_FLOW_CONTINUE; }
static enum mp3_action decode_next_frame_header(struct mp3_data *data, G_GNUC_UNUSED struct tag **tag) { enum mad_layer layer; if ((data->stream).buffer == NULL || (data->stream).error == MAD_ERROR_BUFLEN) { if (!mp3_fill_buffer(data)) return DECODE_BREAK; } if (mad_header_decode(&data->frame.header, &data->stream)) { if ((data->stream).error == MAD_ERROR_LOSTSYNC && (data->stream).this_frame) { signed long tagsize = id3_tag_query((data->stream). this_frame, (data->stream). bufend - (data->stream). this_frame); if (tagsize > 0) { if (tag && !(*tag)) { mp3_parse_id3(data, (size_t)tagsize, tag); } else { mad_stream_skip(&(data->stream), tagsize); } return DECODE_CONT; } } if (MAD_RECOVERABLE((data->stream).error)) { return DECODE_SKIP; } else { if ((data->stream).error == MAD_ERROR_BUFLEN) return DECODE_CONT; else { g_warning("unrecoverable frame level error " "(%s).\n", mad_stream_errorstr(&data->stream)); return DECODE_BREAK; } } } layer = data->frame.header.layer; if (!data->layer) { if (layer != MAD_LAYER_II && layer != MAD_LAYER_III) { /* Only layer 2 and 3 have been tested to work */ return DECODE_SKIP; } data->layer = layer; } else if (layer != data->layer) { /* Don't decode frames with a different layer than the first */ return DECODE_SKIP; } return DECODE_OK; }
static int getId3v2FooterSize(FILE * stream, long offset, int whence) { id3_byte_t buf[ID3_TAG_QUERYSIZE]; int bufsize; bufsize = fillBuffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence); if (bufsize <= 0) return 0; return id3_tag_query(buf, bufsize); }
enum mad_flow input_cb(void *_data, struct mad_stream *stream) { struct private_data *data = (struct private_data *)_data; if(data->progressCallback) { data->cancelled = data->progressCallback(data->userData, (float)data->file->Tell() / data->file->Length()); if(data->cancelled) return MAD_FLOW_STOP; } if(data->file->Eof()) { data->cancelled = false; return MAD_FLOW_STOP; } #ifdef USE_LIBID3TAG if (!data->id3checked) { off_t read = data->file->Read(data->inputBuffer, ID3_TAG_QUERYSIZE); int len = id3_tag_query(data->inputBuffer, ID3_TAG_QUERYSIZE); if (len > 0) { data->file->Seek(len, wxFromCurrent); } else { data->file->Seek(0); } data->id3checked = true; } #endif /* "Each time you refill your buffer, you need to preserve the data in * your existing buffer from stream.next_frame to the end. * * This usually amounts to calling memmove() on this unconsumed portion * of the buffer and appending new data after it, before calling * mad_stream_buffer()" * -- Rob Leslie, on the mad-dev mailing list */ unsigned int unconsumedBytes; if(stream->next_frame) { unconsumedBytes = data->inputBuffer + INPUT_BUFFER_SIZE - stream->next_frame; memmove(data->inputBuffer, stream->next_frame, unconsumedBytes); } else unconsumedBytes = 0; off_t read = data->file->Read(data->inputBuffer + unconsumedBytes, INPUT_BUFFER_SIZE - unconsumedBytes); mad_stream_buffer(stream, data->inputBuffer, read + unconsumedBytes); return MAD_FLOW_CONTINUE; }
/* ** long getTagLength(string strFile) ** ** This is a static method so that you can easily get the length of id3 tag if there is ** an id3 tag residing in the beginning of the MP3 file. This is useful when you try ** to decode a mp3 file. You can simply skip the id3 tag to the real audio data. However ** some exception exists such as a mp3 file with XING header or Lame information */ long CMP3ID3::getTagLength(string strFile){ int fileID = -1; fileID = open(strFile.c_str(),O_RDONLY); if ( fileID != -1 ){ unsigned char buffer[ID3_TAG_QUERYSIZE]; read(fileID,(unsigned char *)buffer,ID3_TAG_QUERYSIZE); long lRet = id3_tag_query(buffer, ID3_TAG_QUERYSIZE); if ( lRet < 0 ) lRet = 0; close(fileID); return lRet; } return 0; }
/* * NAME: query_tag() * DESCRIPTION: check for a tag at a file's current position */ static signed long query_tag(FILE *iofile) { fpos_t save_position; id3_byte_t query[ID3_TAG_QUERYSIZE]; signed long size; if (fgetpos(iofile, &save_position) == -1) return 0; size = id3_tag_query(query, fread(query, 1, sizeof(query), iofile)); if (fsetpos(iofile, &save_position) == -1) return 0; return size; }
int LibMadWrapper::findValidHeader(struct mad_header &header) { int ret; while ((ret = mad_header_decode(&header, this->stream)) != 0 && MAD_RECOVERABLE(this->stream->error)) { if (this->stream->error == MAD_ERROR_LOSTSYNC) { long tagsize = id3_tag_query(this->stream->this_frame, this->stream->bufend - this->stream->this_frame); if (tagsize > 0) { mad_stream_skip(this->stream, tagsize); continue; } } } return ret; }
static enum mp3_action decodeNextFrame(struct mp3_data *data) { if ((data->stream).buffer == NULL || (data->stream).error == MAD_ERROR_BUFLEN) { if (!mp3_fill_buffer(data)) return DECODE_BREAK; } if (mad_frame_decode(&data->frame, &data->stream)) { #ifdef HAVE_ID3TAG if ((data->stream).error == MAD_ERROR_LOSTSYNC) { signed long tagsize = id3_tag_query((data->stream). this_frame, (data->stream). bufend - (data->stream). this_frame); if (tagsize > 0) { mad_stream_skip(&(data->stream), tagsize); return DECODE_CONT; } } #endif if (MAD_RECOVERABLE((data->stream).error)) { return DECODE_SKIP; } else { if ((data->stream).error == MAD_ERROR_BUFLEN) return DECODE_CONT; else { g_warning("unrecoverable frame level error " "(%s).\n", mad_stream_errorstr(&data->stream)); return DECODE_BREAK; } } } return DECODE_OK; }
ImportFileHandle *FLACImportPlugin::Open(const wxString &filename) { // First check if it really is a FLAC file int cnt; wxFile binaryFile; if (!binaryFile.Open(filename)) { return false; // File not found } #ifdef USE_LIBID3TAG // Skip any ID3 tags that might be present id3_byte_t query[ID3_TAG_QUERYSIZE]; cnt = binaryFile.Read(query, sizeof(query)); cnt = id3_tag_query(query, cnt); binaryFile.Seek(cnt); #endif char buf[5]; cnt = binaryFile.Read(buf, 4); binaryFile.Close(); if (cnt == wxInvalidOffset || strncmp(buf, FLAC_HEADER, 4) != 0) { // File is not a FLAC file return false; } // Open the file for import FLACImportFileHandle *handle = new FLACImportFileHandle(filename); bool success = handle->Init(); if (!success) { delete handle; return NULL; } return handle; }
/** * MP3音乐播放回调函数, * 负责将解码数据填充声音缓存区 * * @note 声音缓存区的格式为双声道,16位低字序 * * @param buf 声音缓冲区指针 * @param reqn 缓冲区帧大小 * @param pdata 用户数据,无用 */ static int mp3_audiocallback(void *buf, unsigned int reqn, void *pdata) { int avail_frame; int snd_buf_frame_size = (int) reqn; int ret; double incr; signed short *audio_buf = buf; unsigned i; uint16_t *output; UNUSED(pdata); if (g_status != ST_PLAYING) { if (handle_seek() == -1) { __end(); return -1; } xAudioClearSndBuf(buf, snd_buf_frame_size); xrKernelDelayThread(100000); return 0; } while (snd_buf_frame_size > 0) { avail_frame = g_buff_frame_size - g_buff_frame_start; if (avail_frame >= snd_buf_frame_size) { send_to_sndbuf(audio_buf, &g_buff[g_buff_frame_start * 2], snd_buf_frame_size, 2); g_buff_frame_start += snd_buf_frame_size; audio_buf += snd_buf_frame_size * 2; snd_buf_frame_size = 0; } else { send_to_sndbuf(audio_buf, &g_buff[g_buff_frame_start * 2], avail_frame, 2); snd_buf_frame_size -= avail_frame; audio_buf += avail_frame * 2; if (stream.buffer == NULL || stream.error == MAD_ERROR_BUFLEN) { size_t read_size, remaining = 0; uint8_t *read_start; int bufsize; if (stream.next_frame != NULL) { remaining = stream.bufend - stream.next_frame; memmove(g_input_buff, stream.next_frame, remaining); read_start = g_input_buff + remaining; read_size = BUFF_SIZE - remaining; } else { read_size = BUFF_SIZE; read_start = g_input_buff; remaining = 0; } if (mp3_data.use_buffer) bufsize = buffered_reader_read(mp3_data.r, read_start, read_size); else bufsize = xrIoRead(mp3_data.fd, read_start, read_size); if (bufsize <= 0) { __end(); return -1; } if (bufsize < read_size) { uint8_t *guard = read_start + read_size; memset(guard, 0, MAD_BUFFER_GUARD); read_size += MAD_BUFFER_GUARD; } mad_stream_buffer(&stream, g_input_buff, read_size + remaining); stream.error = 0; } ret = mad_frame_decode(&frame, &stream); if (ret == -1) { if (MAD_RECOVERABLE(stream.error) || stream.error == MAD_ERROR_BUFLEN) { if (stream.error == MAD_ERROR_LOSTSYNC) { long tagsize = id3_tag_query(stream.this_frame, stream.bufend - stream.this_frame); if (tagsize > 0) { mad_stream_skip(&stream, tagsize); } if (mad_header_decode(&frame.header, &stream) == -1) { if (stream.error != MAD_ERROR_BUFLEN) { if (!MAD_RECOVERABLE(stream.error)) { __end(); return -1; } } } else { stream.error = MAD_ERROR_NONE; } } g_buff_frame_size = 0; g_buff_frame_start = 0; continue; } else { __end(); return -1; } } output = &g_buff[0]; if (stream.error != MAD_ERROR_NONE) { continue; } mad_synth_frame(&synth, &frame); for (i = 0; i < synth.pcm.length; i++) { signed short sample; if (MAD_NCHANNELS(&frame.header) == 2) { /* Left channel */ sample = MadFixedToSshort(synth.pcm.samples[0][i]); *(output++) = sample; sample = MadFixedToSshort(synth.pcm.samples[1][i]); *(output++) = sample; } else { sample = MadFixedToSshort(synth.pcm.samples[0][i]); *(output++) = sample; *(output++) = sample; } } g_buff_frame_size = synth.pcm.length; g_buff_frame_start = 0; incr = frame.header.duration.seconds; incr += mad_timer_fraction(frame.header.duration, MAD_UNITS_MILLISECONDS) / 1000.0; g_play_time += incr; add_bitrate(&g_inst_br, frame.header.bitrate, incr); } } return 0; }
void LibMadWrapper::render(pcm_t *const bufferToFill, const uint32_t Channels, frame_t framesToRender) { framesToRender = min(framesToRender, this->getFrames() - this->framesAlreadyRendered); int32_t *pcm = static_cast<int32_t *>(bufferToFill); // the outer loop, used for decoding and synthesizing MPEG frames while (framesToRender > 0 && !this->stopFillBuffer) { // write back tempbuffer, i.e. frames weve buffered from previous calls to libmad (necessary due to inelegant API of libmad, i.e. cant tell how many frames to render during one call) { const size_t itemsToCpy = min<size_t>(this->tempBuf.size(), framesToRender * Channels); memcpy(pcm, this->tempBuf.data(), itemsToCpy * sizeof(int32_t)); this->tempBuf.erase(this->tempBuf.begin(), this->tempBuf.begin() + itemsToCpy); const size_t framesCpyd = itemsToCpy / Channels; framesToRender -= framesCpyd; this->framesAlreadyRendered += framesCpyd; // again: adjust position pcm += itemsToCpy; } int framesToDoNow = (framesToRender / gConfig.FramesToRender) > 0 ? gConfig.FramesToRender : framesToRender % gConfig.FramesToRender; if (framesToDoNow == 0) { continue; } int ret = mad_frame_decode(&this->frame.Value, this->stream); if (ret != 0) { if (this->stream->error == MAD_ERROR_LOSTSYNC) { long tagsize = id3_tag_query(this->stream->this_frame, this->stream->bufend - this->stream->this_frame); if (tagsize > 0) { mad_stream_skip(this->stream, tagsize); continue; } } string errstr = mad_stream_errorstr(this->stream); if (MAD_RECOVERABLE(this->stream->error)) { errstr += " (recoverable)"; CLOG(LogLevel_t::Info, errstr); continue; } else { errstr += " (not recoverable)"; CLOG(LogLevel_t::Warning, errstr); break; } } mad_synth_frame(&this->synth.Value, &this->frame.Value); /* save PCM samples from synth.pcm */ /* &synth.pcm->samplerate contains the sampling frequency */ unsigned short nsamples = this->synth->pcm.length; mad_fixed_t const *left_ch = this->synth->pcm.samples[0]; mad_fixed_t const *right_ch = this->synth->pcm.samples[1]; unsigned int item = 0; /* audio normalization */ const float absoluteGain = (numeric_limits<int32_t>::max()) / (numeric_limits<int32_t>::max() * this->gainCorrection); for (; !this->stopFillBuffer && framesToDoNow > 0 && // frames left during this loop framesToRender > 0 && // frames left during this call nsamples > 0; // frames left from libmad framesToRender--, nsamples--, framesToDoNow--) { int32_t sample; /* output sample(s) in 24-bit signed little-endian PCM */ sample = LibMadWrapper::toInt24Sample(*left_ch++); sample = gConfig.useAudioNormalization ? floor(sample * absoluteGain) : sample; pcm[item++] = sample; if (Channels == 2) // our buffer is for 2 channels { if (this->synth.Value.pcm.channels == 2) // ...but did mad also decoded for 2 channels? { sample = LibMadWrapper::toInt24Sample(*right_ch++); sample = gConfig.useAudioNormalization ? floor(sample * absoluteGain) : sample; pcm[item++] = sample; } else { // what? only one channel in a stereo file? well then: pseudo stereo pcm[item++] = sample; CLOG(LogLevel_t::Warning, "decoded only one channel, though this is a stereo file!"); } } this->framesAlreadyRendered++; } pcm += item /* % this->count*/; // "bufferToFill" (i.e. "pcm") seems to be full, drain the rest pcm samples from libmad and temporarily save them while (!this->stopFillBuffer && nsamples > 0) { int32_t sample; /* output sample(s) in 24-bit signed little-endian PCM */ sample = LibMadWrapper::toInt24Sample(*left_ch++); this->tempBuf.push_back(gConfig.useAudioNormalization ? floor(sample * absoluteGain) : sample); if (Channels == 2) { sample = LibMadWrapper::toInt24Sample(*right_ch++); this->tempBuf.push_back(gConfig.useAudioNormalization ? floor(sample * absoluteGain) : sample); } /* DONT do this: this->framesAlreadyRendered++; since we use framesAlreadyRendered as offset for "bufferToFill"*/ nsamples--; } if (item > this->count) { CLOG(LogLevel_t::Error, "THIS SHOULD NEVER HAPPEN: read " << item << " items but only expected " << this->count << "\n"); break; } } }
/* Handle first-stage decoding: extracting the MP3 frame data. */ int RageSoundReader_MP3::do_mad_frame_decode( bool headers_only ) { int bytes_read = 0; while(1) { int ret; /* Always actually decode the first packet, so we cleanly parse Xing tags. */ if( headers_only && !mad->first_frame ) ret=mad_header_decode( &mad->Frame.header,&mad->Stream ); else ret=mad_frame_decode( &mad->Frame,&mad->Stream ); if( ret == -1 && (mad->Stream.error == MAD_ERROR_BUFLEN || mad->Stream.error == MAD_ERROR_BUFPTR) ) { if( bytes_read > 25000 ) { /* We've read this much without actually getting a frame; error. */ SetError( "Can't find data" ); return -1; } ret = fill_buffer(); if( ret <= 0 ) return ret; bytes_read += ret; continue; } if( ret == -1 && mad->Stream.error == MAD_ERROR_LOSTSYNC ) { /* This might be an ID3V2 tag. */ const int tagsize = id3_tag_query(mad->Stream.this_frame, mad->Stream.bufend - mad->Stream.this_frame); if( tagsize ) { mad_stream_skip(&mad->Stream, tagsize); /* Don't count the tagsize against the max-read-per-call figure. */ bytes_read -= tagsize; continue; } } if( ret == -1 && mad->Stream.error == MAD_ERROR_BADDATAPTR ) { /* * Something's corrupt. One cause of this is cutting an MP3 in the middle * without reencoding; the first two frames will reference data from previous * frames that have been removed. The frame is valid--we can get a header from * it, we just can't synth useful data. * * BASS pretends the bad frames are silent. Emulate that, for compatibility. */ ret = 0; /* pretend success */ } if( !ret ) { /* OK. */ if( mad->first_frame ) { /* We're at the beginning. Is this a Xing tag? */ if(handle_first_frame()) { /* The first frame contained a header. Continue searching. */ continue; } /* We've decoded the first frame of data. * * We want mad->Timer to represent the timestamp of the first sample of the * currently decoded frame. Don't increment mad->Timer on the first frame, * or it'll be the time of the *next* frame. (All frames have the same * duration.) */ mad->first_frame = false; mad->Timer = mad_timer_zero; mad->header_bytes = get_this_frame_byte(mad); } else { mad_timer_add( &mad->Timer,mad->Frame.header.duration ); } fill_frame_index_cache( mad ); return 1; } if( mad->Stream.error == MAD_ERROR_BADCRC ) { /* XXX untested */ mad_frame_mute(&mad->Frame); mad_synth_mute(&mad->Synth); continue; } if( !MAD_RECOVERABLE(mad->Stream.error) ) { /* We've received an unrecoverable error. */ SetError( mad_stream_errorstr(&mad->Stream) ); return -1; } } }
/***************************************************************************** * CheckFooter: check for ID3/APE at the end of the file * CheckHeader: check for ID3/APE at the begining of the file *****************************************************************************/ static void CheckFooter( demux_meta_t *p_demux_meta ) { demux_t *p_demux = (demux_t *)p_demux_meta->p_demux; const int64_t i_pos = stream_Size( p_demux->s ); const size_t i_peek = 128+APE_TAG_HEADERSIZE; const uint8_t *p_peek; const uint8_t *p_peek_id3; int64_t i_id3v2_pos = -1; int64_t i_apevx_pos = -1; int i_id3v2_size; int i_apevx_size; size_t i_id3v1_size; if( i_pos < i_peek ) return; if( stream_Seek( p_demux->s, i_pos - i_peek ) ) return; if( stream_Peek( p_demux->s, &p_peek, i_peek ) < i_peek ) return; p_peek_id3 = &p_peek[APE_TAG_HEADERSIZE]; /* Check/Parse ID3v1 */ i_id3v1_size = id3_tag_query( p_peek_id3, ID3_TAG_QUERYSIZE ); if( i_id3v1_size == 128 ) { msg_Dbg( p_demux, "found ID3v1 tag" ); ParseID3Tag( p_demux_meta, p_peek_id3, i_id3v1_size ); } /* Compute ID3v2 position */ i_id3v2_size = -id3_tag_query( &p_peek_id3[128-ID3_TAG_QUERYSIZE], ID3_TAG_QUERYSIZE ); if( i_id3v2_size > 0 ) i_id3v2_pos = i_pos - i_id3v2_size; /* Compute APE2v2 position */ i_apevx_size = GetAPEvXSize( &p_peek[128+0], APE_TAG_HEADERSIZE ); if( i_apevx_size > 0 ) { i_apevx_pos = i_pos - i_apevx_size; } else if( i_id3v1_size > 0 ) { /* it can be before ID3v1 */ i_apevx_size = GetAPEvXSize( p_peek, APE_TAG_HEADERSIZE ); if( i_apevx_size > 0 ) i_apevx_pos = i_pos - 128 - i_apevx_size; } if( i_id3v2_pos > 0 && i_apevx_pos > 0 ) { msg_Warn( p_demux, "Both ID3v2 and APEv1/2 at the end of file, ignoring APEv1/2" ); i_apevx_pos = -1; } /* Parse ID3v2.4 */ if( i_id3v2_pos > 0 ) { if( !stream_Seek( p_demux->s, i_id3v2_pos ) && stream_Peek( p_demux->s, &p_peek, i_id3v2_size ) == i_id3v2_size ) { msg_Dbg( p_demux, "found ID3v2 tag at end of file" ); ParseID3Tag( p_demux_meta, p_peek, i_id3v2_size ); } } /* Parse APEv1/2 */ if( i_apevx_pos > 0 ) { if( !stream_Seek( p_demux->s, i_apevx_pos ) && stream_Peek( p_demux->s, &p_peek, i_apevx_size ) == i_apevx_size ) { msg_Dbg( p_demux, "found APEvx tag at end of file" ); ParseAPEvXTag( p_demux_meta, p_peek, i_apevx_size ); } } }