Hi, here is a patch that allows for seeking in aac files. bye eichhofener
diff --git a/src/decoder/DecoderBuffer.cxx b/src/decoder/DecoderBuffer.cxx index 4767151..2667a7b 100644 --- a/src/decoder/DecoderBuffer.cxx +++ b/src/decoder/DecoderBuffer.cxx @@ -20,148 +20,94 @@ #include "config.h" #include "DecoderBuffer.hxx" #include "DecoderAPI.hxx" -#include "util/ConstBuffer.hxx" #include "util/VarSize.hxx" +#include "util/Error.hxx" #include <assert.h> #include <string.h> #include <stdlib.h> -struct DecoderBuffer { - Decoder *decoder; - InputStream *is; - /** the allocated size of the buffer */ - size_t size; - - /** the current length of the buffer */ - size_t length; - - /** number of bytes already consumed at the beginning of the - buffer */ - size_t consumed; - - /** the actual buffer (dynamic size) */ - unsigned char data[sizeof(size_t)]; - - DecoderBuffer(Decoder *_decoder, InputStream &_is, - size_t _size) - :decoder(_decoder), is(&_is), - size(_size), length(0), consumed(0) {} -}; - -DecoderBuffer * -decoder_buffer_new(Decoder *decoder, InputStream &is, - size_t size) -{ - assert(size > 0); - - return NewVarSize<DecoderBuffer>(sizeof(DecoderBuffer::data), - size, - decoder, is, size); -} - -void -decoder_buffer_free(DecoderBuffer *buffer) +void DecoderBuffer::shift() { - assert(buffer != nullptr); + assert(consumed > 0); - DeleteVarSize(buffer); + length -= consumed; + memmove(bufdata, bufdata + consumed, length); + consumed = 0; } -bool -decoder_buffer_is_empty(const DecoderBuffer *buffer) -{ - return buffer->consumed == buffer->length; -} - -bool -decoder_buffer_is_full(const DecoderBuffer *buffer) -{ - return buffer->consumed == 0 && buffer->length == buffer->size; -} - -void -decoder_buffer_clear(DecoderBuffer *buffer) -{ - buffer->length = buffer->consumed = 0; -} - -static void -decoder_buffer_shift(DecoderBuffer *buffer) -{ - assert(buffer->consumed > 0); - - buffer->length -= buffer->consumed; - memmove(buffer->data, buffer->data + buffer->consumed, buffer->length); - buffer->consumed = 0; -} - -bool -decoder_buffer_fill(DecoderBuffer *buffer) +bool DecoderBuffer::fill() { size_t nbytes; - if (buffer->consumed > 0) - decoder_buffer_shift(buffer); + if (consumed > 0) shift(); - if (buffer->length >= buffer->size) + if (length >= bufsize) /* buffer is full */ return false; - nbytes = decoder_read(buffer->decoder, *buffer->is, - buffer->data + buffer->length, - buffer->size - buffer->length); + nbytes = decoder_read(decoder, is, + bufdata + length, + bufsize - length); if (nbytes == 0) /* end of file, I/O error or decoder command received */ return false; - buffer->length += nbytes; - assert(buffer->length <= buffer->size); + length += nbytes; + assert(length <= bufsize); return true; } -ConstBuffer<void> -decoder_buffer_read(const DecoderBuffer *buffer) +ConstBuffer<void> DecoderBuffer::read() const { return { - buffer->data + buffer->consumed, - buffer->length - buffer->consumed + bufdata + consumed, + length - consumed }; } -void -decoder_buffer_consume(DecoderBuffer *buffer, size_t nbytes) +void DecoderBuffer::consume(size_t nbytes) { /* just move the "consumed" pointer - decoder_buffer_shift() will do the real work later (called by decoder_buffer_fill()) */ - buffer->consumed += nbytes; + consumed += nbytes; + offset += nbytes; + + assert(consumed <= length); +} - assert(buffer->consumed <= buffer->length); +bool DecoderBuffer::seek(InputStream::offset_type o) +{ + Error e; + if(!is.Seek(o, SEEK_SET, e)) return false; + + offset=o; + clear(); + return true; } -bool -decoder_buffer_skip(DecoderBuffer *buffer, size_t nbytes) +bool DecoderBuffer::skip(size_t nbytes) { bool success; /* this could probably be optimized by seeking */ while (true) { - auto data = decoder_buffer_read(buffer); + auto data = read(); if (!data.IsEmpty()) { if (data.size > nbytes) data.size = nbytes; - decoder_buffer_consume(buffer, data.size); + consume(data.size); nbytes -= data.size; if (nbytes == 0) return true; } - success = decoder_buffer_fill(buffer); + success = fill(); if (!success) return false; } diff --git a/src/decoder/DecoderBuffer.hxx b/src/decoder/DecoderBuffer.hxx index 4cadd77..9b7eaf9 100644 --- a/src/decoder/DecoderBuffer.hxx +++ b/src/decoder/DecoderBuffer.hxx @@ -21,6 +21,8 @@ #define MPD_DECODER_BUFFER_HXX #include "Compiler.h" +#include "input/InputStream.hxx" +#include "util/ConstBuffer.hxx" #include <stddef.h> @@ -29,83 +31,116 @@ * create a buffer object, and use its high-level methods to fill and * read it. It will automatically handle shifting the buffer. */ -struct DecoderBuffer; - struct Decoder; struct InputStream; template<typename T> struct ConstBuffer; -/** - * Creates a new buffer. - * - * @param decoder the decoder object, used for decoder_read(), may be nullptr - * @param is the input stream object where we should read from - * @param size the maximum size of the buffer - * @return the new decoder_buffer object - */ -DecoderBuffer * -decoder_buffer_new(Decoder *decoder, InputStream &is, - size_t size); - -/** - * Frees resources used by the decoder_buffer object. - */ -void -decoder_buffer_free(DecoderBuffer *buffer); - -gcc_pure -bool -decoder_buffer_is_empty(const DecoderBuffer *buffer); - -gcc_pure -bool -decoder_buffer_is_full(const DecoderBuffer *buffer); +class DecoderBuffer +{ +public: + /** + * Creates a new buffer. + * + * @param decoder the decoder object, used for decoder_read(), may be nullptr + * @param is the input stream object where we should read from + * @param size the maximum size of the buffer + */ + DecoderBuffer(Decoder* _decoder, InputStream& _is, size_t _size) + :decoder(_decoder), is(_is), offset(0), bufsize(_size), length(0), consumed(0) + { + bufdata=new unsigned char[bufsize]; + } + + /** + * Frees resources used by the decoder_buffer object. + */ + ~DecoderBuffer() { delete[] bufdata; } + + /** + * Read data from the input_stream and append it to the buffer. + * + * @return true if data was appended; false if there is no data + * available (yet), end of file, I/O error or a decoder command was + * received + */ + bool fill(); + + /** + * Skips the specified number of bytes, discarding its data. + * + * @param buffer the decoder_buffer object + * @param nbytes the number of bytes to skip + * @return true on success, false on error + */ + bool skip(size_t nbytes); + + /** + * Reads data from the buffer. This data is not yet consumed, you + * have to call decoder_buffer_consume() to do that. The returned + * buffer becomes invalid after a decoder_buffer_fill() or a + * decoder_buffer_consume() call. + * + * @param buffer the decoder_buffer object + */ + ConstBuffer<void> read() const; + + /** + * Consume (delete, invalidate) a part of the buffer. The "nbytes" + * parameter must not be larger than the length returned by + * decoder_buffer_read(). + * + * @param buffer the decoder_buffer object + * @param nbytes the number of bytes to consume + */ + void consume(size_t nbytes); + + bool seek(InputStream::offset_type offset); + + void clear() + { + length = consumed = 0; + } + + bool full() const + { + return consumed == 0 && length == bufsize; + } + + InputStream::offset_type getOffset() const { return offset; } + +private: + bool empty() const + { + return consumed == length; + } + + void shift(); + +private: + Decoder* decoder; + +protected: + InputStream& is; + +private: + InputStream::offset_type offset; + + /** the allocated size of the buffer */ + size_t bufsize; + + /** the current length of the buffer */ + size_t length; + + /** number of bytes already consumed at the beginning of the + buffer */ + size_t consumed; + + /** the actual buffer (dynamic size) */ + unsigned char* bufdata; +}; -void -decoder_buffer_clear(DecoderBuffer *buffer); -/** - * Read data from the input_stream and append it to the buffer. - * - * @return true if data was appended; false if there is no data - * available (yet), end of file, I/O error or a decoder command was - * received - */ -bool -decoder_buffer_fill(DecoderBuffer *buffer); -/** - * Reads data from the buffer. This data is not yet consumed, you - * have to call decoder_buffer_consume() to do that. The returned - * buffer becomes invalid after a decoder_buffer_fill() or a - * decoder_buffer_consume() call. - * - * @param buffer the decoder_buffer object - */ -gcc_pure -ConstBuffer<void> -decoder_buffer_read(const DecoderBuffer *buffer); - -/** - * Consume (delete, invalidate) a part of the buffer. The "nbytes" - * parameter must not be larger than the length returned by - * decoder_buffer_read(). - * - * @param buffer the decoder_buffer object - * @param nbytes the number of bytes to consume - */ -void -decoder_buffer_consume(DecoderBuffer *buffer, size_t nbytes); - -/** - * Skips the specified number of bytes, discarding its data. - * - * @param buffer the decoder_buffer object - * @param nbytes the number of bytes to skip - * @return true on success, false on error - */ -bool -decoder_buffer_skip(DecoderBuffer *buffer, size_t nbytes); #endif diff --git a/src/decoder/plugins/FaadDecoderPlugin.cxx b/src/decoder/plugins/FaadDecoderPlugin.cxx index e80665d..03d493b 100644 --- a/src/decoder/plugins/FaadDecoderPlugin.cxx +++ b/src/decoder/plugins/FaadDecoderPlugin.cxx @@ -35,21 +35,64 @@ #include <string.h> #include <unistd.h> +#include <vector> +#include <iostream> + #define AAC_MAX_CHANNELS 6 -static const unsigned adts_sample_rates[] = - { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, - 16000, 12000, 11025, 8000, 7350, 0, 0, 0 +static constexpr Domain faad_decoder_domain("faad_decoder"); + + +class DecoderBufferFaad : public DecoderBuffer +{ +public: + DecoderBufferFaad(Decoder* _decoder, InputStream& _is, size_t _size) + : DecoderBuffer(_decoder, _is, _size) + { + duration=faad_song_duration(); + } + + double getDuration() const { return duration; } + size_t adts_find_frame(); + + float seekSample(float seekTime); + + enum { samplesPerFrame=1024 }; +private: + /** + * Check whether the buffer head is an AAC frame, and return the frame + * length. Returns 0 if it is not a frame. + */ + static size_t adts_check_frame(const unsigned char *bufdata); + + /** + * Find the next AAC frame in the buffer. Returns 0 if no frame is + * found or if not enough data is available. + */ + float adts_song_duration(); + + float faad_song_duration(); + +private: + static const unsigned adts_sample_rates[]; + + unsigned sampleRate; + float duration; + + short offset0; + std::vector<short> offsets; }; -static constexpr Domain faad_decoder_domain("faad_decoder"); +const unsigned DecoderBufferFaad::adts_sample_rates[] = + { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, 7350, 0, 0, 0 +}; /** * Check whether the buffer head is an AAC frame, and return the frame * length. Returns 0 if it is not a frame. */ -static size_t -adts_check_frame(const unsigned char *data) +size_t DecoderBufferFaad::adts_check_frame(const unsigned char *data) { /* check syncword */ if (!((data[0] == 0xFF) && ((data[1] & 0xF6) == 0xF0))) @@ -64,14 +107,13 @@ adts_check_frame(const unsigned char *data) * Find the next AAC frame in the buffer. Returns 0 if no frame is * found or if not enough data is available. */ -static size_t -adts_find_frame(DecoderBuffer *buffer) +size_t DecoderBufferFaad::adts_find_frame() { while (true) { - auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + auto data = ConstBuffer<uint8_t>::FromVoid(read()); if (data.size < 8) { /* not enough data yet */ - if (!decoder_buffer_fill(buffer)) + if (!fill()) /* failed */ return 0; @@ -83,13 +125,13 @@ adts_find_frame(DecoderBuffer *buffer) memchr(data.data, 0xff, data.size); if (p == nullptr) { /* no marker - discard the buffer */ - decoder_buffer_clear(buffer); + clear(); continue; } if (p > data.data) { /* discard data before 0xff */ - decoder_buffer_consume(buffer, p - data.data); + consume(p - data.data); continue; } @@ -98,7 +140,7 @@ adts_find_frame(DecoderBuffer *buffer) if (frame_length == 0) { /* it's just some random 0xff byte; discard it and continue searching */ - decoder_buffer_consume(buffer, 1); + consume(1); continue; } @@ -106,11 +148,11 @@ adts_find_frame(DecoderBuffer *buffer) /* available buffer size is smaller than the frame will be - attempt to read more data */ - if (!decoder_buffer_fill(buffer)) { + if (!fill()) { /* not enough data; discard this frame to prevent a possible buffer overflow */ - decoder_buffer_clear(buffer); + clear(); } continue; @@ -121,44 +163,52 @@ adts_find_frame(DecoderBuffer *buffer) } } -static float -adts_song_duration(DecoderBuffer *buffer) +float DecoderBufferFaad::adts_song_duration() { - unsigned sample_rate = 0; + sampleRate = 0; + short lastOffset=0; + offset0=0; + offsets.clear(); /* Read all frames to ensure correct time and bitrate */ unsigned frames = 0; for (;; frames++) { - unsigned frame_length = adts_find_frame(buffer); + unsigned frame_length = adts_find_frame(); if (frame_length == 0) break; if (frames == 0) { - auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + auto data = ConstBuffer<uint8_t>::FromVoid(read()); assert(!data.IsEmpty()); assert(frame_length <= data.size); - sample_rate = adts_sample_rates[(data.data[2] & 0x3c) >> 2]; + sampleRate = adts_sample_rates[(data.data[2] & 0x3c) >> 2]; + offset0=getOffset(); + lastOffset=offset0; + } + else + { + offsets.push_back(getOffset()-lastOffset); + lastOffset=getOffset(); } - decoder_buffer_consume(buffer, frame_length); + consume(frame_length); } - float frames_per_second = (float)sample_rate / 1024.0; + float frames_per_second = ((float)sampleRate) / samplesPerFrame; if (frames_per_second <= 0) return -1; return (float)frames / frames_per_second; } -static float -faad_song_duration(DecoderBuffer *buffer, InputStream &is) +float DecoderBufferFaad::faad_song_duration() { const auto size = is.GetSize(); const size_t fileread = size >= 0 ? size : 0; - decoder_buffer_fill(buffer); - auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + fill(); + auto data = ConstBuffer<uint8_t>::FromVoid(read()); if (data.IsEmpty()) return -1; @@ -167,29 +217,28 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is) /* skip the ID3 tag */ tagsize = (data.data[6] << 21) | (data.data[7] << 14) | - (data.data[8] << 7) | (data.data[9] << 0); + (data.data[8] << 7) | (data.data[9] << 0); tagsize += 10; - bool success = decoder_buffer_skip(buffer, tagsize) && - decoder_buffer_fill(buffer); + bool success = skip(tagsize) && fill(); if (!success) return -1; - data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + data = ConstBuffer<uint8_t>::FromVoid(read()); if (data.IsEmpty()) return -1; } if (is.IsSeekable() && data.size >= 2 && - data.data[0] == 0xFF && ((data.data[1] & 0xF6) == 0xF0)) { + data.data[0] == 0xFF && ((data.data[1] & 0xF6) == 0xF0)) { /* obtain the duration from the ADTS header */ - float song_length = adts_song_duration(buffer); + float song_length = adts_song_duration(); is.LockSeek(tagsize, SEEK_SET, IgnoreError()); - decoder_buffer_clear(buffer); - decoder_buffer_fill(buffer); + clear(); + fill(); return song_length; } else if (data.size >= 5 && memcmp(data.data, "ADIF", 4) == 0) { @@ -215,12 +264,25 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is) return -1; } +float DecoderBufferFaad::seekSample(float seekTime) +{ + int frame=seekTime*sampleRate/samplesPerFrame; + + InputStream::offset_type seekOffset=offset0; + for(int i=0; i<frame; ++i) + seekOffset+=offsets[i]; + + std::cout << "seek offset "<< seekOffset << std::endl; + seek(seekOffset); + return ((float)frame)*samplesPerFrame/sampleRate; +} + /** * Wrapper for NeAACDecInit() which works around some API * inconsistencies in libfaad. */ static bool -faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer, +faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer& buffer, AudioFormat &audio_format, Error &error) { uint32_t sample_rate; @@ -233,7 +295,7 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer, uint32_t *sample_rate_p = &sample_rate; #endif - auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + auto data = ConstBuffer<uint8_t>::FromVoid(buffer.read()); if (data.IsEmpty()) { error.Set(faad_decoder_domain, "Empty file"); return false; @@ -241,16 +303,16 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer, uint8_t channels; int32_t nbytes = NeAACDecInit(decoder, - /* deconst hack, libfaad requires this */ - const_cast<uint8_t *>(data.data), - data.size, - sample_rate_p, &channels); + /* deconst hack, libfaad requires this */ + const_cast<uint8_t *>(data.data), + data.size, + sample_rate_p, &channels); if (nbytes < 0) { error.Set(faad_decoder_domain, "Not an AAC stream"); return false; } - decoder_buffer_consume(buffer, nbytes); + buffer.consume(nbytes); return audio_format_init_checked(audio_format, sample_rate, SampleFormat::S16, channels, error); @@ -261,17 +323,17 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer, * inconsistencies in libfaad. */ static const void * -faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer *buffer, - NeAACDecFrameInfo *frame_info) +faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer& buffer, + NeAACDecFrameInfo *frame_info) { - auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + auto data = ConstBuffer<uint8_t>::FromVoid(buffer.read()); if (data.IsEmpty()) return nullptr; return NeAACDecDecode(decoder, frame_info, - /* deconst hack, libfaad requires this */ - const_cast<uint8_t *>(data.data), - data.size); + /* deconst hack, libfaad requires this */ + const_cast<uint8_t *>(data.data), + data.size); } /** @@ -282,10 +344,8 @@ faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer *buffer, static float faad_get_file_time_float(InputStream &is) { - DecoderBuffer *buffer = - decoder_buffer_new(nullptr, is, - FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); - float length = faad_song_duration(buffer, is); + DecoderBufferFaad buffer(nullptr, is, FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); + float length = buffer.getDuration(); if (length < 0) { NeAACDecHandle decoder = NeAACDecOpen(); @@ -295,18 +355,16 @@ faad_get_file_time_float(InputStream &is) config->outputFormat = FAAD_FMT_16BIT; NeAACDecSetConfiguration(decoder, config); - decoder_buffer_fill(buffer); + buffer.fill(); AudioFormat audio_format; if (faad_decoder_init(decoder, buffer, audio_format, - IgnoreError())) + IgnoreError())) length = 0; NeAACDecClose(decoder); } - decoder_buffer_free(buffer); - return length; } @@ -319,9 +377,9 @@ static int faad_get_file_time(InputStream &is) { int file_time = -1; - float length; + float length=faad_get_file_time_float(is); - if ((length = faad_get_file_time_float(is)) >= 0) + if (length >= 0) file_time = length + 0.5; return file_time; @@ -330,10 +388,8 @@ faad_get_file_time(InputStream &is) static void faad_stream_decode(Decoder &mpd_decoder, InputStream &is) { - DecoderBuffer *buffer = - decoder_buffer_new(&mpd_decoder, is, - FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); - const float total_time = faad_song_duration(buffer, is); + DecoderBufferFaad buffer(&mpd_decoder, is, FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); + const float total_time = buffer.getDuration(); /* create the libfaad decoder */ @@ -346,10 +402,10 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is) config->dontUpSampleImplicitSBR = 0; NeAACDecSetConfiguration(decoder, config); - while (!decoder_buffer_is_full(buffer) && !is.LockIsEOF() && - decoder_get_command(mpd_decoder) == DecoderCommand::NONE) { - adts_find_frame(buffer); - decoder_buffer_fill(buffer); + while (!buffer.full() && !is.LockIsEOF() && + decoder_get_command(mpd_decoder) == DecoderCommand::NONE) { + buffer.adts_find_frame(); + buffer.fill(); } /* initialize it */ @@ -359,13 +415,12 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is) if (!faad_decoder_init(decoder, buffer, audio_format, error)) { LogError(error); NeAACDecClose(decoder); - decoder_buffer_free(buffer); return; } /* initialize the MPD core */ - decoder_initialized(mpd_decoder, audio_format, false, total_time); + decoder_initialized(mpd_decoder, audio_format, true, total_time); /* the decoder loop */ @@ -378,7 +433,7 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is) /* find the next frame */ - frame_size = adts_find_frame(buffer); + frame_size = buffer.adts_find_frame(); if (frame_size == 0) /* end of file */ break; @@ -389,34 +444,34 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is) if (frame_info.error > 0) { FormatWarning(faad_decoder_domain, - "error decoding AAC stream: %s", - NeAACDecGetErrorMessage(frame_info.error)); + "error decoding AAC stream: %s", + NeAACDecGetErrorMessage(frame_info.error)); break; } if (frame_info.channels != audio_format.channels) { FormatDefault(faad_decoder_domain, - "channel count changed from %u to %u", - audio_format.channels, frame_info.channels); + "channel count changed from %u to %u", + audio_format.channels, frame_info.channels); break; } if (frame_info.samplerate != audio_format.sample_rate) { FormatDefault(faad_decoder_domain, - "sample rate changed from %u to %lu", - audio_format.sample_rate, - (unsigned long)frame_info.samplerate); + "sample rate changed from %u to %lu", + audio_format.sample_rate, + (unsigned long)frame_info.samplerate); break; } - decoder_buffer_consume(buffer, frame_info.bytesconsumed); + buffer.consume(frame_info.bytesconsumed); /* update bit rate and position */ if (frame_info.samples > 0) { bit_rate = frame_info.bytesconsumed * 8.0 * - frame_info.channels * audio_format.sample_rate / - frame_info.samples / 1000 + 0.5; + frame_info.channels * audio_format.sample_rate / + frame_info.samples / 1000 + 0.5; } /* send PCM samples to MPD */ @@ -424,12 +479,22 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is) cmd = decoder_data(mpd_decoder, is, decoded, (size_t)frame_info.samples * 2, bit_rate); + + if (cmd == DecoderCommand::SEEK) + { + float seekTime = decoder_seek_where(mpd_decoder); + float foundTime=buffer.seekSample(seekTime); + decoder_command_finished(mpd_decoder); + decoder_timestamp(mpd_decoder, foundTime); + + cmd = DecoderCommand::NONE; + } + } while (cmd != DecoderCommand::STOP); /* cleanup */ NeAACDecClose(decoder); - decoder_buffer_free(buffer); } static bool
_______________________________________________ mpd-devel mailing list mpd-devel@musicpd.org http://mailman.blarg.de/listinfo/mpd-devel