diff options
Diffstat (limited to 'src/decoder')
-rw-r--r-- | src/decoder/DecoderAPI.cxx | 641 | ||||
-rw-r--r-- | src/decoder/DecoderAPI.hxx | 237 | ||||
-rw-r--r-- | src/decoder/DecoderBuffer.cxx | 71 | ||||
-rw-r--r-- | src/decoder/DecoderBuffer.hxx | 117 | ||||
-rw-r--r-- | src/decoder/DecoderCommand.hxx | 32 | ||||
-rw-r--r-- | src/decoder/DecoderControl.cxx | 139 | ||||
-rw-r--r-- | src/decoder/DecoderControl.hxx | 395 | ||||
-rw-r--r-- | src/decoder/DecoderError.cxx | 23 | ||||
-rw-r--r-- | src/decoder/DecoderError.hxx | 25 | ||||
-rw-r--r-- | src/decoder/DecoderInternal.cxx | 104 | ||||
-rw-r--r-- | src/decoder/DecoderInternal.hxx | 126 | ||||
-rw-r--r-- | src/decoder/DecoderList.cxx | 163 | ||||
-rw-r--r-- | src/decoder/DecoderList.hxx | 89 | ||||
-rw-r--r-- | src/decoder/DecoderPlugin.cxx | 48 | ||||
-rw-r--r-- | src/decoder/DecoderPlugin.hxx | 186 | ||||
-rw-r--r-- | src/decoder/DecoderPrint.cxx | 55 | ||||
-rw-r--r-- | src/decoder/DecoderPrint.hxx | 28 | ||||
-rw-r--r-- | src/decoder/DecoderThread.cxx | 509 | ||||
-rw-r--r-- | src/decoder/DecoderThread.hxx | 28 | ||||
-rw-r--r-- | src/decoder/plugins/AdPlugDecoderPlugin.cxx (renamed from src/decoder/AdPlugDecoderPlugin.cxx) | 27 | ||||
-rw-r--r-- | src/decoder/plugins/AdPlugDecoderPlugin.h (renamed from src/decoder/AdPlugDecoderPlugin.h) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/AudiofileDecoderPlugin.cxx (renamed from src/decoder/AudiofileDecoderPlugin.cxx) | 146 | ||||
-rw-r--r-- | src/decoder/plugins/AudiofileDecoderPlugin.hxx (renamed from src/decoder/AudiofileDecoderPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/DsdLib.cxx (renamed from src/decoder/DsdLib.cxx) | 93 | ||||
-rw-r--r-- | src/decoder/plugins/DsdLib.hxx (renamed from src/decoder/DsdLib.hxx) | 16 | ||||
-rw-r--r-- | src/decoder/plugins/DsdiffDecoderPlugin.cxx (renamed from src/decoder/DsdiffDecoderPlugin.cxx) | 166 | ||||
-rw-r--r-- | src/decoder/plugins/DsdiffDecoderPlugin.hxx (renamed from src/decoder/DsdiffDecoderPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/DsfDecoderPlugin.cxx (renamed from src/decoder/DsfDecoderPlugin.cxx) | 210 | ||||
-rw-r--r-- | src/decoder/plugins/DsfDecoderPlugin.hxx (renamed from src/decoder/DsfDecoderPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/FaadDecoderPlugin.cxx (renamed from src/decoder/FaadDecoderPlugin.cxx) | 296 | ||||
-rw-r--r-- | src/decoder/plugins/FaadDecoderPlugin.hxx (renamed from src/decoder/FaadDecoderPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/FfmpegDecoderPlugin.cxx (renamed from src/decoder/FfmpegDecoderPlugin.cxx) | 86 | ||||
-rw-r--r-- | src/decoder/plugins/FfmpegDecoderPlugin.hxx (renamed from src/decoder/FfmpegDecoderPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/FfmpegMetaData.cxx (renamed from src/decoder/FfmpegMetaData.cxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/FfmpegMetaData.hxx (renamed from src/decoder/FfmpegMetaData.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/FlacCommon.cxx (renamed from src/decoder/FlacCommon.cxx) | 23 | ||||
-rw-r--r-- | src/decoder/plugins/FlacCommon.hxx (renamed from src/decoder/FlacCommon.hxx) | 5 | ||||
-rw-r--r-- | src/decoder/plugins/FlacDecoderPlugin.cxx (renamed from src/decoder/FlacDecoderPlugin.cxx) | 30 | ||||
-rw-r--r-- | src/decoder/plugins/FlacDecoderPlugin.h (renamed from src/decoder/FlacDecoderPlugin.h) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/FlacDomain.cxx (renamed from src/decoder/FlacDomain.cxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/FlacDomain.hxx (renamed from src/decoder/FlacDomain.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/FlacIOHandle.cxx (renamed from src/decoder/FlacIOHandle.cxx) | 30 | ||||
-rw-r--r-- | src/decoder/plugins/FlacIOHandle.hxx (renamed from src/decoder/FlacIOHandle.hxx) | 6 | ||||
-rw-r--r-- | src/decoder/plugins/FlacInput.cxx (renamed from src/decoder/FlacInput.cxx) | 18 | ||||
-rw-r--r-- | src/decoder/plugins/FlacInput.hxx (renamed from src/decoder/FlacInput.hxx) | 4 | ||||
-rw-r--r-- | src/decoder/plugins/FlacMetadata.cxx (renamed from src/decoder/FlacMetadata.cxx) | 152 | ||||
-rw-r--r-- | src/decoder/plugins/FlacMetadata.hxx (renamed from src/decoder/FlacMetadata.hxx) | 26 | ||||
-rw-r--r-- | src/decoder/plugins/FlacPcm.cxx (renamed from src/decoder/FlacPcm.cxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/FlacPcm.hxx (renamed from src/decoder/FlacPcm.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/FluidsynthDecoderPlugin.cxx (renamed from src/decoder/FluidsynthDecoderPlugin.cxx) | 16 | ||||
-rw-r--r-- | src/decoder/plugins/FluidsynthDecoderPlugin.hxx (renamed from src/decoder/FluidsynthDecoderPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/GmeDecoderPlugin.cxx (renamed from src/decoder/GmeDecoderPlugin.cxx) | 47 | ||||
-rw-r--r-- | src/decoder/plugins/GmeDecoderPlugin.hxx (renamed from src/decoder/GmeDecoderPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/MadDecoderPlugin.cxx (renamed from src/decoder/MadDecoderPlugin.cxx) | 325 | ||||
-rw-r--r-- | src/decoder/plugins/MadDecoderPlugin.hxx (renamed from src/decoder/MadDecoderPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/MikmodDecoderPlugin.cxx (renamed from src/decoder/MikmodDecoderPlugin.cxx) | 22 | ||||
-rw-r--r-- | src/decoder/plugins/MikmodDecoderPlugin.hxx (renamed from src/decoder/MikmodDecoderPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/ModplugDecoderPlugin.cxx (renamed from src/decoder/ModplugDecoderPlugin.cxx) | 48 | ||||
-rw-r--r-- | src/decoder/plugins/ModplugDecoderPlugin.hxx (renamed from src/decoder/ModplugDecoderPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/MpcdecDecoderPlugin.cxx (renamed from src/decoder/MpcdecDecoderPlugin.cxx) | 32 | ||||
-rw-r--r-- | src/decoder/plugins/MpcdecDecoderPlugin.hxx (renamed from src/decoder/MpcdecDecoderPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/Mpg123DecoderPlugin.cxx (renamed from src/decoder/Mpg123DecoderPlugin.cxx) | 151 | ||||
-rw-r--r-- | src/decoder/plugins/Mpg123DecoderPlugin.hxx (renamed from src/decoder/Mpg123DecoderPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/OggCodec.cxx (renamed from src/decoder/OggCodec.cxx) | 3 | ||||
-rw-r--r-- | src/decoder/plugins/OggCodec.hxx (renamed from src/decoder/OggCodec.hxx) | 5 | ||||
-rw-r--r-- | src/decoder/plugins/OggFind.cxx (renamed from src/decoder/OggFind.cxx) | 16 | ||||
-rw-r--r-- | src/decoder/plugins/OggFind.hxx (renamed from src/decoder/OggFind.hxx) | 8 | ||||
-rw-r--r-- | src/decoder/plugins/OggSyncState.hxx (renamed from src/decoder/OggSyncState.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/OggUtil.cxx (renamed from src/decoder/OggUtil.cxx) | 4 | ||||
-rw-r--r-- | src/decoder/plugins/OggUtil.hxx (renamed from src/decoder/OggUtil.hxx) | 4 | ||||
-rw-r--r-- | src/decoder/plugins/OpusDecoderPlugin.cxx (renamed from src/decoder/OpusDecoderPlugin.cxx) | 154 | ||||
-rw-r--r-- | src/decoder/plugins/OpusDecoderPlugin.h (renamed from src/decoder/OpusDecoderPlugin.h) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/OpusDomain.cxx (renamed from src/decoder/OpusDomain.cxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/OpusDomain.hxx (renamed from src/decoder/OpusDomain.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/OpusHead.cxx (renamed from src/decoder/OpusHead.cxx) | 3 | ||||
-rw-r--r-- | src/decoder/plugins/OpusHead.hxx (renamed from src/decoder/OpusHead.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/OpusReader.hxx (renamed from src/decoder/OpusReader.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/OpusTags.cxx (renamed from src/decoder/OpusTags.cxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/OpusTags.hxx (renamed from src/decoder/OpusTags.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/PcmDecoderPlugin.cxx (renamed from src/decoder/PcmDecoderPlugin.cxx) | 27 | ||||
-rw-r--r-- | src/decoder/plugins/PcmDecoderPlugin.hxx (renamed from src/decoder/PcmDecoderPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/SidplayDecoderPlugin.cxx (renamed from src/decoder/SidplayDecoderPlugin.cxx) | 70 | ||||
-rw-r--r-- | src/decoder/plugins/SidplayDecoderPlugin.hxx (renamed from src/decoder/SidplayDecoderPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/SndfileDecoderPlugin.cxx (renamed from src/decoder/SndfileDecoderPlugin.cxx) | 172 | ||||
-rw-r--r-- | src/decoder/plugins/SndfileDecoderPlugin.hxx (renamed from src/decoder/SndfileDecoderPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/VorbisComments.cxx (renamed from src/decoder/VorbisComments.cxx) | 55 | ||||
-rw-r--r-- | src/decoder/plugins/VorbisComments.hxx (renamed from src/decoder/VorbisComments.hxx) | 4 | ||||
-rw-r--r-- | src/decoder/plugins/VorbisDecoderPlugin.cxx (renamed from src/decoder/VorbisDecoderPlugin.cxx) | 125 | ||||
-rw-r--r-- | src/decoder/plugins/VorbisDecoderPlugin.h (renamed from src/decoder/VorbisDecoderPlugin.h) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/VorbisDomain.cxx (renamed from src/decoder/VorbisDomain.cxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/VorbisDomain.hxx (renamed from src/decoder/VorbisDomain.hxx) | 6 | ||||
-rw-r--r-- | src/decoder/plugins/WavpackDecoderPlugin.cxx (renamed from src/decoder/WavpackDecoderPlugin.cxx) | 178 | ||||
-rw-r--r-- | src/decoder/plugins/WavpackDecoderPlugin.hxx (renamed from src/decoder/WavpackDecoderPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/WildmidiDecoderPlugin.cxx (renamed from src/decoder/WildmidiDecoderPlugin.cxx) | 28 | ||||
-rw-r--r-- | src/decoder/plugins/WildmidiDecoderPlugin.hxx (renamed from src/decoder/WildmidiDecoderPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/decoder/plugins/XiphTags.cxx (renamed from src/decoder/XiphTags.cxx) | 7 | ||||
-rw-r--r-- | src/decoder/plugins/XiphTags.hxx (renamed from src/decoder/XiphTags.hxx) | 2 |
97 files changed, 4555 insertions, 1377 deletions
diff --git a/src/decoder/DecoderAPI.cxx b/src/decoder/DecoderAPI.cxx new file mode 100644 index 000000000..4794d60e7 --- /dev/null +++ b/src/decoder/DecoderAPI.cxx @@ -0,0 +1,641 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + * + * 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 Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DecoderAPI.hxx" +#include "DecoderError.hxx" +#include "pcm/PcmConvert.hxx" +#include "AudioConfig.hxx" +#include "ReplayGainConfig.hxx" +#include "MusicChunk.hxx" +#include "MusicBuffer.hxx" +#include "MusicPipe.hxx" +#include "DecoderControl.hxx" +#include "DecoderInternal.hxx" +#include "DetachedSong.hxx" +#include "input/InputStream.hxx" +#include "util/Error.hxx" +#include "util/ConstBuffer.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> +#include <math.h> + +void +decoder_initialized(Decoder &decoder, + const AudioFormat audio_format, + bool seekable, SignedSongTime duration) +{ + DecoderControl &dc = decoder.dc; + struct audio_format_string af_string; + + assert(dc.state == DecoderState::START); + assert(dc.pipe != nullptr); + assert(dc.pipe->IsEmpty()); + assert(decoder.convert == nullptr); + assert(decoder.stream_tag == nullptr); + assert(decoder.decoder_tag == nullptr); + assert(!decoder.seeking); + assert(audio_format.IsDefined()); + assert(audio_format.IsValid()); + + dc.in_audio_format = audio_format; + dc.out_audio_format = getOutputAudioFormat(audio_format); + + dc.seekable = seekable; + dc.total_time = duration; + + FormatDebug(decoder_domain, "audio_format=%s, seekable=%s", + audio_format_to_string(dc.in_audio_format, &af_string), + seekable ? "true" : "false"); + + if (dc.in_audio_format != dc.out_audio_format) { + FormatDebug(decoder_domain, "converting to %s", + audio_format_to_string(dc.out_audio_format, + &af_string)); + + decoder.convert = new PcmConvert(); + + Error error; + if (!decoder.convert->Open(dc.in_audio_format, + dc.out_audio_format, + error)) + decoder.error = std::move(error); + } + + dc.Lock(); + dc.state = DecoderState::DECODE; + dc.client_cond.signal(); + dc.Unlock(); +} + +/** + * Checks if we need an "initial seek". If so, then the initial seek + * is prepared, and the function returns true. + */ +gcc_pure +static bool +decoder_prepare_initial_seek(Decoder &decoder) +{ + const DecoderControl &dc = decoder.dc; + assert(dc.pipe != nullptr); + + if (dc.state != DecoderState::DECODE) + /* wait until the decoder has finished initialisation + (reading file headers etc.) before emitting the + virtual "SEEK" command */ + return false; + + if (decoder.initial_seek_running) + /* initial seek has already begun - override any other + command */ + return true; + + if (decoder.initial_seek_pending) { + if (!dc.seekable) { + /* seeking is not possible */ + decoder.initial_seek_pending = false; + return false; + } + + if (dc.command == DecoderCommand::NONE) { + /* begin initial seek */ + + decoder.initial_seek_pending = false; + decoder.initial_seek_running = true; + return true; + } + + /* skip initial seek when there's another command + (e.g. STOP) */ + + decoder.initial_seek_pending = false; + } + + return false; +} + +/** + * Returns the current decoder command. May return a "virtual" + * synthesized command, e.g. to seek to the beginning of the CUE + * track. + */ +gcc_pure +static DecoderCommand +decoder_get_virtual_command(Decoder &decoder) +{ + if (decoder.error.IsDefined()) + /* an error has occurred: stop the decoder plugin */ + return DecoderCommand::STOP; + + const DecoderControl &dc = decoder.dc; + assert(dc.pipe != nullptr); + + if (decoder_prepare_initial_seek(decoder)) + return DecoderCommand::SEEK; + + return dc.command; +} + +DecoderCommand +decoder_get_command(Decoder &decoder) +{ + return decoder_get_virtual_command(decoder); +} + +void +decoder_command_finished(Decoder &decoder) +{ + DecoderControl &dc = decoder.dc; + + dc.Lock(); + + assert(dc.command != DecoderCommand::NONE || + decoder.initial_seek_running); + assert(dc.command != DecoderCommand::SEEK || + decoder.initial_seek_running || + dc.seek_error || decoder.seeking); + assert(dc.pipe != nullptr); + + if (decoder.initial_seek_running) { + assert(!decoder.seeking); + assert(decoder.chunk == nullptr); + assert(dc.pipe->IsEmpty()); + + decoder.initial_seek_running = false; + decoder.timestamp = dc.start_time.ToDoubleS(); + dc.Unlock(); + return; + } + + if (decoder.seeking) { + decoder.seeking = false; + + /* delete frames from the old song position */ + + if (decoder.chunk != nullptr) { + dc.buffer->Return(decoder.chunk); + decoder.chunk = nullptr; + } + + dc.pipe->Clear(*dc.buffer); + + decoder.timestamp = dc.seek_time.ToDoubleS(); + } + + dc.command = DecoderCommand::NONE; + dc.client_cond.signal(); + dc.Unlock(); +} + +SongTime +decoder_seek_time(Decoder &decoder) +{ + const DecoderControl &dc = decoder.dc; + + assert(dc.pipe != nullptr); + + if (decoder.initial_seek_running) + return dc.start_time; + + assert(dc.command == DecoderCommand::SEEK); + + decoder.seeking = true; + + return dc.seek_time; +} + +uint64_t +decoder_seek_where_frame(Decoder &decoder) +{ + const DecoderControl &dc = decoder.dc; + + return decoder_seek_time(decoder).ToScale<uint64_t>(dc.in_audio_format.sample_rate); +} + +void decoder_seek_error(Decoder & decoder) +{ + DecoderControl &dc = decoder.dc; + + assert(dc.pipe != nullptr); + + if (decoder.initial_seek_running) { + /* d'oh, we can't seek to the sub-song start position, + what now? - no idea, ignoring the problem for now. */ + decoder.initial_seek_running = false; + return; + } + + assert(dc.command == DecoderCommand::SEEK); + + dc.seek_error = true; + decoder.seeking = false; + + decoder_command_finished(decoder); +} + +InputStream * +decoder_open_uri(Decoder &decoder, const char *uri, Error &error) +{ + assert(decoder.dc.state == DecoderState::START || + decoder.dc.state == DecoderState::DECODE); + + DecoderControl &dc = decoder.dc; + Mutex &mutex = dc.mutex; + Cond &cond = dc.cond; + + InputStream *is = InputStream::Open(uri, mutex, cond, error); + if (is == nullptr) + return nullptr; + + mutex.lock(); + while (true) { + is->Update(); + if (is->IsReady()) { + mutex.unlock(); + return is; + } + + if (dc.command == DecoderCommand::STOP) { + mutex.unlock(); + delete is; + return nullptr; + } + + cond.wait(mutex); + } +} + +/** + * Should be read operation be cancelled? That is the case when the + * player thread has sent a command such as "STOP". + */ +gcc_pure +static inline bool +decoder_check_cancel_read(const Decoder *decoder) +{ + if (decoder == nullptr) + return false; + + const DecoderControl &dc = decoder->dc; + if (dc.command == DecoderCommand::NONE) + return false; + + /* ignore the SEEK command during initialization, the plugin + should handle that after it has initialized successfully */ + if (dc.command == DecoderCommand::SEEK && + (dc.state == DecoderState::START || decoder->seeking)) + return false; + + return true; +} + +size_t +decoder_read(Decoder *decoder, + InputStream &is, + void *buffer, size_t length) +{ + /* XXX don't allow decoder==nullptr */ + + assert(decoder == nullptr || + decoder->dc.state == DecoderState::START || + decoder->dc.state == DecoderState::DECODE); + assert(buffer != nullptr); + + if (length == 0) + return 0; + + is.Lock(); + + while (true) { + if (decoder_check_cancel_read(decoder)) { + is.Unlock(); + return 0; + } + + if (is.IsAvailable()) + break; + + is.cond.wait(is.mutex); + } + + Error error; + size_t nbytes = is.Read(buffer, length, error); + assert(nbytes == 0 || !error.IsDefined()); + assert(nbytes > 0 || error.IsDefined() || is.IsEOF()); + + is.Unlock(); + + if (gcc_unlikely(nbytes == 0 && error.IsDefined())) + LogError(error); + + return nbytes; +} + +bool +decoder_read_full(Decoder *decoder, InputStream &is, + void *_buffer, size_t size) +{ + uint8_t *buffer = (uint8_t *)_buffer; + + while (size > 0) { + size_t nbytes = decoder_read(decoder, is, buffer, size); + if (nbytes == 0) + return false; + + buffer += nbytes; + size -= nbytes; + } + + return true; +} + +bool +decoder_skip(Decoder *decoder, InputStream &is, size_t size) +{ + while (size > 0) { + char buffer[1024]; + size_t nbytes = decoder_read(decoder, is, buffer, + std::min(sizeof(buffer), size)); + if (nbytes == 0) + return false; + + size -= nbytes; + } + + return true; +} + +void +decoder_timestamp(Decoder &decoder, double t) +{ + assert(t >= 0); + + decoder.timestamp = t; +} + +/** + * Sends a #tag as-is to the music pipe. Flushes the current chunk + * (decoder.chunk) if there is one. + */ +static DecoderCommand +do_send_tag(Decoder &decoder, const Tag &tag) +{ + MusicChunk *chunk; + + if (decoder.chunk != nullptr) { + /* there is a partial chunk - flush it, we want the + tag in a new chunk */ + decoder.FlushChunk(); + } + + assert(decoder.chunk == nullptr); + + chunk = decoder.GetChunk(); + if (chunk == nullptr) { + assert(decoder.dc.command != DecoderCommand::NONE); + return decoder.dc.command; + } + + chunk->tag = new Tag(tag); + return DecoderCommand::NONE; +} + +static bool +update_stream_tag(Decoder &decoder, InputStream *is) +{ + Tag *tag; + + tag = is != nullptr + ? is->LockReadTag() + : nullptr; + if (tag == nullptr) { + tag = decoder.song_tag; + if (tag == nullptr) + return false; + + /* no stream tag present - submit the song tag + instead */ + decoder.song_tag = nullptr; + } + + delete decoder.stream_tag; + decoder.stream_tag = tag; + return true; +} + +DecoderCommand +decoder_data(Decoder &decoder, + InputStream *is, + const void *data, size_t length, + uint16_t kbit_rate) +{ + DecoderControl &dc = decoder.dc; + DecoderCommand cmd; + + assert(dc.state == DecoderState::DECODE); + assert(dc.pipe != nullptr); + assert(length % dc.in_audio_format.GetFrameSize() == 0); + + dc.Lock(); + cmd = decoder_get_virtual_command(decoder); + dc.Unlock(); + + if (cmd == DecoderCommand::STOP || cmd == DecoderCommand::SEEK || + length == 0) + return cmd; + + assert(!decoder.initial_seek_pending); + assert(!decoder.initial_seek_running); + + /* send stream tags */ + + if (update_stream_tag(decoder, is)) { + if (decoder.decoder_tag != nullptr) { + /* merge with tag from decoder plugin */ + Tag *tag = Tag::Merge(*decoder.decoder_tag, + *decoder.stream_tag); + cmd = do_send_tag(decoder, *tag); + delete tag; + } else + /* send only the stream tag */ + cmd = do_send_tag(decoder, *decoder.stream_tag); + + if (cmd != DecoderCommand::NONE) + return cmd; + } + + if (decoder.convert != nullptr) { + assert(dc.in_audio_format != dc.out_audio_format); + + Error error; + auto result = decoder.convert->Convert({data, length}, + error); + if (data == nullptr) { + /* the PCM conversion has failed - stop + playback, since we have no better way to + bail out */ + LogError(error); + return DecoderCommand::STOP; + } + + data = result.data; + length = result.size; + } else { + assert(dc.in_audio_format == dc.out_audio_format); + } + + while (length > 0) { + MusicChunk *chunk; + bool full; + + chunk = decoder.GetChunk(); + if (chunk == nullptr) { + assert(dc.command != DecoderCommand::NONE); + return dc.command; + } + + const auto dest = + chunk->Write(dc.out_audio_format, + SongTime::FromS(decoder.timestamp) - + dc.song->GetStartTime(), + kbit_rate); + if (dest.IsEmpty()) { + /* the chunk is full, flush it */ + decoder.FlushChunk(); + continue; + } + + const size_t nbytes = std::min(dest.size, length); + + /* copy the buffer */ + + memcpy(dest.data, data, nbytes); + + /* expand the music pipe chunk */ + + full = chunk->Expand(dc.out_audio_format, nbytes); + if (full) { + /* the chunk is full, flush it */ + decoder.FlushChunk(); + } + + data = (const uint8_t *)data + nbytes; + length -= nbytes; + + decoder.timestamp += (double)nbytes / + dc.out_audio_format.GetTimeToSize(); + + if (dc.end_time.IsPositive() && + decoder.timestamp >= dc.end_time.ToDoubleS()) + /* the end of this range has been reached: + stop decoding */ + return DecoderCommand::STOP; + } + + return DecoderCommand::NONE; +} + +DecoderCommand +decoder_tag(Decoder &decoder, InputStream *is, + Tag &&tag) +{ + gcc_unused const DecoderControl &dc = decoder.dc; + DecoderCommand cmd; + + assert(dc.state == DecoderState::DECODE); + assert(dc.pipe != nullptr); + + /* save the tag */ + + delete decoder.decoder_tag; + decoder.decoder_tag = new Tag(tag); + + /* check for a new stream tag */ + + update_stream_tag(decoder, is); + + /* check if we're seeking */ + + if (decoder_prepare_initial_seek(decoder)) + /* during initial seek, no music chunk must be created + until seeking is finished; skip the rest of the + function here */ + return DecoderCommand::SEEK; + + /* send tag to music pipe */ + + if (decoder.stream_tag != nullptr) { + /* merge with tag from input stream */ + Tag *merged; + + merged = Tag::Merge(*decoder.stream_tag, + *decoder.decoder_tag); + cmd = do_send_tag(decoder, *merged); + delete merged; + } else + /* send only the decoder tag */ + cmd = do_send_tag(decoder, *decoder.decoder_tag); + + return cmd; +} + +void +decoder_replay_gain(Decoder &decoder, + const ReplayGainInfo *replay_gain_info) +{ + if (replay_gain_info != nullptr) { + static unsigned serial; + if (++serial == 0) + serial = 1; + + if (REPLAY_GAIN_OFF != replay_gain_mode) { + ReplayGainMode rgm = replay_gain_mode; + if (rgm != REPLAY_GAIN_ALBUM) + rgm = REPLAY_GAIN_TRACK; + + const auto &tuple = replay_gain_info->tuples[rgm]; + const auto scale = + tuple.CalculateScale(replay_gain_preamp, + replay_gain_missing_preamp, + replay_gain_limit); + decoder.dc.replay_gain_db = 20.0 * log10f(scale); + } + + decoder.replay_gain_info = *replay_gain_info; + decoder.replay_gain_serial = serial; + + if (decoder.chunk != nullptr) { + /* flush the current chunk because the new + replay gain values affect the following + samples */ + decoder.FlushChunk(); + } + } else + decoder.replay_gain_serial = 0; +} + +void +decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp) +{ + DecoderControl &dc = decoder.dc; + + dc.SetMixRamp(std::move(mix_ramp)); +} diff --git a/src/decoder/DecoderAPI.hxx b/src/decoder/DecoderAPI.hxx new file mode 100644 index 000000000..b756331d9 --- /dev/null +++ b/src/decoder/DecoderAPI.hxx @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + * + * 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 Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/*! \file + * \brief The MPD Decoder API + * + * This is the public API which is used by decoder plugins to + * communicate with the mpd core. + */ + +#ifndef MPD_DECODER_API_HXX +#define MPD_DECODER_API_HXX + +// IWYU pragma: begin_exports + +#include "check.h" +#include "DecoderCommand.hxx" +#include "DecoderPlugin.hxx" +#include "ReplayGainInfo.hxx" +#include "tag/Tag.hxx" +#include "AudioFormat.hxx" +#include "MixRampInfo.hxx" +#include "config/ConfigData.hxx" +#include "Chrono.hxx" + +// IWYU pragma: end_exports + +#include <stdint.h> + +class Error; + +/** + * Notify the player thread that it has finished initialization and + * that it has read the song's meta data. + * + * @param decoder the decoder object + * @param audio_format the audio format which is going to be sent to + * decoder_data() + * @param seekable true if the song is seekable + * @param duration the total duration of this song; negative if + * unknown + */ +void +decoder_initialized(Decoder &decoder, + AudioFormat audio_format, + bool seekable, SignedSongTime duration); + +/** + * Determines the pending decoder command. + * + * @param decoder the decoder object + * @return the current command, or DecoderCommand::NONE if there is no + * command pending + */ +gcc_pure +DecoderCommand +decoder_get_command(Decoder &decoder); + +/** + * Called by the decoder when it has performed the requested command + * (dc->command). This function resets dc->command and wakes up the + * player thread. + * + * @param decoder the decoder object + */ +void +decoder_command_finished(Decoder &decoder); + +/** + * Call this when you have received the DecoderCommand::SEEK command. + * + * @param decoder the decoder object + * @return the destination position for the seek in milliseconds + */ +gcc_pure +SongTime +decoder_seek_time(Decoder &decoder); + +/** + * Call this when you have received the DecoderCommand::SEEK command. + * + * @param decoder the decoder object + * @return the destination position for the seek in frames + */ +gcc_pure +uint64_t +decoder_seek_where_frame(Decoder &decoder); + +/** + * Call this instead of decoder_command_finished() when seeking has + * failed. + * + * @param decoder the decoder object + */ +void +decoder_seek_error(Decoder &decoder); + +/** + * Open a new #InputStream and wait until it's ready. Can get + * cancelled by DecoderCommand::STOP (returns nullptr without setting + * #Error). + */ +InputStream * +decoder_open_uri(Decoder &decoder, const char *uri, Error &error); + +/** + * Blocking read from the input stream. + * + * @param decoder the decoder object + * @param is the input stream to read from + * @param buffer the destination buffer + * @param length the maximum number of bytes to read + * @return the number of bytes read, or 0 if one of the following + * occurs: end of file; error; command (like SEEK or STOP). + */ +size_t +decoder_read(Decoder *decoder, InputStream &is, + void *buffer, size_t length); + +static inline size_t +decoder_read(Decoder &decoder, InputStream &is, + void *buffer, size_t length) +{ + return decoder_read(&decoder, is, buffer, length); +} + +/** + * Blocking read from the input stream. Attempts to fill the buffer + * completely; there is no partial result. + * + * @return true on success, false on error or command or not enough + * data + */ +bool +decoder_read_full(Decoder *decoder, InputStream &is, + void *buffer, size_t size); + +/** + * Skip data on the #InputStream. + * + * @return true on success, false on error or command + */ +bool +decoder_skip(Decoder *decoder, InputStream &is, size_t size); + +/** + * Sets the time stamp for the next data chunk [seconds]. The MPD + * core automatically counts it up, and a decoder plugin only needs to + * use this function if it thinks that adding to the time stamp based + * on the buffer size won't work. + */ +void +decoder_timestamp(Decoder &decoder, double t); + +/** + * This function is called by the decoder plugin when it has + * successfully decoded block of input data. + * + * @param decoder the decoder object + * @param is an input stream which is buffering while we are waiting + * for the player + * @param data the source buffer + * @param length the number of bytes in the buffer + * @return the current command, or DecoderCommand::NONE if there is no + * command pending + */ +DecoderCommand +decoder_data(Decoder &decoder, InputStream *is, + const void *data, size_t length, + uint16_t kbit_rate); + +static inline DecoderCommand +decoder_data(Decoder &decoder, InputStream &is, + const void *data, size_t length, + uint16_t kbit_rate) +{ + return decoder_data(decoder, &is, data, length, kbit_rate); +} + +/** + * This function is called by the decoder plugin when it has + * successfully decoded a tag. + * + * @param decoder the decoder object + * @param is an input stream which is buffering while we are waiting + * for the player + * @param tag the tag to send + * @return the current command, or DecoderCommand::NONE if there is no + * command pending + */ +DecoderCommand +decoder_tag(Decoder &decoder, InputStream *is, Tag &&tag); + +static inline DecoderCommand +decoder_tag(Decoder &decoder, InputStream &is, Tag &&tag) +{ + return decoder_tag(decoder, &is, std::move(tag)); +} + +/** + * Set replay gain values for the following chunks. + * + * @param decoder the decoder object + * @param rgi the replay_gain_info object; may be nullptr to invalidate + * the previous replay gain values + */ +void +decoder_replay_gain(Decoder &decoder, + const ReplayGainInfo *replay_gain_info); + +/** + * Store MixRamp tags. + * + * @param decoder the decoder object + * @param mixramp_start the mixramp_start tag; may be nullptr to invalidate + * @param mixramp_end the mixramp_end tag; may be nullptr to invalidate + */ +void +decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp); + +#endif diff --git a/src/decoder/DecoderBuffer.cxx b/src/decoder/DecoderBuffer.cxx new file mode 100644 index 000000000..a8958d6fd --- /dev/null +++ b/src/decoder/DecoderBuffer.cxx @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + * + * 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 Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DecoderBuffer.hxx" +#include "DecoderAPI.hxx" + +#include <assert.h> + +bool +DecoderBuffer::Fill() +{ + auto w = buffer.Write(); + if (w.IsEmpty()) + /* buffer is full */ + return false; + + size_t nbytes = decoder_read(decoder, is, + w.data, w.size); + if (nbytes == 0) + /* end of file, I/O error or decoder command + received */ + return false; + + buffer.Append(nbytes); + return true; +} + +ConstBuffer<void> +DecoderBuffer::Need(size_t min_size) +{ + while (true) { + const auto r = Read(); + if (r.size >= min_size) + return r; + + if (!Fill()) + return nullptr; + } +} + +bool +DecoderBuffer::Skip(size_t nbytes) +{ + const auto r = buffer.Read(); + if (r.size >= nbytes) { + buffer.Consume(nbytes); + return true; + } + + buffer.Clear(); + nbytes -= r.size; + + return decoder_skip(decoder, is, nbytes); +} diff --git a/src/decoder/DecoderBuffer.hxx b/src/decoder/DecoderBuffer.hxx new file mode 100644 index 000000000..9cf47d915 --- /dev/null +++ b/src/decoder/DecoderBuffer.hxx @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + * + * 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 Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_BUFFER_HXX +#define MPD_DECODER_BUFFER_HXX + +#include "Compiler.h" +#include "util/DynamicFifoBuffer.hxx" +#include "util/ConstBuffer.hxx" + +#include <stddef.h> + +struct Decoder; +class InputStream; + +/** + * This objects handles buffered reads in decoder plugins easily. You + * create a buffer object, and use its high-level methods to fill and + * read it. It will automatically handle shifting the buffer. + */ +class DecoderBuffer { + Decoder *const decoder; + InputStream &is; + + DynamicFifoBuffer<uint8_t> buffer; + +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), buffer(_size) {} + + const InputStream &GetStream() const { + return is; + } + + void Clear() { + buffer.Clear(); + } + + /** + * Read data from the #InputStream 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(); + + /** + * How many bytes are stored in the buffer? + */ + gcc_pure + size_t GetAvailable() const { + return buffer.GetAvailable(); + } + + /** + * Reads data from the buffer. This data is not yet consumed, + * you have to call Consume() to do that. The returned buffer + * becomes invalid after a Fill() or a Consume() call. + */ + ConstBuffer<void> Read() const { + auto r = buffer.Read(); + return { r.data, r.size }; + } + + /** + * Wait until this number of bytes are available. Returns nullptr on + * error. + */ + ConstBuffer<void> Need(size_t min_size); + + /** + * Consume (delete, invalidate) a part of the buffer. The + * "nbytes" parameter must not be larger than the length + * returned by Read(). + * + * @param nbytes the number of bytes to consume + */ + void Consume(size_t nbytes) { + buffer.Consume(nbytes); + } + + /** + * Skips the specified number of bytes, discarding its data. + * + * @param nbytes the number of bytes to skip + * @return true on success, false on error + */ + bool Skip(size_t nbytes); +}; + +#endif diff --git a/src/decoder/DecoderCommand.hxx b/src/decoder/DecoderCommand.hxx new file mode 100644 index 000000000..a00519644 --- /dev/null +++ b/src/decoder/DecoderCommand.hxx @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + * + * 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 Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_COMMAND_HXX +#define MPD_DECODER_COMMAND_HXX + +#include <stdint.h> + +enum class DecoderCommand : uint8_t { + NONE = 0, + START, + STOP, + SEEK +}; + +#endif diff --git a/src/decoder/DecoderControl.cxx b/src/decoder/DecoderControl.cxx new file mode 100644 index 000000000..c30da6214 --- /dev/null +++ b/src/decoder/DecoderControl.cxx @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + * + * 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 Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DecoderControl.hxx" +#include "MusicPipe.hxx" +#include "DetachedSong.hxx" + +#include <assert.h> + +DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond) + :mutex(_mutex), client_cond(_client_cond), + state(DecoderState::STOP), + command(DecoderCommand::NONE), + client_is_waiting(false), + song(nullptr), + replay_gain_db(0), replay_gain_prev_db(0) {} + +DecoderControl::~DecoderControl() +{ + ClearError(); + + delete song; +} + +void +DecoderControl::WaitForDecoder() +{ + assert(!client_is_waiting); + client_is_waiting = true; + + client_cond.wait(mutex); + + assert(client_is_waiting); + client_is_waiting = false; +} + +bool +DecoderControl::IsCurrentSong(const DetachedSong &_song) const +{ + switch (state) { + case DecoderState::STOP: + case DecoderState::ERROR: + return false; + + case DecoderState::START: + case DecoderState::DECODE: + return song->IsSame(_song); + } + + assert(false); + gcc_unreachable(); +} + +void +DecoderControl::Start(DetachedSong *_song, + SongTime _start_time, SongTime _end_time, + MusicBuffer &_buffer, MusicPipe &_pipe) +{ + assert(_song != nullptr); + assert(_pipe.IsEmpty()); + + delete song; + song = _song; + start_time = _start_time; + end_time = _end_time; + buffer = &_buffer; + pipe = &_pipe; + + LockSynchronousCommand(DecoderCommand::START); +} + +void +DecoderControl::Stop() +{ + Lock(); + + if (command != DecoderCommand::NONE) + /* Attempt to cancel the current command. If it's too + late and the decoder thread is already executing + the old command, we'll call STOP again in this + function (see below). */ + SynchronousCommandLocked(DecoderCommand::STOP); + + if (state != DecoderState::STOP && state != DecoderState::ERROR) + SynchronousCommandLocked(DecoderCommand::STOP); + + Unlock(); +} + +bool +DecoderControl::Seek(SongTime t) +{ + assert(state != DecoderState::START); + + if (state == DecoderState::STOP || + state == DecoderState::ERROR || !seekable) + return false; + + seek_time = t; + seek_error = false; + LockSynchronousCommand(DecoderCommand::SEEK); + + return !seek_error; +} + +void +DecoderControl::Quit() +{ + assert(thread.IsDefined()); + + quit = true; + LockAsynchronousCommand(DecoderCommand::STOP); + + thread.Join(); +} + +void +DecoderControl::CycleMixRamp() +{ + previous_mix_ramp = std::move(mix_ramp); + mix_ramp.Clear(); +} diff --git a/src/decoder/DecoderControl.hxx b/src/decoder/DecoderControl.hxx new file mode 100644 index 000000000..ed2b8c538 --- /dev/null +++ b/src/decoder/DecoderControl.hxx @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + * + * 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 Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_CONTROL_HXX +#define MPD_DECODER_CONTROL_HXX + +#include "DecoderCommand.hxx" +#include "AudioFormat.hxx" +#include "MixRampInfo.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "thread/Thread.hxx" +#include "Chrono.hxx" +#include "util/Error.hxx" + +#include <assert.h> +#include <stdint.h> + +/* damn you, windows.h! */ +#ifdef ERROR +#undef ERROR +#endif + +class DetachedSong; +class MusicBuffer; +class MusicPipe; + +enum class DecoderState : uint8_t { + STOP = 0, + START, + DECODE, + + /** + * The last "START" command failed, because there was an I/O + * error or because no decoder was able to decode the file. + * This state will only come after START; once the state has + * turned to DECODE, by definition no such error can occur. + */ + ERROR, +}; + +struct DecoderControl { + /** + * The handle of the decoder thread. + */ + Thread thread; + + /** + * This lock protects #state and #command. + * + * This is usually a reference to PlayerControl::mutex, so + * that both player thread and decoder thread share a mutex. + * This simplifies synchronization with #cond and + * #client_cond. + */ + Mutex &mutex; + + /** + * Trigger this object after you have modified #command. This + * is also used by the decoder thread to notify the caller + * when it has finished a command. + */ + Cond cond; + + /** + * The trigger of this object's client. It is signalled + * whenever an event occurs. + * + * This is usually a reference to PlayerControl::cond. + */ + Cond &client_cond; + + DecoderState state; + DecoderCommand command; + + /** + * The error that occurred in the decoder thread. This + * attribute is only valid if #state is #DecoderState::ERROR. + * The object must be freed when this object transitions to + * any other state (usually #DecoderState::START). + */ + Error error; + + bool quit; + + /** + * Is the client currently waiting for the DecoderThread? If + * false, the DecoderThread may omit invoking Cond::signal(), + * reducing the number of system calls. + */ + bool client_is_waiting; + + bool seek_error; + bool seekable; + SongTime seek_time; + + /** the format of the song file */ + AudioFormat in_audio_format; + + /** the format being sent to the music pipe */ + AudioFormat out_audio_format; + + /** + * The song currently being decoded. This attribute is set by + * the player thread, when it sends the #DecoderCommand::START + * command. + * + * This is a duplicate, and must be freed when this attribute + * is cleared. + */ + DetachedSong *song; + + /** + * The initial seek position, e.g. to the start of a sub-track + * described by a CUE file. + * + * This attribute is set by Start(). + */ + SongTime start_time; + + /** + * The decoder will stop when it reaches this position. 0 + * means don't stop before the end of the file. + * + * This attribute is set by Start(). + */ + SongTime end_time; + + SignedSongTime total_time; + + /** the #MusicChunk allocator */ + MusicBuffer *buffer; + + /** + * The destination pipe for decoded chunks. The caller thread + * owns this object, and is responsible for freeing it. + */ + MusicPipe *pipe; + + float replay_gain_db; + float replay_gain_prev_db; + + MixRampInfo mix_ramp, previous_mix_ramp; + + /** + * @param _mutex see #mutex + * @param _client_cond see #client_cond + */ + DecoderControl(Mutex &_mutex, Cond &_client_cond); + ~DecoderControl(); + + /** + * Locks the object. + */ + void Lock() const { + mutex.lock(); + } + + /** + * Unlocks the object. + */ + void Unlock() const { + mutex.unlock(); + } + + /** + * Signals the object. This function is only valid in the + * player thread. The object should be locked prior to + * calling this function. + */ + void Signal() { + cond.signal(); + } + + /** + * Waits for a signal on the #DecoderControl object. This function + * is only valid in the decoder thread. The object must be locked + * prior to calling this function. + */ + void Wait() { + cond.wait(mutex); + } + + /** + * Waits for a signal from the decoder thread. This object + * must be locked prior to calling this function. This method + * is only valid in the player thread. + * + * Caller must hold the lock. + */ + void WaitForDecoder(); + + bool IsIdle() const { + return state == DecoderState::STOP || + state == DecoderState::ERROR; + } + + gcc_pure + bool LockIsIdle() const { + Lock(); + bool result = IsIdle(); + Unlock(); + return result; + } + + bool IsStarting() const { + return state == DecoderState::START; + } + + gcc_pure + bool LockIsStarting() const { + Lock(); + bool result = IsStarting(); + Unlock(); + return result; + } + + bool HasFailed() const { + assert(command == DecoderCommand::NONE); + + return state == DecoderState::ERROR; + } + + gcc_pure + bool LockHasFailed() const { + Lock(); + bool result = HasFailed(); + Unlock(); + return result; + } + + /** + * Checks whether an error has occurred, and if so, returns a + * copy of the #Error object. + * + * Caller must lock the object. + */ + gcc_pure + Error GetError() const { + assert(command == DecoderCommand::NONE); + assert(state != DecoderState::ERROR || error.IsDefined()); + + Error result; + if (state == DecoderState::ERROR) + result.Set(error); + return result; + } + + /** + * Like GetError(), but locks and unlocks the object. + */ + gcc_pure + Error LockGetError() const { + Lock(); + Error result = GetError(); + Unlock(); + return result; + } + + /** + * Clear the error condition and free the #Error object (if any). + * + * Caller must lock the object. + */ + void ClearError() { + if (state == DecoderState::ERROR) { + error.Clear(); + state = DecoderState::STOP; + } + } + + /** + * Check if the specified song is currently being decoded. If the + * decoder is not running currently (or being started), then this + * function returns false in any case. + * + * Caller must lock the object. + */ + gcc_pure + bool IsCurrentSong(const DetachedSong &_song) const; + + gcc_pure + bool LockIsCurrentSong(const DetachedSong &_song) const { + Lock(); + const bool result = IsCurrentSong(_song); + Unlock(); + return result; + } + +private: + /** + * Wait for the command to be finished by the decoder thread. + * + * To be called from the client thread. Caller must lock the + * object. + */ + void WaitCommandLocked() { + while (command != DecoderCommand::NONE) + WaitForDecoder(); + } + + /** + * Send a command to the decoder thread and synchronously wait + * for it to finish. + * + * To be called from the client thread. Caller must lock the + * object. + */ + void SynchronousCommandLocked(DecoderCommand cmd) { + command = cmd; + Signal(); + WaitCommandLocked(); + } + + /** + * Send a command to the decoder thread and synchronously wait + * for it to finish. + * + * To be called from the client thread. This method locks the + * object. + */ + void LockSynchronousCommand(DecoderCommand cmd) { + Lock(); + ClearError(); + SynchronousCommandLocked(cmd); + Unlock(); + } + + void LockAsynchronousCommand(DecoderCommand cmd) { + Lock(); + command = cmd; + Signal(); + Unlock(); + } + +public: + /** + * Start the decoder. + * + * @param song the song to be decoded; the given instance will be + * owned and freed by the decoder + * @param start_time see #DecoderControl + * @param end_time see #DecoderControl + * @param pipe the pipe which receives the decoded chunks (owned by + * the caller) + */ + void Start(DetachedSong *song, SongTime start_time, SongTime end_time, + MusicBuffer &buffer, MusicPipe &pipe); + + void Stop(); + + bool Seek(SongTime t); + + void Quit(); + + const char *GetMixRampStart() const { + return mix_ramp.GetStart(); + } + + const char *GetMixRampEnd() const { + return mix_ramp.GetEnd(); + } + + const char *GetMixRampPreviousEnd() const { + return previous_mix_ramp.GetEnd(); + } + + void SetMixRamp(MixRampInfo &&new_value) { + mix_ramp = std::move(new_value); + } + + /** + * Move mixramp_end to mixramp_prev_end and clear + * mixramp_start/mixramp_end. + */ + void CycleMixRamp(); +}; + +#endif diff --git a/src/decoder/DecoderError.cxx b/src/decoder/DecoderError.cxx new file mode 100644 index 000000000..bd3842837 --- /dev/null +++ b/src/decoder/DecoderError.cxx @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + * + * 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 Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "DecoderError.hxx" +#include "util/Domain.hxx" + +const Domain decoder_domain("decoder"); diff --git a/src/decoder/DecoderError.hxx b/src/decoder/DecoderError.hxx new file mode 100644 index 000000000..83cf98204 --- /dev/null +++ b/src/decoder/DecoderError.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + * + * 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 Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_ERROR_HXX +#define MPD_DECODER_ERROR_HXX + +extern const class Domain decoder_domain; + +#endif diff --git a/src/decoder/DecoderInternal.cxx b/src/decoder/DecoderInternal.cxx new file mode 100644 index 000000000..f35878682 --- /dev/null +++ b/src/decoder/DecoderInternal.cxx @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + * + * 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 Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DecoderInternal.hxx" +#include "DecoderControl.hxx" +#include "pcm/PcmConvert.hxx" +#include "MusicPipe.hxx" +#include "MusicBuffer.hxx" +#include "MusicChunk.hxx" +#include "tag/Tag.hxx" + +#include <assert.h> + +Decoder::~Decoder() +{ + /* caller must flush the chunk */ + assert(chunk == nullptr); + + if (convert != nullptr) { + convert->Close(); + delete convert; + } + + delete song_tag; + delete stream_tag; + delete decoder_tag; +} + +/** + * All chunks are full of decoded data; wait for the player to free + * one. + */ +static DecoderCommand +need_chunks(DecoderControl &dc) +{ + if (dc.command == DecoderCommand::NONE) + dc.Wait(); + + return dc.command; +} + +MusicChunk * +Decoder::GetChunk() +{ + DecoderCommand cmd; + + if (chunk != nullptr) + return chunk; + + do { + chunk = dc.buffer->Allocate(); + if (chunk != nullptr) { + chunk->replay_gain_serial = replay_gain_serial; + if (replay_gain_serial != 0) + chunk->replay_gain_info = replay_gain_info; + + return chunk; + } + + dc.Lock(); + cmd = need_chunks(dc); + dc.Unlock(); + } while (cmd == DecoderCommand::NONE); + + return nullptr; +} + +void +Decoder::FlushChunk() +{ + assert(!seeking); + assert(!initial_seek_running); + assert(!initial_seek_pending); + assert(chunk != nullptr); + + if (chunk->IsEmpty()) + dc.buffer->Return(chunk); + else + dc.pipe->Push(chunk); + + chunk = nullptr; + + dc.Lock(); + if (dc.client_is_waiting) + dc.client_cond.signal(); + dc.Unlock(); +} diff --git a/src/decoder/DecoderInternal.hxx b/src/decoder/DecoderInternal.hxx new file mode 100644 index 000000000..24b665e85 --- /dev/null +++ b/src/decoder/DecoderInternal.hxx @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + * + * 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 Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_INTERNAL_HXX +#define MPD_DECODER_INTERNAL_HXX + +#include "ReplayGainInfo.hxx" +#include "util/Error.hxx" + +class PcmConvert; +struct MusicChunk; +struct DecoderControl; +struct Tag; + +struct Decoder { + DecoderControl &dc; + + /** + * For converting input data to the configured audio format. + * nullptr means no conversion necessary. + */ + PcmConvert *convert; + + /** + * The time stamp of the next data chunk, in seconds. + */ + double timestamp; + + /** + * Is the initial seek (to the start position of the sub-song) + * pending, or has it been performed already? + */ + bool initial_seek_pending; + + /** + * Is the initial seek currently running? During this time, + * the decoder command is SEEK. This flag is set by + * decoder_get_virtual_command(), when the virtual SEEK + * command is generated for the first time. + */ + bool initial_seek_running; + + /** + * This flag is set by decoder_seek_time(), and checked by + * decoder_command_finished(). It is used to clean up after + * seeking. + */ + bool seeking; + + /** + * The tag from the song object. This is only used for local + * files, because we expect the stream server to send us a new + * tag each time we play it. + */ + Tag *song_tag; + + /** the last tag received from the stream */ + Tag *stream_tag; + + /** the last tag received from the decoder plugin */ + Tag *decoder_tag; + + /** the chunk currently being written to */ + MusicChunk *chunk; + + ReplayGainInfo replay_gain_info; + + /** + * A positive serial number for checking if replay gain info + * has changed since the last check. + */ + unsigned replay_gain_serial; + + /** + * An error has occurred (in DecoderAPI.cxx), and the plugin + * will be asked to stop. + */ + Error error; + + Decoder(DecoderControl &_dc, bool _initial_seek_pending, Tag *_tag) + :dc(_dc), + convert(nullptr), + timestamp(0), + initial_seek_pending(_initial_seek_pending), + initial_seek_running(false), + seeking(false), + song_tag(_tag), stream_tag(nullptr), decoder_tag(nullptr), + chunk(nullptr), + replay_gain_serial(0) { + } + + ~Decoder(); + + /** + * Returns the current chunk the decoder writes to, or allocates a new + * chunk if there is none. + * + * @return the chunk, or NULL if we have received a decoder command + */ + MusicChunk *GetChunk(); + + /** + * Flushes the current chunk. + * + * Caller must not lock the #DecoderControl object. + */ + void FlushChunk(); +}; + +#endif diff --git a/src/decoder/DecoderList.cxx b/src/decoder/DecoderList.cxx new file mode 100644 index 000000000..cd6881ce2 --- /dev/null +++ b/src/decoder/DecoderList.cxx @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + * + * 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 Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DecoderList.hxx" +#include "DecoderPlugin.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigData.hxx" +#include "plugins/AudiofileDecoderPlugin.hxx" +#include "plugins/PcmDecoderPlugin.hxx" +#include "plugins/DsdiffDecoderPlugin.hxx" +#include "plugins/DsfDecoderPlugin.hxx" +#include "plugins/FlacDecoderPlugin.h" +#include "plugins/OpusDecoderPlugin.h" +#include "plugins/VorbisDecoderPlugin.h" +#include "plugins/AdPlugDecoderPlugin.h" +#include "plugins/WavpackDecoderPlugin.hxx" +#include "plugins/FfmpegDecoderPlugin.hxx" +#include "plugins/GmeDecoderPlugin.hxx" +#include "plugins/FaadDecoderPlugin.hxx" +#include "plugins/MadDecoderPlugin.hxx" +#include "plugins/SndfileDecoderPlugin.hxx" +#include "plugins/Mpg123DecoderPlugin.hxx" +#include "plugins/WildmidiDecoderPlugin.hxx" +#include "plugins/MikmodDecoderPlugin.hxx" +#include "plugins/ModplugDecoderPlugin.hxx" +#include "plugins/MpcdecDecoderPlugin.hxx" +#include "plugins/FluidsynthDecoderPlugin.hxx" +#include "plugins/SidplayDecoderPlugin.hxx" +#include "util/Macros.hxx" + +#include <string.h> + +const struct DecoderPlugin *const decoder_plugins[] = { +#ifdef HAVE_MAD + &mad_decoder_plugin, +#endif +#ifdef HAVE_MPG123 + &mpg123_decoder_plugin, +#endif +#ifdef ENABLE_VORBIS_DECODER + &vorbis_decoder_plugin, +#endif +#if defined(HAVE_FLAC) + &oggflac_decoder_plugin, +#endif +#ifdef HAVE_FLAC + &flac_decoder_plugin, +#endif +#ifdef HAVE_OPUS + &opus_decoder_plugin, +#endif +#ifdef ENABLE_SNDFILE + &sndfile_decoder_plugin, +#endif +#ifdef HAVE_AUDIOFILE + &audiofile_decoder_plugin, +#endif +#ifdef ENABLE_DSD + &dsdiff_decoder_plugin, + &dsf_decoder_plugin, +#endif +#ifdef HAVE_FAAD + &faad_decoder_plugin, +#endif +#ifdef HAVE_MPCDEC + &mpcdec_decoder_plugin, +#endif +#ifdef HAVE_WAVPACK + &wavpack_decoder_plugin, +#endif +#ifdef HAVE_MODPLUG + &modplug_decoder_plugin, +#endif +#ifdef ENABLE_MIKMOD_DECODER + &mikmod_decoder_plugin, +#endif +#ifdef ENABLE_SIDPLAY + &sidplay_decoder_plugin, +#endif +#ifdef ENABLE_WILDMIDI + &wildmidi_decoder_plugin, +#endif +#ifdef ENABLE_FLUIDSYNTH + &fluidsynth_decoder_plugin, +#endif +#ifdef HAVE_ADPLUG + &adplug_decoder_plugin, +#endif +#ifdef HAVE_FFMPEG + &ffmpeg_decoder_plugin, +#endif +#ifdef HAVE_GME + &gme_decoder_plugin, +#endif + &pcm_decoder_plugin, + nullptr +}; + +static constexpr unsigned num_decoder_plugins = + ARRAY_SIZE(decoder_plugins) - 1; + +/** which plugins have been initialized successfully? */ +bool decoder_plugins_enabled[num_decoder_plugins]; + +const struct DecoderPlugin * +decoder_plugin_from_name(const char *name) +{ + return decoder_plugins_find([=](const DecoderPlugin &plugin){ + return strcmp(plugin.name, name) == 0; + }); +} + +void decoder_plugin_init_all(void) +{ + struct config_param empty; + + for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) { + const DecoderPlugin &plugin = *decoder_plugins[i]; + const struct config_param *param = + config_find_block(CONF_DECODER, "plugin", plugin.name); + + if (param == nullptr) + param = ∅ + else if (!param->GetBlockValue("enabled", true)) + /* the plugin is disabled in mpd.conf */ + continue; + + if (plugin.Init(*param)) + decoder_plugins_enabled[i] = true; + } +} + +void decoder_plugin_deinit_all(void) +{ + decoder_plugins_for_each_enabled([=](const DecoderPlugin &plugin){ + plugin.Finish(); + }); +} + +bool +decoder_plugins_supports_suffix(const char *suffix) +{ + return decoder_plugins_try([suffix](const DecoderPlugin &plugin){ + return plugin.SupportsSuffix(suffix); + }); +} diff --git a/src/decoder/DecoderList.hxx b/src/decoder/DecoderList.hxx new file mode 100644 index 000000000..47085d4ae --- /dev/null +++ b/src/decoder/DecoderList.hxx @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + * + * 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 Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_LIST_HXX +#define MPD_DECODER_LIST_HXX + +#include "Compiler.h" + +struct DecoderPlugin; + +extern const struct DecoderPlugin *const decoder_plugins[]; +extern bool decoder_plugins_enabled[]; + +/* interface for using plugins */ + +gcc_pure +const struct DecoderPlugin * +decoder_plugin_from_name(const char *name); + +/* this is where we "load" all the "plugins" ;-) */ +void decoder_plugin_init_all(void); + +/* this is where we "unload" all the "plugins" */ +void decoder_plugin_deinit_all(void); + +template<typename F> +static inline const DecoderPlugin * +decoder_plugins_find(F f) +{ + for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) + if (decoder_plugins_enabled[i] && f(*decoder_plugins[i])) + return decoder_plugins[i]; + + return nullptr; +} + +template<typename F> +static inline bool +decoder_plugins_try(F f) +{ + for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) + if (decoder_plugins_enabled[i] && f(*decoder_plugins[i])) + return true; + + return false; +} + +template<typename F> +static inline void +decoder_plugins_for_each(F f) +{ + for (auto i = decoder_plugins; *i != nullptr; ++i) + f(**i); +} + +template<typename F> +static inline void +decoder_plugins_for_each_enabled(F f) +{ + for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) + if (decoder_plugins_enabled[i]) + f(*decoder_plugins[i]); +} + +/** + * Is there at least once #DecoderPlugin that supports the specified + * file name suffix? + */ +gcc_pure gcc_nonnull_all +bool +decoder_plugins_supports_suffix(const char *suffix); + +#endif diff --git a/src/decoder/DecoderPlugin.cxx b/src/decoder/DecoderPlugin.cxx new file mode 100644 index 000000000..a0722c348 --- /dev/null +++ b/src/decoder/DecoderPlugin.cxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + * + * 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 Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DecoderPlugin.hxx" +#include "util/StringUtil.hxx" + +#include <assert.h> + +bool +DecoderPlugin::SupportsSuffix(const char *suffix) const +{ +#if !CLANG_CHECK_VERSION(3,6) + /* disabled on clang due to -Wtautological-pointer-compare */ + assert(suffix != nullptr); +#endif + + return suffixes != nullptr && string_array_contains(suffixes, suffix); + +} + +bool +DecoderPlugin::SupportsMimeType(const char *mime_type) const +{ +#if !CLANG_CHECK_VERSION(3,6) + /* disabled on clang due to -Wtautological-pointer-compare */ + assert(mime_type != nullptr); +#endif + + return mime_types != nullptr && + string_array_contains(mime_types, mime_type); +} diff --git a/src/decoder/DecoderPlugin.hxx b/src/decoder/DecoderPlugin.hxx new file mode 100644 index 000000000..dbf3db9aa --- /dev/null +++ b/src/decoder/DecoderPlugin.hxx @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + * + * 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 Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_PLUGIN_HXX +#define MPD_DECODER_PLUGIN_HXX + +#include "Compiler.h" + +struct config_param; +class InputStream; +struct tag_handler; +class Path; + +/** + * Opaque handle which the decoder plugin passes to the functions in + * this header. + */ +struct Decoder; + +struct DecoderPlugin { + const char *name; + + /** + * Initialize the decoder plugin. Optional method. + * + * @param param a configuration block for this plugin, or nullptr + * if none is configured + * @return true if the plugin was initialized successfully, + * false if the plugin is not available + */ + bool (*init)(const config_param ¶m); + + /** + * Deinitialize a decoder plugin which was initialized + * successfully. Optional method. + */ + void (*finish)(void); + + /** + * Decode a stream (data read from an #input_stream object). + * + * Either implement this method or file_decode(). If + * possible, it is recommended to implement this method, + * because it is more versatile. + */ + void (*stream_decode)(Decoder &decoder, InputStream &is); + + /** + * Decode a local file. + * + * Either implement this method or stream_decode(). + */ + void (*file_decode)(Decoder &decoder, Path path_fs); + + /** + * Scan metadata of a file. + * + * @return false if the operation has failed + */ + bool (*scan_file)(Path path_fs, + const struct tag_handler *handler, + void *handler_ctx); + + /** + * Scan metadata of a file. + * + * @return false if the operation has failed + */ + bool (*scan_stream)(InputStream &is, + const struct tag_handler *handler, + void *handler_ctx); + + /** + * @brief Return a "virtual" filename for subtracks in + * container formats like flac + * @param const char* pathname full pathname for the file on fs + * @param const unsigned int tnum track number + * + * @return nullptr if there are no multiple files + * a filename for every single track according to tnum (param 2) + * do not include full pathname here, just the "virtual" file + * + * Free the return value with delete[]. + */ + char* (*container_scan)(Path path_fs, const unsigned int tnum); + + /* last element in these arrays must always be a nullptr: */ + const char *const*suffixes; + const char *const*mime_types; + + /** + * Initialize a decoder plugin. + * + * @param param a configuration block for this plugin, or nullptr if none + * is configured + * @return true if the plugin was initialized successfully, false if + * the plugin is not available + */ + bool Init(const config_param ¶m) const { + return init != nullptr + ? init(param) + : true; + } + + /** + * Deinitialize a decoder plugin which was initialized successfully. + */ + void Finish() const { + if (finish != nullptr) + finish(); + } + + /** + * Decode a stream. + */ + void StreamDecode(Decoder &decoder, InputStream &is) const { + stream_decode(decoder, is); + } + + /** + * Decode a file. + */ + template<typename P> + void FileDecode(Decoder &decoder, P path_fs) const { + file_decode(decoder, path_fs); + } + + /** + * Read the tag of a file. + */ + template<typename P> + bool ScanFile(P path_fs, + const tag_handler &handler, void *handler_ctx) const { + return scan_file != nullptr + ? scan_file(path_fs, &handler, handler_ctx) + : false; + } + + /** + * Read the tag of a stream. + */ + bool ScanStream(InputStream &is, + const tag_handler &handler, void *handler_ctx) const { + return scan_stream != nullptr + ? scan_stream(is, &handler, handler_ctx) + : false; + } + + /** + * return "virtual" tracks in a container + */ + template<typename P> + char *ContainerScan(P path, const unsigned int tnum) const { + return container_scan(path, tnum); + } + + /** + * Does the plugin announce the specified file name suffix? + */ + gcc_pure gcc_nonnull_all + bool SupportsSuffix(const char *suffix) const; + + /** + * Does the plugin announce the specified MIME type? + */ + gcc_pure gcc_nonnull_all + bool SupportsMimeType(const char *mime_type) const; +}; + +#endif diff --git a/src/decoder/DecoderPrint.cxx b/src/decoder/DecoderPrint.cxx new file mode 100644 index 000000000..54b89c36c --- /dev/null +++ b/src/decoder/DecoderPrint.cxx @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + * + * 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 Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DecoderPrint.hxx" +#include "DecoderList.hxx" +#include "DecoderPlugin.hxx" +#include "client/Client.hxx" + +#include <functional> + +#include <assert.h> + +static void +decoder_plugin_print(Client &client, + const DecoderPlugin &plugin) +{ + const char *const*p; + + assert(plugin.name != nullptr); + + client_printf(client, "plugin: %s\n", plugin.name); + + if (plugin.suffixes != nullptr) + for (p = plugin.suffixes; *p != nullptr; ++p) + client_printf(client, "suffix: %s\n", *p); + + if (plugin.mime_types != nullptr) + for (p = plugin.mime_types; *p != nullptr; ++p) + client_printf(client, "mime_type: %s\n", *p); +} + +void +decoder_list_print(Client &client) +{ + using namespace std::placeholders; + const auto f = std::bind(decoder_plugin_print, std::ref(client), _1); + decoder_plugins_for_each_enabled(f); +} diff --git a/src/decoder/DecoderPrint.hxx b/src/decoder/DecoderPrint.hxx new file mode 100644 index 000000000..695bd099d --- /dev/null +++ b/src/decoder/DecoderPrint.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + * + * 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 Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_PRINT_HXX +#define MPD_DECODER_PRINT_HXX + +class Client; + +void +decoder_list_print(Client &client); + +#endif diff --git a/src/decoder/DecoderThread.cxx b/src/decoder/DecoderThread.cxx new file mode 100644 index 000000000..dd5518b98 --- /dev/null +++ b/src/decoder/DecoderThread.cxx @@ -0,0 +1,509 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + * + * 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 Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DecoderThread.hxx" +#include "DecoderControl.hxx" +#include "DecoderInternal.hxx" +#include "DecoderError.hxx" +#include "DecoderPlugin.hxx" +#include "DetachedSong.hxx" +#include "system/FatalError.hxx" +#include "MusicPipe.hxx" +#include "fs/Traits.hxx" +#include "fs/AllocatedPath.hxx" +#include "DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "input/LocalOpen.hxx" +#include "DecoderList.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "thread/Name.hxx" +#include "tag/ApeReplayGain.hxx" +#include "Log.hxx" + +#include <functional> + +static constexpr Domain decoder_thread_domain("decoder_thread"); + +/** + * Marks the current decoder command as "finished" and notifies the + * player thread. + * + * @param dc the #DecoderControl object; must be locked + */ +static void +decoder_command_finished_locked(DecoderControl &dc) +{ + assert(dc.command != DecoderCommand::NONE); + + dc.command = DecoderCommand::NONE; + + dc.client_cond.signal(); +} + +/** + * Opens the input stream with input_stream::Open(), and waits until + * the stream gets ready. If a decoder STOP command is received + * during that, it cancels the operation (but does not close the + * stream). + * + * Unlock the decoder before calling this function. + * + * @return an input_stream on success or if #DecoderCommand::STOP is + * received, nullptr on error + */ +static InputStream * +decoder_input_stream_open(DecoderControl &dc, const char *uri) +{ + Error error; + + InputStream *is = InputStream::Open(uri, dc.mutex, dc.cond, error); + if (is == nullptr) { + if (error.IsDefined()) + LogError(error); + + return nullptr; + } + + /* wait for the input stream to become ready; its metadata + will be available then */ + + dc.Lock(); + + is->Update(); + while (!is->IsReady() && + dc.command != DecoderCommand::STOP) { + dc.Wait(); + + is->Update(); + } + + if (!is->Check(error)) { + dc.Unlock(); + + LogError(error); + return nullptr; + } + + dc.Unlock(); + + return is; +} + +static InputStream * +decoder_input_stream_open(DecoderControl &dc, Path path) +{ + Error error; + + InputStream *is = OpenLocalInputStream(path, dc.mutex, dc.cond, error); + if (is == nullptr) { + LogError(error); + return nullptr; + } + + assert(is->IsReady()); + + return is; +} + +static bool +decoder_stream_decode(const DecoderPlugin &plugin, + Decoder &decoder, + InputStream &input_stream) +{ + assert(plugin.stream_decode != nullptr); + assert(decoder.stream_tag == nullptr); + assert(decoder.decoder_tag == nullptr); + assert(input_stream.IsReady()); + assert(decoder.dc.state == DecoderState::START); + + FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name); + + if (decoder.dc.command == DecoderCommand::STOP) + return true; + + /* rewind the stream, so each plugin gets a fresh start */ + input_stream.Rewind(IgnoreError()); + + decoder.dc.Unlock(); + + FormatThreadName("decoder:%s", plugin.name); + + plugin.StreamDecode(decoder, input_stream); + + SetThreadName("decoder"); + + decoder.dc.Lock(); + + assert(decoder.dc.state == DecoderState::START || + decoder.dc.state == DecoderState::DECODE); + + return decoder.dc.state != DecoderState::START; +} + +static bool +decoder_file_decode(const DecoderPlugin &plugin, + Decoder &decoder, Path path) +{ + assert(plugin.file_decode != nullptr); + assert(decoder.stream_tag == nullptr); + assert(decoder.decoder_tag == nullptr); + assert(!path.IsNull()); + assert(path.IsAbsolute()); + assert(decoder.dc.state == DecoderState::START); + + FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name); + + if (decoder.dc.command == DecoderCommand::STOP) + return true; + + decoder.dc.Unlock(); + + FormatThreadName("decoder:%s", plugin.name); + + plugin.FileDecode(decoder, path); + + SetThreadName("decoder"); + + decoder.dc.Lock(); + + assert(decoder.dc.state == DecoderState::START || + decoder.dc.state == DecoderState::DECODE); + + return decoder.dc.state != DecoderState::START; +} + +gcc_pure +static bool +decoder_check_plugin_mime(const DecoderPlugin &plugin, const InputStream &is) +{ + assert(plugin.stream_decode != nullptr); + + const char *mime_type = is.GetMimeType(); + return mime_type != nullptr && plugin.SupportsMimeType(mime_type); +} + +gcc_pure +static bool +decoder_check_plugin_suffix(const DecoderPlugin &plugin, const char *suffix) +{ + assert(plugin.stream_decode != nullptr); + + return suffix != nullptr && plugin.SupportsSuffix(suffix); +} + +gcc_pure +static bool +decoder_check_plugin(const DecoderPlugin &plugin, const InputStream &is, + const char *suffix) +{ + return plugin.stream_decode != nullptr && + (decoder_check_plugin_mime(plugin, is) || + decoder_check_plugin_suffix(plugin, suffix)); +} + +static bool +decoder_run_stream_plugin(Decoder &decoder, InputStream &is, + const char *suffix, + const DecoderPlugin &plugin, + bool &tried_r) +{ + if (!decoder_check_plugin(plugin, is, suffix)) + return false; + + tried_r = true; + return decoder_stream_decode(plugin, decoder, is); +} + +static bool +decoder_run_stream_locked(Decoder &decoder, InputStream &is, + const char *uri, bool &tried_r) +{ + UriSuffixBuffer suffix_buffer; + const char *const suffix = uri_get_suffix(uri, suffix_buffer); + + using namespace std::placeholders; + const auto f = std::bind(decoder_run_stream_plugin, + std::ref(decoder), std::ref(is), suffix, + _1, std::ref(tried_r)); + return decoder_plugins_try(f); +} + +/** + * Try decoding a stream, using the fallback plugin. + */ +static bool +decoder_run_stream_fallback(Decoder &decoder, InputStream &is) +{ + const struct DecoderPlugin *plugin; + + plugin = decoder_plugin_from_name("mad"); + return plugin != nullptr && plugin->stream_decode != nullptr && + decoder_stream_decode(*plugin, decoder, is); +} + +/** + * Try decoding a stream. + */ +static bool +decoder_run_stream(Decoder &decoder, const char *uri) +{ + DecoderControl &dc = decoder.dc; + InputStream *input_stream; + bool success; + + dc.Unlock(); + + input_stream = decoder_input_stream_open(dc, uri); + if (input_stream == nullptr) { + dc.Lock(); + return false; + } + + dc.Lock(); + + bool tried = false; + success = dc.command == DecoderCommand::STOP || + decoder_run_stream_locked(decoder, *input_stream, uri, + tried) || + /* fallback to mp3: this is needed for bastard streams + that don't have a suffix or set the mimeType */ + (!tried && + decoder_run_stream_fallback(decoder, *input_stream)); + + dc.Unlock(); + delete input_stream; + dc.Lock(); + + return success; +} + +/** + * Attempt to load replay gain data, and pass it to + * decoder_replay_gain(). + */ +static void +decoder_load_replay_gain(Decoder &decoder, Path path_fs) +{ + ReplayGainInfo info; + if (replay_gain_ape_read(path_fs, info)) + decoder_replay_gain(decoder, &info); +} + +static bool +TryDecoderFile(Decoder &decoder, Path path_fs, const char *suffix, + const DecoderPlugin &plugin) +{ + if (!plugin.SupportsSuffix(suffix)) + return false; + + DecoderControl &dc = decoder.dc; + + if (plugin.file_decode != nullptr) { + dc.Lock(); + + if (decoder_file_decode(plugin, decoder, path_fs)) + return true; + + dc.Unlock(); + } else if (plugin.stream_decode != nullptr) { + InputStream *input_stream = + decoder_input_stream_open(dc, path_fs); + if (input_stream == nullptr) + return false; + + dc.Lock(); + + bool success = decoder_stream_decode(plugin, decoder, + *input_stream); + + dc.Unlock(); + + delete input_stream; + + if (success) { + dc.Lock(); + return true; + } + } + + return false; +} + +/** + * Try decoding a file. + */ +static bool +decoder_run_file(Decoder &decoder, const char *uri_utf8, Path path_fs) +{ + const char *suffix = uri_get_suffix(uri_utf8); + if (suffix == nullptr) + return false; + + DecoderControl &dc = decoder.dc; + dc.Unlock(); + + decoder_load_replay_gain(decoder, path_fs); + + if (decoder_plugins_try([&decoder, path_fs, + suffix](const DecoderPlugin &plugin){ + return TryDecoderFile(decoder, + path_fs, suffix, + plugin); + })) + return true; + + dc.Lock(); + return false; +} + +static void +decoder_run_song(DecoderControl &dc, + const DetachedSong &song, const char *uri, Path path_fs) +{ + Decoder decoder(dc, dc.start_time.IsPositive(), + new Tag(song.GetTag())); + int ret; + + dc.state = DecoderState::START; + + decoder_command_finished_locked(dc); + + ret = !path_fs.IsNull() + ? decoder_run_file(decoder, uri, path_fs) + : decoder_run_stream(decoder, uri); + + dc.Unlock(); + + /* flush the last chunk */ + + if (decoder.chunk != nullptr) + decoder.FlushChunk(); + + dc.Lock(); + + if (decoder.error.IsDefined()) { + /* copy the Error from sruct Decoder to + DecoderControl */ + dc.state = DecoderState::ERROR; + dc.error = std::move(decoder.error); + } else if (ret) + dc.state = DecoderState::STOP; + else { + dc.state = DecoderState::ERROR; + + const char *error_uri = song.GetURI(); + const std::string allocated = uri_remove_auth(error_uri); + if (!allocated.empty()) + error_uri = allocated.c_str(); + + dc.error.Format(decoder_domain, + "Failed to decode %s", error_uri); + } + + dc.client_cond.signal(); +} + +static void +decoder_run(DecoderControl &dc) +{ + dc.ClearError(); + + assert(dc.song != nullptr); + const DetachedSong &song = *dc.song; + + const char *const uri_utf8 = song.GetRealURI(); + + Path path_fs = Path::Null(); + AllocatedPath path_buffer = AllocatedPath::Null(); + if (PathTraitsUTF8::IsAbsolute(uri_utf8)) { + path_buffer = AllocatedPath::FromUTF8(uri_utf8, dc.error); + if (path_buffer.IsNull()) { + dc.state = DecoderState::ERROR; + decoder_command_finished_locked(dc); + return; + } + + path_fs = path_buffer; + } + + decoder_run_song(dc, song, uri_utf8, path_fs); + +} + +static void +decoder_task(void *arg) +{ + DecoderControl &dc = *(DecoderControl *)arg; + + SetThreadName("decoder"); + + dc.Lock(); + + do { + assert(dc.state == DecoderState::STOP || + dc.state == DecoderState::ERROR); + + switch (dc.command) { + case DecoderCommand::START: + dc.CycleMixRamp(); + dc.replay_gain_prev_db = dc.replay_gain_db; + dc.replay_gain_db = 0; + + decoder_run(dc); + break; + + case DecoderCommand::SEEK: + /* this seek was too late, and the decoder had + already finished; start a new decoder */ + + /* we need to clear the pipe here; usually the + PlayerThread is responsible, but it is not + aware that the decoder has finished */ + dc.pipe->Clear(*dc.buffer); + + decoder_run(dc); + break; + + case DecoderCommand::STOP: + decoder_command_finished_locked(dc); + break; + + case DecoderCommand::NONE: + dc.Wait(); + break; + } + } while (dc.command != DecoderCommand::NONE || !dc.quit); + + dc.Unlock(); +} + +void +decoder_thread_start(DecoderControl &dc) +{ + assert(!dc.thread.IsDefined()); + + dc.quit = false; + + Error error; + if (!dc.thread.Start(decoder_task, &dc, error)) + FatalError(error); +} diff --git a/src/decoder/DecoderThread.hxx b/src/decoder/DecoderThread.hxx new file mode 100644 index 000000000..d5fde281c --- /dev/null +++ b/src/decoder/DecoderThread.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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. + * + * 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 Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_THREAD_HXX +#define MPD_DECODER_THREAD_HXX + +struct DecoderControl; + +void +decoder_thread_start(DecoderControl &dc); + +#endif diff --git a/src/decoder/AdPlugDecoderPlugin.cxx b/src/decoder/plugins/AdPlugDecoderPlugin.cxx index c79fca5f9..9cc37ade4 100644 --- a/src/decoder/AdPlugDecoderPlugin.cxx +++ b/src/decoder/plugins/AdPlugDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,9 +20,11 @@ #include "config.h" #include "AdPlugDecoderPlugin.h" #include "tag/TagHandler.hxx" -#include "DecoderAPI.hxx" +#include "../DecoderAPI.hxx" #include "CheckAudioFormat.hxx" +#include "fs/Path.hxx" #include "util/Error.hxx" +#include "util/Domain.hxx" #include "util/Macros.hxx" #include "Log.hxx" @@ -31,11 +33,16 @@ #include <assert.h> +static constexpr Domain adplug_domain("adplug"); + static unsigned sample_rate; static bool adplug_init(const config_param ¶m) { + FormatDebug(adplug_domain, "adplug %s", + CAdPlug::get_version().c_str()); + Error error; sample_rate = param.GetBlockValue("sample_rate", 48000u); @@ -48,12 +55,12 @@ adplug_init(const config_param ¶m) } static void -adplug_file_decode(Decoder &decoder, const char *path_fs) +adplug_file_decode(Decoder &decoder, Path path_fs) { CEmuopl opl(sample_rate, true, true); opl.init(); - CPlayer *player = CAdPlug::factory(path_fs, &opl); + CPlayer *player = CAdPlug::factory(path_fs.c_str(), &opl); if (player == nullptr) return; @@ -61,16 +68,16 @@ adplug_file_decode(Decoder &decoder, const char *path_fs) assert(audio_format.IsValid()); decoder_initialized(decoder, audio_format, false, - player->songlength() / 1000.); + SongTime::FromMS(player->songlength())); - int16_t buffer[2048]; - const unsigned frames_per_buffer = ARRAY_SIZE(buffer) / 2; DecoderCommand cmd; do { if (!player->update()) break; + int16_t buffer[2048]; + constexpr unsigned frames_per_buffer = ARRAY_SIZE(buffer) / 2; opl.update(buffer, frames_per_buffer); cmd = decoder_data(decoder, nullptr, buffer, sizeof(buffer), @@ -90,18 +97,18 @@ adplug_scan_tag(TagType type, const std::string &value, } static bool -adplug_scan_file(const char *path_fs, +adplug_scan_file(Path path_fs, const struct tag_handler *handler, void *handler_ctx) { CEmuopl opl(sample_rate, true, true); opl.init(); - CPlayer *player = CAdPlug::factory(path_fs, &opl); + CPlayer *player = CAdPlug::factory(path_fs.c_str(), &opl); if (player == nullptr) return false; tag_handler_invoke_duration(handler, handler_ctx, - player->songlength() / 1000); + SongTime::FromMS(player->songlength())); if (handler->tag != nullptr) { adplug_scan_tag(TAG_TITLE, player->gettitle(), diff --git a/src/decoder/AdPlugDecoderPlugin.h b/src/decoder/plugins/AdPlugDecoderPlugin.h index a827fdc7d..539dbbf0a 100644 --- a/src/decoder/AdPlugDecoderPlugin.h +++ b/src/decoder/plugins/AdPlugDecoderPlugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/AudiofileDecoderPlugin.cxx b/src/decoder/plugins/AudiofileDecoderPlugin.cxx index b1b8bf613..a0ef71e49 100644 --- a/src/decoder/AudiofileDecoderPlugin.cxx +++ b/src/decoder/plugins/AudiofileDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,10 +19,11 @@ #include "config.h" #include "AudiofileDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" #include "CheckAudioFormat.hxx" #include "tag/TagHandler.hxx" +#include "fs/Path.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" @@ -33,11 +34,21 @@ #include <assert.h> #include <stdio.h> -/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */ -#define CHUNK_SIZE 1020 - static constexpr Domain audiofile_domain("audiofile"); +static void +audiofile_error_func(long, const char *msg) +{ + LogWarning(audiofile_domain, msg); +} + +static bool +audiofile_init(const config_param &) +{ + afSetErrorHandler(audiofile_error_func); + return true; +} + struct AudioFileInputStream { Decoder *const decoder; InputStream &is; @@ -52,18 +63,12 @@ struct AudioFileInputStream { } }; -static int audiofile_get_duration(const char *file) +gcc_pure +static SongTime +audiofile_get_duration(AFfilehandle fh) { - int total_time; - AFfilehandle af_fp = afOpenFile(file, "r", nullptr); - if (af_fp == AF_NULL_FILEHANDLE) { - return -1; - } - total_time = (int) - ((double)afGetFrameCount(af_fp, AF_DEFAULT_TRACK) - / afGetRate(af_fp, AF_DEFAULT_TRACK)); - afCloseFile(af_fp); - return total_time; + return SongTime::FromScale<uint64_t>(afGetFrameCount(fh, AF_DEFAULT_TRACK), + afGetRate(fh, AF_DEFAULT_TRACK)); } static ssize_t @@ -101,15 +106,18 @@ audiofile_file_destroy(AFvirtualfile *vfile) } static AFfileoffset -audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative) +audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset _offset, + int is_relative) { AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure; InputStream &is = afis.is; - int whence = (is_relative ? SEEK_CUR : SEEK_SET); + offset_type offset = _offset; + if (is_relative) + offset += is.GetOffset(); Error error; - if (is.LockSeek(offset, whence, error)) { + if (is.LockSeek(offset, error)) { return is.GetOffset(); } else { LogError(error, "Seek failed"); @@ -165,7 +173,7 @@ audiofile_setup_sample_format(AFfilehandle af_fp) } afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, - AF_SAMPFMT_TWOSCOMP, bits); + AF_SAMPFMT_TWOSCOMP, bits); afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); return audiofile_bits_to_sample_format(bits); @@ -174,88 +182,94 @@ audiofile_setup_sample_format(AFfilehandle af_fp) static void audiofile_stream_decode(Decoder &decoder, InputStream &is) { - AFvirtualfile *vf; - int fs, frame_count; - AFfilehandle af_fp; - AudioFormat audio_format; - float total_time; - uint16_t bit_rate; - int ret; - char chunk[CHUNK_SIZE]; - - if (!is.IsSeekable()) { + if (!is.IsSeekable() || !is.KnownSize()) { LogWarning(audiofile_domain, "not seekable"); return; } AudioFileInputStream afis{&decoder, is}; - vf = setup_virtual_fops(afis); + AFvirtualfile *const vf = setup_virtual_fops(afis); - af_fp = afOpenVirtualFile(vf, "r", nullptr); - if (af_fp == AF_NULL_FILEHANDLE) { - LogWarning(audiofile_domain, "failed to input stream"); + const AFfilehandle fh = afOpenVirtualFile(vf, "r", nullptr); + if (fh == AF_NULL_FILEHANDLE) return; - } Error error; + AudioFormat audio_format; if (!audio_format_init_checked(audio_format, - afGetRate(af_fp, AF_DEFAULT_TRACK), - audiofile_setup_sample_format(af_fp), - afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK), + afGetRate(fh, AF_DEFAULT_TRACK), + audiofile_setup_sample_format(fh), + afGetVirtualChannels(fh, AF_DEFAULT_TRACK), error)) { LogError(error); - afCloseFile(af_fp); + afCloseFile(fh); return; } - frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK); - - total_time = ((float)frame_count / (float)audio_format.sample_rate); + const auto total_time = audiofile_get_duration(fh); - bit_rate = (uint16_t)(is.GetSize() * 8.0 / total_time / 1000.0 + 0.5); + const uint16_t kbit_rate = (uint16_t) + (is.GetSize() * uint64_t(8) / total_time.ToMS()); - fs = (int)afGetVirtualFrameSize(af_fp, AF_DEFAULT_TRACK, 1); + const unsigned frame_size = (unsigned) + afGetVirtualFrameSize(fh, AF_DEFAULT_TRACK, true); decoder_initialized(decoder, audio_format, true, total_time); DecoderCommand cmd; do { - ret = afReadFrames(af_fp, AF_DEFAULT_TRACK, chunk, - CHUNK_SIZE / fs); - if (ret <= 0) + /* pick 1020 since its divisible for 8,16,24, and + 32-bit audio */ + char chunk[1020]; + const int nframes = + afReadFrames(fh, AF_DEFAULT_TRACK, chunk, + sizeof(chunk) / frame_size); + if (nframes <= 0) break; cmd = decoder_data(decoder, nullptr, - chunk, ret * fs, - bit_rate); + chunk, nframes * frame_size, + kbit_rate); if (cmd == DecoderCommand::SEEK) { - AFframecount frame = decoder_seek_where(decoder) * - audio_format.sample_rate; - afSeekFrame(af_fp, AF_DEFAULT_TRACK, frame); + AFframecount frame = decoder_seek_where_frame(decoder); + afSeekFrame(fh, AF_DEFAULT_TRACK, frame); decoder_command_finished(decoder); cmd = DecoderCommand::NONE; } } while (cmd == DecoderCommand::NONE); - afCloseFile(af_fp); + afCloseFile(fh); } -static bool -audiofile_scan_file(const char *file, - const struct tag_handler *handler, void *handler_ctx) +gcc_pure +static SignedSongTime +audiofile_get_duration(InputStream &is) { - int total_time = audiofile_get_duration(file); + if (!is.IsSeekable() || !is.KnownSize()) + return SignedSongTime::Negative(); + + AudioFileInputStream afis{nullptr, is}; + AFvirtualfile *vf = setup_virtual_fops(afis); + AFfilehandle fh = afOpenVirtualFile(vf, "r", nullptr); + if (fh == AF_NULL_FILEHANDLE) + return SignedSongTime::Negative(); + + const auto duration = audiofile_get_duration(fh); + afCloseFile(fh); + return duration; +} - if (total_time < 0) { - FormatWarning(audiofile_domain, - "Failed to get total song time from: %s", - file); +static bool +audiofile_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + const auto duration = audiofile_get_duration(is); + if (duration.IsNegative()) return false; - } - tag_handler_invoke_duration(handler, handler_ctx, total_time); + tag_handler_invoke_duration(handler, handler_ctx, SongTime(duration)); return true; } @@ -271,12 +285,12 @@ static const char *const audiofile_mime_types[] = { const struct DecoderPlugin audiofile_decoder_plugin = { "audiofile", - nullptr, + audiofile_init, nullptr, audiofile_stream_decode, nullptr, - audiofile_scan_file, nullptr, + audiofile_scan_stream, nullptr, audiofile_suffixes, audiofile_mime_types, diff --git a/src/decoder/AudiofileDecoderPlugin.hxx b/src/decoder/plugins/AudiofileDecoderPlugin.hxx index 5a17281b0..61129076d 100644 --- a/src/decoder/AudiofileDecoderPlugin.hxx +++ b/src/decoder/plugins/AudiofileDecoderPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/DsdLib.cxx b/src/decoder/plugins/DsdLib.cxx index eafedda8f..d826970f7 100644 --- a/src/decoder/DsdLib.cxx +++ b/src/decoder/plugins/DsdLib.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,16 +25,14 @@ #include "config.h" #include "DsdLib.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "util/bit_reverse.h" -#include "tag/TagHandler.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" #include "tag/TagId3.hxx" #include "util/Error.hxx" +#include "util/Alloc.hxx" -#include <unistd.h> #include <string.h> -#include <stdio.h> /* for SEEK_SET, SEEK_CUR */ +#include <stdlib.h> #ifdef HAVE_ID3TAG #include <id3tag.h> @@ -54,27 +52,15 @@ DsdId::Equals(const char *s) const */ bool dsdlib_skip_to(Decoder *decoder, InputStream &is, - int64_t offset) + offset_type offset) { if (is.IsSeekable()) - return is.Seek(offset, SEEK_SET, IgnoreError()); + return is.LockSeek(offset, IgnoreError()); if (is.GetOffset() > offset) return false; - char buffer[8192]; - while (is.GetOffset() < offset) { - size_t length = sizeof(buffer); - if (offset - is.GetOffset() < (int64_t)length) - length = offset - is.GetOffset(); - - size_t nbytes = decoder_read(decoder, is, buffer, length); - if (nbytes == 0) - return false; - } - - assert(is.GetOffset() == offset); - return true; + return dsdlib_skip(decoder, is, offset - is.GetOffset()); } /** @@ -82,30 +68,39 @@ dsdlib_skip_to(Decoder *decoder, InputStream &is, */ bool dsdlib_skip(Decoder *decoder, InputStream &is, - int64_t delta) + offset_type delta) { - assert(delta >= 0); - if (delta == 0) return true; if (is.IsSeekable()) - return is.Seek(delta, SEEK_CUR, IgnoreError()); + return is.LockSeek(is.GetOffset() + delta, IgnoreError()); - char buffer[8192]; - while (delta > 0) { - size_t length = sizeof(buffer); - if ((int64_t)length > delta) - length = delta; + if (delta > 1024 * 1024) + /* don't skip more than one megabyte; it would be too + expensive */ + return false; - size_t nbytes = decoder_read(decoder, is, buffer, length); - if (nbytes == 0) - return false; + return decoder_skip(decoder, is, delta); +} - delta -= nbytes; - } +bool +dsdlib_valid_freq(uint32_t samplefreq) +{ + switch (samplefreq) { + case 2822400: /* DSD64, 64xFs, Fs = 44.100kHz */ + case 3072000: /* DSD64 with Fs = 48.000 kHz */ + case 5644800: + case 6144000: + case 11289600: + case 12288000: + case 22579200:/* DSD512 */ + case 24576000: + return true; - return true; + default: + return false; + } } #ifdef HAVE_ID3TAG @@ -116,42 +111,40 @@ dsdlib_tag_id3(InputStream &is, { assert(tagoffset >= 0); - if (tagoffset == 0) + if (tagoffset == 0 || !is.KnownSize()) return; if (!dsdlib_skip_to(nullptr, is, tagoffset)) return; - struct id3_tag *id3_tag = nullptr; - id3_length_t count; - /* Prevent broken files causing problems */ const auto size = is.GetSize(); const auto offset = is.GetOffset(); if (offset >= size) return; - count = size - offset; + const id3_length_t count = size - offset; - /* Check and limit id3 tag size to prevent a stack overflow */ - if (count == 0 || count > 4096) + if (count < 10 || count > 1024 * 1024) return; - id3_byte_t dsdid3[count]; - id3_byte_t *dsdid3data; - dsdid3data = dsdid3; + id3_byte_t *const id3_buf = new id3_byte_t[count]; + if (id3_buf == nullptr) + return; - if (!decoder_read_full(nullptr, is, dsdid3data, count)) + if (!decoder_read_full(nullptr, is, id3_buf, count)) { + delete[] id3_buf; return; + } - id3_tag = id3_tag_parse(dsdid3data, count); + struct id3_tag *id3_tag = id3_tag_parse(id3_buf, count); + delete[] id3_buf; if (id3_tag == nullptr) return; scan_id3_tag(id3_tag, handler, handler_ctx); id3_tag_delete(id3_tag); - return; } #endif diff --git a/src/decoder/DsdLib.hxx b/src/decoder/plugins/DsdLib.hxx index 5c6127149..8295bcbf6 100644 --- a/src/decoder/DsdLib.hxx +++ b/src/decoder/plugins/DsdLib.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,13 +21,14 @@ #define MPD_DECODER_DSDLIB_HXX #include "system/ByteOrder.hxx" +#include "input/Offset.hxx" #include "Compiler.h" #include <stddef.h> #include <stdint.h> struct Decoder; -struct InputStream; +class InputStream; struct DsdId { char value[4]; @@ -60,11 +61,18 @@ public: bool dsdlib_skip_to(Decoder *decoder, InputStream &is, - int64_t offset); + offset_type offset); bool dsdlib_skip(Decoder *decoder, InputStream &is, - int64_t delta); + offset_type delta); + +/** + * Check if the sample frequency is a valid DSD frequency. + **/ +gcc_const +bool +dsdlib_valid_freq(uint32_t samplefreq); /** * Add tags from ID3 tag. All tags commonly found in the ID3 tags of diff --git a/src/decoder/DsdiffDecoderPlugin.cxx b/src/decoder/plugins/DsdiffDecoderPlugin.cxx index 767395215..b6c79e11e 100644 --- a/src/decoder/DsdiffDecoderPlugin.cxx +++ b/src/decoder/plugins/DsdiffDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,8 +28,8 @@ #include "config.h" #include "DsdiffDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" #include "CheckAudioFormat.hxx" #include "util/bit_reverse.h" #include "util/Error.hxx" @@ -38,9 +38,6 @@ #include "DsdLib.hxx" #include "Log.hxx" -#include <unistd.h> -#include <stdio.h> /* for SEEK_SET, SEEK_CUR */ - struct DsdiffHeader { DsdId id; DffDsdUint64 size; @@ -69,15 +66,7 @@ struct dsdiff_native_tag { struct DsdiffMetaData { unsigned sample_rate, channels; bool bitreverse; - uint64_t chunk_size; -#ifdef HAVE_ID3TAG - InputStream::offset_type id3_offset; - uint64_t id3_size; -#endif - /** offset for artist tag */ - InputStream::offset_type diar_offset; - /** offset for title tag */ - InputStream::offset_type diti_offset; + offset_type chunk_size; }; static bool lsbitfirst; @@ -121,14 +110,14 @@ dsdiff_read_payload(Decoder *decoder, InputStream &is, static bool dsdiff_read_prop_snd(Decoder *decoder, InputStream &is, DsdiffMetaData *metadata, - InputStream::offset_type end_offset) + offset_type end_offset) { DsdiffChunkHeader header; - while ((InputStream::offset_type)(is.GetOffset() + sizeof(header)) <= end_offset) { + while (is.GetOffset() + sizeof(header) <= end_offset) { if (!dsdiff_read_chunk_header(decoder, is, &header)) return false; - InputStream::offset_type chunk_end_offset = is.GetOffset() + offset_type chunk_end_offset = is.GetOffset() + header.GetSize(); if (chunk_end_offset > end_offset) return false; @@ -182,7 +171,7 @@ dsdiff_read_prop(Decoder *decoder, InputStream &is, const DsdiffChunkHeader *prop_header) { uint64_t prop_size = prop_header->GetSize(); - InputStream::offset_type end_offset = is.GetOffset() + prop_size; + const offset_type end_offset = is.GetOffset() + prop_size; DsdId prop_id; if (prop_size < sizeof(prop_id) || @@ -199,7 +188,7 @@ dsdiff_read_prop(Decoder *decoder, InputStream &is, static void dsdiff_handle_native_tag(InputStream &is, const struct tag_handler *handler, - void *handler_ctx, InputStream::offset_type tagoffset, + void *handler_ctx, offset_type tagoffset, TagType type) { if (!dsdlib_skip_to(nullptr, is, tagoffset)) @@ -250,18 +239,20 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is, if (!dsdiff_read_chunk_header(decoder, is, chunk_header)) return false; - metadata->diar_offset = 0; - metadata->diti_offset = 0; + /** offset for artist tag */ + offset_type artist_offset = 0; + /** offset for title tag */ + offset_type title_offset = 0; #ifdef HAVE_ID3TAG - metadata->id3_offset = 0; + offset_type id3_offset = 0; #endif /* Now process all the remaining chunk headers in the stream and record their position and size */ do { - uint64_t chunk_size = chunk_header->GetSize(); + offset_type chunk_size = chunk_header->GetSize(); /* DIIN chunk, is directly followed by other chunks */ if (chunk_header->id.Equals("DIIN")) @@ -270,20 +261,19 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is, /* DIAR chunk - DSDIFF native tag for Artist */ if (chunk_header->id.Equals("DIAR")) { chunk_size = chunk_header->GetSize(); - metadata->diar_offset = is.GetOffset(); + artist_offset = is.GetOffset(); } /* DITI chunk - DSDIFF native tag for Title */ if (chunk_header->id.Equals("DITI")) { chunk_size = chunk_header->GetSize(); - metadata->diti_offset = is.GetOffset(); + title_offset = is.GetOffset(); } #ifdef HAVE_ID3TAG /* 'ID3 ' chunk, offspec. Used by sacdextract */ if (chunk_header->id.Equals("ID3 ")) { chunk_size = chunk_header->GetSize(); - metadata->id3_offset = is.GetOffset(); - metadata->id3_size = chunk_size; + id3_offset = is.GetOffset(); } #endif @@ -294,22 +284,21 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is, /* done processing chunk headers, process tags if any */ #ifdef HAVE_ID3TAG - if (metadata->id3_offset != 0) - { + if (id3_offset != 0) { /* a ID3 tag has preference over the other tags, do not process other tags if we have one */ - dsdlib_tag_id3(is, handler, handler_ctx, metadata->id3_offset); + dsdlib_tag_id3(is, handler, handler_ctx, id3_offset); return true; } #endif - if (metadata->diar_offset != 0) + if (artist_offset != 0) dsdiff_handle_native_tag(is, handler, handler_ctx, - metadata->diar_offset, TAG_ARTIST); + artist_offset, TAG_ARTIST); - if (metadata->diti_offset != 0) + if (title_offset != 0) dsdiff_handle_native_tag(is, handler, handler_ctx, - metadata->diti_offset, TAG_TITLE); + title_offset, TAG_TITLE); return true; } @@ -339,13 +328,13 @@ dsdiff_read_metadata(Decoder *decoder, InputStream &is, chunk_header)) return false; } else if (chunk_header->id.Equals("DSD ")) { - const uint64_t chunk_size = chunk_header->GetSize(); + const offset_type chunk_size = chunk_header->GetSize(); metadata->chunk_size = chunk_size; return true; } else { /* ignore unknown chunk */ - const uint64_t chunk_size = chunk_header->GetSize(); - InputStream::offset_type chunk_end_offset = + const offset_type chunk_size = chunk_header->GetSize(); + const offset_type chunk_end_offset = is.GetOffset() + chunk_size; if (!dsdlib_skip_to(decoder, is, chunk_end_offset)) @@ -361,29 +350,53 @@ bit_reverse_buffer(uint8_t *p, uint8_t *end) *p = bit_reverse(*p); } +static offset_type +FrameToOffset(uint64_t frame, unsigned channels) +{ + return frame * channels; +} + /** * Decode one "DSD" chunk. */ static bool dsdiff_decode_chunk(Decoder &decoder, InputStream &is, - unsigned channels, - uint64_t chunk_size) + unsigned channels, unsigned sample_rate, + const offset_type total_bytes) { + const offset_type start_offset = is.GetOffset(); + uint8_t buffer[8192]; const size_t sample_size = sizeof(buffer[0]); const size_t frame_size = channels * sample_size; const unsigned buffer_frames = sizeof(buffer) / frame_size; - const unsigned buffer_samples = buffer_frames * frame_size; - const size_t buffer_size = buffer_samples * sample_size; + const size_t buffer_size = buffer_frames * frame_size; + + auto cmd = decoder_get_command(decoder); + for (offset_type remaining_bytes = total_bytes; + remaining_bytes >= frame_size && cmd != DecoderCommand::STOP;) { + if (cmd == DecoderCommand::SEEK) { + uint64_t frame = decoder_seek_where_frame(decoder); + offset_type offset = FrameToOffset(frame, channels); + if (offset >= total_bytes) { + decoder_command_finished(decoder); + break; + } + + if (dsdlib_skip_to(&decoder, is, + start_offset + offset)) { + decoder_command_finished(decoder); + remaining_bytes = total_bytes - offset; + } else + decoder_seek_error(decoder); + } - while (chunk_size >= frame_size) { /* see how much aligned data from the remaining chunk fits into the local buffer */ size_t now_size = buffer_size; - if (chunk_size < (uint64_t)now_size) { - unsigned now_frames = - (unsigned)chunk_size / frame_size; + if (remaining_bytes < (offset_type)now_size) { + unsigned now_frames = remaining_bytes / frame_size; now_size = now_frames * frame_size; } @@ -391,28 +404,16 @@ dsdiff_decode_chunk(Decoder &decoder, InputStream &is, return false; const size_t nbytes = now_size; - chunk_size -= nbytes; + remaining_bytes -= nbytes; if (lsbitfirst) bit_reverse_buffer(buffer, buffer + nbytes); - const auto cmd = decoder_data(decoder, is, buffer, nbytes, 0); - switch (cmd) { - case DecoderCommand::NONE: - break; - - case DecoderCommand::START: - case DecoderCommand::STOP: - return false; - - case DecoderCommand::SEEK: - - /* Not implemented yet */ - decoder_seek_error(decoder); - break; - } + cmd = decoder_data(decoder, is, buffer, nbytes, + sample_rate / 1000); } - return dsdlib_skip(&decoder, is, chunk_size); + + return true; } static void @@ -435,36 +436,22 @@ dsdiff_stream_decode(Decoder &decoder, InputStream &is) } /* calculate song time from DSD chunk size and sample frequency */ - uint64_t chunk_size = metadata.chunk_size; - float songtime = ((chunk_size / metadata.channels) * 8) / - (float) metadata.sample_rate; + offset_type chunk_size = metadata.chunk_size; + + uint64_t n_frames = chunk_size / audio_format.channels; + auto songtime = SongTime::FromScale<uint64_t>(n_frames, + audio_format.sample_rate); /* success: file was recognized */ - decoder_initialized(decoder, audio_format, false, songtime); + decoder_initialized(decoder, audio_format, is.IsSeekable(), songtime); /* every iteration of the following loop decodes one "DSD" chunk from a DFF file */ - while (true) { - chunk_size = chunk_header.GetSize(); - - if (chunk_header.id.Equals("DSD ")) { - if (!dsdiff_decode_chunk(decoder, is, - metadata.channels, - chunk_size)) - break; - } else { - /* ignore other chunks */ - if (!dsdlib_skip(&decoder, is, chunk_size)) - break; - } - - /* read next chunk header; the first one was read by - dsdiff_read_metadata() */ - if (!dsdiff_read_chunk_header(&decoder, - is, &chunk_header)) - break; - } + dsdiff_decode_chunk(decoder, is, + metadata.channels, + metadata.sample_rate, + chunk_size); } static bool @@ -487,8 +474,9 @@ dsdiff_scan_stream(InputStream &is, return false; /* calculate song time and add as tag */ - unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) / - metadata.sample_rate; + uint64_t n_frames = metadata.chunk_size / audio_format.channels; + auto songtime = SongTime::FromScale<uint64_t>(n_frames, + audio_format.sample_rate); tag_handler_invoke_duration(handler, handler_ctx, songtime); /* Read additional metadata and created tags if available */ diff --git a/src/decoder/DsdiffDecoderPlugin.hxx b/src/decoder/plugins/DsdiffDecoderPlugin.hxx index be14fc9cd..7aa36752b 100644 --- a/src/decoder/DsdiffDecoderPlugin.hxx +++ b/src/decoder/plugins/DsdiffDecoderPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/DsfDecoderPlugin.cxx b/src/decoder/plugins/DsfDecoderPlugin.cxx index 9fbfe9cda..690616d15 100644 --- a/src/decoder/DsfDecoderPlugin.cxx +++ b/src/decoder/plugins/DsfDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,8 +29,8 @@ #include "config.h" #include "DsfDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" #include "CheckAudioFormat.hxx" #include "util/bit_reverse.h" #include "util/Error.hxx" @@ -39,16 +39,16 @@ #include "tag/TagHandler.hxx" #include "Log.hxx" -#include <unistd.h> -#include <stdio.h> /* for SEEK_SET, SEEK_CUR */ +#include <string.h> + +static constexpr unsigned DSF_BLOCK_SIZE = 4096; struct DsfMetaData { unsigned sample_rate, channels; bool bitreverse; - uint64_t chunk_size; + offset_type n_blocks; #ifdef HAVE_ID3TAG - InputStream::offset_type id3_offset; - uint64_t id3_size; + offset_type id3_offset; #endif }; @@ -107,12 +107,12 @@ dsf_read_metadata(Decoder *decoder, InputStream &is, !dsf_header.id.Equals("DSD ")) return false; - const uint64_t chunk_size = dsf_header.size.Read(); + const offset_type chunk_size = dsf_header.size.Read(); if (sizeof(dsf_header) != chunk_size) return false; #ifdef HAVE_ID3TAG - const uint64_t metadata_offset = dsf_header.pmeta.Read(); + const offset_type metadata_offset = dsf_header.pmeta.Read(); #endif /* read the 'fmt ' chunk of the DSF file */ @@ -127,19 +127,20 @@ dsf_read_metadata(Decoder *decoder, InputStream &is, return false; uint32_t samplefreq = FromLE32(dsf_fmt_chunk.sample_freq); + const unsigned channels = FromLE32(dsf_fmt_chunk.channelnum); /* for now, only support version 1 of the standard, DSD raw stereo files with a sample freq of 2822400 or 5644800 Hz */ - if (dsf_fmt_chunk.version != 1 || dsf_fmt_chunk.formatid != 0 - || dsf_fmt_chunk.channeltype != 2 - || dsf_fmt_chunk.channelnum != 2 - || (samplefreq != 2822400 && samplefreq != 5644800)) + if (FromLE32(dsf_fmt_chunk.version) != 1 || + FromLE32(dsf_fmt_chunk.formatid) != 0 || + !audio_valid_channel_count(channels) || + !dsdlib_valid_freq(samplefreq)) return false; uint32_t chblksize = FromLE32(dsf_fmt_chunk.block_size); /* according to the spec block size should always be 4096 */ - if (chblksize != 4096) + if (chblksize != DSF_BLOCK_SIZE) return false; /* read the 'data' chunk of the DSF file */ @@ -151,37 +152,33 @@ dsf_read_metadata(Decoder *decoder, InputStream &is, /* data size of DSF files are padded to multiple of 4096, we use the actual data size as chunk size */ - uint64_t data_size = data_chunk.size.Read(); + offset_type data_size = data_chunk.size.Read(); if (data_size < sizeof(data_chunk)) return false; data_size -= sizeof(data_chunk); /* data_size cannot be bigger or equal to total file size */ - const uint64_t size = (uint64_t)is.GetSize(); - if (data_size >= size) + if (is.KnownSize() && data_size > is.GetRest()) return false; /* use the sample count from the DSF header as the upper bound, because some DSF files contain junk at the end of the "data" chunk */ const uint64_t samplecnt = dsf_fmt_chunk.scnt.Read(); - const uint64_t playable_size = samplecnt * 2 / 8; + const offset_type playable_size = samplecnt * channels / 8; if (data_size > playable_size) data_size = playable_size; - metadata->chunk_size = data_size; - metadata->channels = (unsigned) dsf_fmt_chunk.channelnum; + const size_t block_size = channels * DSF_BLOCK_SIZE; + metadata->n_blocks = data_size / block_size; + metadata->channels = channels; metadata->sample_rate = samplefreq; #ifdef HAVE_ID3TAG - /* metada_offset cannot be bigger then or equal to total file size */ - if (metadata_offset >= size) - metadata->id3_offset = 0; - else - metadata->id3_offset = (InputStream::offset_type)metadata_offset; + metadata->id3_offset = metadata_offset; #endif /* check bits per sample format, determine if bitreverse is needed */ - metadata->bitreverse = dsf_fmt_chunk.bitssample == 1; + metadata->bitreverse = FromLE32(dsf_fmt_chunk.bitssample) == 1; return true; } @@ -192,6 +189,13 @@ bit_reverse_buffer(uint8_t *p, uint8_t *end) *p = bit_reverse(*p); } +static void +InterleaveDsfBlockMono(uint8_t *gcc_restrict dest, + const uint8_t *gcc_restrict src) +{ + memcpy(dest, src, DSF_BLOCK_SIZE); +} + /** * DSF data is build up of alternating 4096 blocks of DSD samples for left and * right. Convert the buffer holding 1 block of 4096 DSD left samples and 1 @@ -199,22 +203,49 @@ bit_reverse_buffer(uint8_t *p, uint8_t *end) * order. */ static void -dsf_to_pcm_order(uint8_t *dest, uint8_t *scratch, size_t nrbytes) +InterleaveDsfBlockStereo(uint8_t *gcc_restrict dest, + const uint8_t *gcc_restrict src) { - for (unsigned i = 0, j = 0; i < (unsigned)nrbytes; i += 2) { - scratch[i] = *(dest+j); - j++; + for (size_t i = 0; i < DSF_BLOCK_SIZE; ++i) { + dest[2 * i] = src[i]; + dest[2 * i + 1] = src[DSF_BLOCK_SIZE + i]; } +} - for (unsigned i = 1, j = 0; i < (unsigned) nrbytes; i += 2) { - scratch[i] = *(dest+4096+j); - j++; - } +static void +InterleaveDsfBlockChannel(uint8_t *gcc_restrict dest, + const uint8_t *gcc_restrict src, + unsigned channels) +{ + for (size_t i = 0; i < DSF_BLOCK_SIZE; ++i, dest += channels, ++src) + *dest = *src; +} - for (unsigned i = 0; i < (unsigned)nrbytes; i++) { - *dest = scratch[i]; - dest++; - } +static void +InterleaveDsfBlockGeneric(uint8_t *gcc_restrict dest, + const uint8_t *gcc_restrict src, + unsigned channels) +{ + for (unsigned c = 0; c < channels; ++c, ++dest, src += DSF_BLOCK_SIZE) + InterleaveDsfBlockChannel(dest, src, channels); +} + +static void +InterleaveDsfBlock(uint8_t *gcc_restrict dest, const uint8_t *gcc_restrict src, + unsigned channels) +{ + if (channels == 1) + InterleaveDsfBlockMono(dest, src); + else if (channels == 2) + InterleaveDsfBlockStereo(dest, src); + else + InterleaveDsfBlockGeneric(dest, src, channels); +} + +static offset_type +FrameToBlock(uint64_t frame) +{ + return frame / DSF_BLOCK_SIZE; } /** @@ -222,60 +253,50 @@ dsf_to_pcm_order(uint8_t *dest, uint8_t *scratch, size_t nrbytes) */ static bool dsf_decode_chunk(Decoder &decoder, InputStream &is, - unsigned channels, - uint64_t chunk_size, - bool bitreverse) + unsigned channels, unsigned sample_rate, + offset_type n_blocks, + bool bitreverse) { - uint8_t buffer[8192]; - - /* scratch buffer for DSF samples to convert to the needed - normal left/right regime of samples */ - uint8_t dsf_scratch_buffer[8192]; - - const size_t sample_size = sizeof(buffer[0]); - const size_t frame_size = channels * sample_size; - const unsigned buffer_frames = sizeof(buffer) / frame_size; - const unsigned buffer_samples = buffer_frames * frame_size; - const size_t buffer_size = buffer_samples * sample_size; - - while (chunk_size >= frame_size) { - /* see how much aligned data from the remaining chunk - fits into the local buffer */ - size_t now_size = buffer_size; - if (chunk_size < (uint64_t)now_size) { - unsigned now_frames = - (unsigned)chunk_size / frame_size; - now_size = now_frames * frame_size; + const size_t block_size = channels * DSF_BLOCK_SIZE; + const offset_type start_offset = is.GetOffset(); + + auto cmd = decoder_get_command(decoder); + for (offset_type i = 0; i < n_blocks && cmd != DecoderCommand::STOP;) { + if (cmd == DecoderCommand::SEEK) { + uint64_t frame = decoder_seek_where_frame(decoder); + offset_type block = FrameToBlock(frame); + if (block >= n_blocks) { + decoder_command_finished(decoder); + break; + } + + offset_type offset = + start_offset + block * block_size; + if (dsdlib_skip_to(&decoder, is, offset)) { + decoder_command_finished(decoder); + i = block; + } else + decoder_seek_error(decoder); } - if (!decoder_read_full(&decoder, is, buffer, now_size)) + /* worst-case buffer size */ + uint8_t buffer[MAX_CHANNELS * DSF_BLOCK_SIZE]; + if (!decoder_read_full(&decoder, is, buffer, block_size)) return false; - const size_t nbytes = now_size; - chunk_size -= nbytes; - if (bitreverse) - bit_reverse_buffer(buffer, buffer + nbytes); + bit_reverse_buffer(buffer, buffer + block_size); - dsf_to_pcm_order(buffer, dsf_scratch_buffer, nbytes); + uint8_t interleaved_buffer[MAX_CHANNELS * DSF_BLOCK_SIZE]; + InterleaveDsfBlock(interleaved_buffer, buffer, channels); - const auto cmd = decoder_data(decoder, is, buffer, nbytes, 0); - switch (cmd) { - case DecoderCommand::NONE: - break; - - case DecoderCommand::START: - case DecoderCommand::STOP: - return false; - - case DecoderCommand::SEEK: - - /* not implemented yet */ - decoder_seek_error(decoder); - break; - } + cmd = decoder_data(decoder, is, + interleaved_buffer, block_size, + sample_rate / 1000); + ++i; } - return dsdlib_skip(&decoder, is, chunk_size); + + return true; } static void @@ -295,17 +316,17 @@ dsf_stream_decode(Decoder &decoder, InputStream &is) return; } /* Calculate song time from DSD chunk size and sample frequency */ - uint64_t chunk_size = metadata.chunk_size; - float songtime = ((chunk_size / metadata.channels) * 8) / - (float) metadata.sample_rate; + const auto n_blocks = metadata.n_blocks; + auto songtime = SongTime::FromScale<uint64_t>(n_blocks * DSF_BLOCK_SIZE, + audio_format.sample_rate); /* success: file was recognized */ - decoder_initialized(decoder, audio_format, false, songtime); + decoder_initialized(decoder, audio_format, is.IsSeekable(), songtime); - if (!dsf_decode_chunk(decoder, is, metadata.channels, - chunk_size, - metadata.bitreverse)) - return; + dsf_decode_chunk(decoder, is, metadata.channels, + metadata.sample_rate, + n_blocks, + metadata.bitreverse); } static bool @@ -326,8 +347,9 @@ dsf_scan_stream(InputStream &is, return false; /* calculate song time and add as tag */ - unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) / - metadata.sample_rate; + const auto n_blocks = metadata.n_blocks; + auto songtime = SongTime::FromScale<uint64_t>(n_blocks * DSF_BLOCK_SIZE, + audio_format.sample_rate); tag_handler_invoke_duration(handler, handler_ctx, songtime); #ifdef HAVE_ID3TAG diff --git a/src/decoder/DsfDecoderPlugin.hxx b/src/decoder/plugins/DsfDecoderPlugin.hxx index 921c94698..02bea0b5c 100644 --- a/src/decoder/DsfDecoderPlugin.hxx +++ b/src/decoder/plugins/DsfDecoderPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/FaadDecoderPlugin.cxx b/src/decoder/plugins/FaadDecoderPlugin.cxx index ae1181b4c..add23aaa4 100644 --- a/src/decoder/FaadDecoderPlugin.cxx +++ b/src/decoder/plugins/FaadDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,11 +19,12 @@ #include "config.h" #include "FaadDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "DecoderBuffer.hxx" -#include "InputStream.hxx" +#include "../DecoderAPI.hxx" +#include "../DecoderBuffer.hxx" +#include "input/InputStream.hxx" #include "CheckAudioFormat.hxx" #include "tag/TagHandler.hxx" +#include "util/ConstBuffer.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" @@ -62,55 +63,42 @@ adts_check_frame(const unsigned char *data) * found or if not enough data is available. */ static size_t -adts_find_frame(DecoderBuffer *buffer) +adts_find_frame(DecoderBuffer &buffer) { while (true) { - size_t length; - const uint8_t *data = (const uint8_t *) - decoder_buffer_read(buffer, &length); - if (data == nullptr || length < 8) { - /* not enough data yet */ - if (!decoder_buffer_fill(buffer)) - /* failed */ - return 0; - - continue; - } + auto data = ConstBuffer<uint8_t>::FromVoid(buffer.Need(8)); + if (data.IsNull()) + /* failed */ + return 0; /* find the 0xff marker */ - const uint8_t *p = (const uint8_t *)memchr(data, 0xff, length); + const uint8_t *p = (const uint8_t *) + memchr(data.data, 0xff, data.size); if (p == nullptr) { /* no marker - discard the buffer */ - decoder_buffer_clear(buffer); + buffer.Clear(); continue; } - if (p > data) { + if (p > data.data) { /* discard data before 0xff */ - decoder_buffer_consume(buffer, p - data); + buffer.Consume(p - data.data); continue; } /* is it a frame? */ - const size_t frame_length = adts_check_frame(data); + const size_t frame_length = adts_check_frame(data.data); if (frame_length == 0) { /* it's just some random 0xff byte; discard it and continue searching */ - decoder_buffer_consume(buffer, 1); + buffer.Consume(1); continue; } - if (length < frame_length) { - /* available buffer size is smaller than the - frame will be - attempt to read more - data */ - if (!decoder_buffer_fill(buffer)) { - /* not enough data; discard this frame - to prevent a possible buffer - overflow */ - decoder_buffer_clear(buffer); - } - + if (buffer.Need(frame_length).IsNull()) { + /* not enough data; discard this frame to + prevent a possible buffer overflow */ + buffer.Clear(); continue; } @@ -119,38 +107,34 @@ adts_find_frame(DecoderBuffer *buffer) } } -static float -adts_song_duration(DecoderBuffer *buffer) +static SignedSongTime +adts_song_duration(DecoderBuffer &buffer) { - const InputStream &is = decoder_buffer_get_stream(buffer); + const InputStream &is = buffer.GetStream(); const bool estimate = !is.CheapSeeking(); - const auto file_size = is.GetSize(); - if (estimate && file_size <= 0) - return -1; + if (estimate && !is.KnownSize()) + return SignedSongTime::Negative(); unsigned sample_rate = 0; /* Read all frames to ensure correct time and bitrate */ - unsigned frames; - for (frames = 0;; frames++) { + unsigned frames = 0; + for (;; frames++) { const unsigned frame_length = adts_find_frame(buffer); if (frame_length == 0) break; - if (frames == 0) { - size_t buffer_length; - const uint8_t *data = (const uint8_t *) - decoder_buffer_read(buffer, &buffer_length); - assert(data != nullptr); - assert(frame_length <= buffer_length); + auto data = ConstBuffer<uint8_t>::FromVoid(buffer.Read()); + assert(!data.IsEmpty()); + assert(frame_length <= data.size); - sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2]; + sample_rate = adts_sample_rates[(data.data[2] & 0x3c) >> 2]; if (sample_rate == 0) break; } - decoder_buffer_consume(buffer, frame_length); + buffer.Consume(frame_length); if (estimate && frames == 128) { /* if this is a remote file, don't slurp the @@ -160,88 +144,85 @@ adts_song_duration(DecoderBuffer *buffer) have until now */ const auto offset = is.GetOffset() - - decoder_buffer_available(buffer); + - buffer.GetAvailable(); if (offset <= 0) - return -1; + return SignedSongTime::Negative(); + const auto file_size = is.GetSize(); frames = (frames * file_size) / offset; break; } } if (sample_rate == 0) - return -1; - - float frames_per_second = (float)sample_rate / 1024.0; - assert(frames_per_second > 0); + return SignedSongTime::Negative(); - return (float)frames / frames_per_second; + return SignedSongTime::FromScale<uint64_t>(frames * uint64_t(1024), + sample_rate); } -static float -faad_song_duration(DecoderBuffer *buffer, InputStream &is) +static SignedSongTime +faad_song_duration(DecoderBuffer &buffer, InputStream &is) { - const auto size = is.GetSize(); - const size_t fileread = size >= 0 ? size : 0; - - decoder_buffer_fill(buffer); - size_t length; - const uint8_t *data = (const uint8_t *) - decoder_buffer_read(buffer, &length); - if (data == nullptr) - return -1; + auto data = ConstBuffer<uint8_t>::FromVoid(buffer.Need(5)); + if (data.IsNull()) + return SignedSongTime::Negative(); size_t tagsize = 0; - if (length >= 10 && !memcmp(data, "ID3", 3)) { + if (data.size >= 10 && !memcmp(data.data, "ID3", 3)) { /* skip the ID3 tag */ - tagsize = (data[6] << 21) | (data[7] << 14) | - (data[8] << 7) | (data[9] << 0); + tagsize = (data.data[6] << 21) | (data.data[7] << 14) | + (data.data[8] << 7) | (data.data[9] << 0); tagsize += 10; - const bool success = decoder_buffer_skip(buffer, tagsize) && - decoder_buffer_fill(buffer); - if (!success) - return -1; + if (!buffer.Skip(tagsize)) + return SignedSongTime::Negative(); - data = (const uint8_t *)decoder_buffer_read(buffer, &length); - if (data == nullptr) - return -1; + data = ConstBuffer<uint8_t>::FromVoid(buffer.Need(5)); + if (data.IsNull()) + return SignedSongTime::Negative(); } - if (length >= 8 && adts_check_frame(data) > 0) { + if (data.size >= 8 && adts_check_frame(data.data) > 0) { /* obtain the duration from the ADTS header */ if (!is.IsSeekable()) - return -1; + return SignedSongTime::Negative(); + + auto song_length = adts_song_duration(buffer); - float song_length = adts_song_duration(buffer); + is.LockSeek(tagsize, IgnoreError()); - is.LockSeek(tagsize, SEEK_SET, IgnoreError()); - decoder_buffer_clear(buffer); + buffer.Clear(); return song_length; - } else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) { + } else if (data.size >= 5 && memcmp(data.data, "ADIF", 4) == 0) { /* obtain the duration from the ADIF header */ - size_t skip_size = (data[4] & 0x80) ? 9 : 0; - if (8 + skip_size > length) + if (!is.KnownSize()) + return SignedSongTime::Negative(); + + size_t skip_size = (data.data[4] & 0x80) ? 9 : 0; + + if (8 + skip_size > data.size) /* not enough data yet; skip parsing this header */ - return -1; + return SignedSongTime::Negative(); + + unsigned bit_rate = ((data.data[4 + skip_size] & 0x0F) << 19) | + (data.data[5 + skip_size] << 11) | + (data.data[6 + skip_size] << 3) | + (data.data[7 + skip_size] & 0xE0); - unsigned bit_rate = ((data[4 + skip_size] & 0x0F) << 19) | - (data[5 + skip_size] << 11) | - (data[6 + skip_size] << 3) | - (data[7 + skip_size] & 0xE0); + const auto size = is.GetSize(); + if (bit_rate == 0) + return SignedSongTime::Negative(); - if (fileread != 0 && bit_rate != 0) - return fileread * 8.0 / bit_rate; - else - return fileread; + return SongTime::FromScale(size, bit_rate / 8); } else - return -1; + return SignedSongTime::Negative(); } static NeAACDecHandle @@ -264,14 +245,11 @@ faad_decoder_new() * inconsistencies in libfaad. */ static bool -faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer, +faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer &buffer, AudioFormat &audio_format, Error &error) { - - size_t length; - const unsigned char *data = (const unsigned char *) - decoder_buffer_read(buffer, &length); - if (data == nullptr) { + auto data = ConstBuffer<uint8_t>::FromVoid(buffer.Read()); + if (data.IsEmpty()) { error.Set(faad_decoder_domain, "Empty file"); return false; } @@ -280,15 +258,15 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer, unsigned long sample_rate; long nbytes = NeAACDecInit(decoder, /* deconst hack, libfaad requires this */ - const_cast<unsigned char *>(data), - length, + const_cast<unsigned char *>(data.data), + data.size, &sample_rate, &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); @@ -299,87 +277,57 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer, * inconsistencies in libfaad. */ static const void * -faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer *buffer, +faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer &buffer, NeAACDecFrameInfo *frame_info) { - size_t length; - const unsigned char *data = (const unsigned char *) - decoder_buffer_read(buffer, &length); - if (data == nullptr) + 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<unsigned char *>(data), - length); + const_cast<uint8_t *>(data.data), + data.size); } /** - * Get a song file's total playing time in seconds, as a float. - * Returns 0 if the duration is unknown, and a negative value if the - * file is invalid. + * Determine a song file's total playing time. + * + * The first return value specifies whether the file was recognized. + * The second return value is the duration. */ -static float -faad_get_file_time_float(InputStream &is) +static std::pair<bool, SignedSongTime> +faad_get_file_time(InputStream &is) { - DecoderBuffer *const buffer = - decoder_buffer_new(nullptr, is, - FAAD_MIN_STREAMSIZE * MAX_CHANNELS); - - float length = faad_song_duration(buffer, is); - - if (length < 0) { - AudioFormat audio_format; + DecoderBuffer buffer(nullptr, is, + FAAD_MIN_STREAMSIZE * MAX_CHANNELS); + auto duration = faad_song_duration(buffer, is); + bool recognized = !duration.IsNegative(); + if (!recognized) { NeAACDecHandle decoder = faad_decoder_new(); - decoder_buffer_fill(buffer); + buffer.Fill(); + AudioFormat audio_format; if (faad_decoder_init(decoder, buffer, audio_format, IgnoreError())) - length = 0; + recognized = true; NeAACDecClose(decoder); } - decoder_buffer_free(buffer); - - return length; -} - -/** - * Get a song file's total playing time in seconds, as an int. - * Returns 0 if the duration is unknown, and a negative value if the - * file is invalid. - */ -static int -faad_get_file_time(InputStream &is) -{ - float length = faad_get_file_time_float(is); - if (length < 0) - return -1; - - return int(length + 0.5); + return std::make_pair(recognized, duration); } static void -faad_stream_decode(Decoder &mpd_decoder, InputStream &is) +faad_stream_decode(Decoder &mpd_decoder, InputStream &is, + DecoderBuffer &buffer, const NeAACDecHandle decoder) { - DecoderBuffer *const buffer = - decoder_buffer_new(&mpd_decoder, is, - FAAD_MIN_STREAMSIZE * MAX_CHANNELS); - - const float total_time = faad_song_duration(buffer, is); + const auto total_time = faad_song_duration(buffer, is); - /* create the libfaad decoder */ - - const NeAACDecHandle decoder = faad_decoder_new(); - - while (!decoder_buffer_is_full(buffer) && !is.LockIsEOF() && - decoder_get_command(mpd_decoder) == DecoderCommand::NONE) { - adts_find_frame(buffer); - decoder_buffer_fill(buffer); - } + if (adts_find_frame(buffer) == 0) + return; /* initialize it */ @@ -387,8 +335,6 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is) AudioFormat audio_format; if (!faad_decoder_init(decoder, buffer, audio_format, error)) { LogError(error); - NeAACDecClose(decoder); - decoder_buffer_free(buffer); return; } @@ -399,7 +345,7 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is) /* the decoder loop */ DecoderCommand cmd; - uint16_t bit_rate = 0; + unsigned bit_rate = 0; do { /* find the next frame */ @@ -436,7 +382,7 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is) break; } - decoder_buffer_consume(buffer, frame_info.bytesconsumed); + buffer.Consume(frame_info.bytesconsumed); /* update bit rate and position */ @@ -452,22 +398,36 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is) (size_t)frame_info.samples * 2, bit_rate); } while (cmd != DecoderCommand::STOP); +} + +static void +faad_stream_decode(Decoder &mpd_decoder, InputStream &is) +{ + DecoderBuffer buffer(&mpd_decoder, is, + FAAD_MIN_STREAMSIZE * MAX_CHANNELS); + + /* create the libfaad decoder */ + + const NeAACDecHandle decoder = faad_decoder_new(); + + faad_stream_decode(mpd_decoder, is, buffer, decoder); /* cleanup */ NeAACDecClose(decoder); - decoder_buffer_free(buffer); } static bool faad_scan_stream(InputStream &is, const struct tag_handler *handler, void *handler_ctx) { - int file_time = faad_get_file_time(is); - if (file_time < 0) + auto result = faad_get_file_time(is); + if (!result.first) return false; - tag_handler_invoke_duration(handler, handler_ctx, file_time); + if (!result.second.IsNegative()) + tag_handler_invoke_duration(handler, handler_ctx, + SongTime(result.second)); return true; } @@ -476,7 +436,7 @@ static const char *const faad_mime_types[] = { "audio/aac", "audio/aacp", nullptr }; -const struct DecoderPlugin faad_decoder_plugin = { +const DecoderPlugin faad_decoder_plugin = { "faad", nullptr, nullptr, diff --git a/src/decoder/FaadDecoderPlugin.hxx b/src/decoder/plugins/FaadDecoderPlugin.hxx index 817927d5e..968433e9b 100644 --- a/src/decoder/FaadDecoderPlugin.hxx +++ b/src/decoder/plugins/FaadDecoderPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 9a00bf3c4..722f954e2 100644 --- a/src/decoder/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,10 +22,11 @@ #include "config.h" #include "FfmpegDecoderPlugin.hxx" -#include "DecoderAPI.hxx" +#include "lib/ffmpeg/Domain.hxx" +#include "../DecoderAPI.hxx" #include "FfmpegMetaData.hxx" #include "tag/TagHandler.hxx" -#include "InputStream.hxx" +#include "input/InputStream.hxx" #include "CheckAudioFormat.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" @@ -47,8 +48,6 @@ extern "C" { #include <assert.h> #include <string.h> -static constexpr Domain ffmpeg_domain("ffmpeg"); - /* suppress the ffmpeg compatibility macro */ #ifdef SampleFormat #undef SampleFormat @@ -120,13 +119,35 @@ mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence) { AvioStream *stream = (AvioStream *)opaque; - if (whence == AVSEEK_SIZE) - return stream->input.size; + switch (whence) { + case SEEK_SET: + break; + + case SEEK_CUR: + pos += stream->input.GetOffset(); + break; + + case SEEK_END: + if (!stream->input.KnownSize()) + return -1; - if (!stream->input.LockSeek(pos, whence, IgnoreError())) + pos += stream->input.GetSize(); + break; + + case AVSEEK_SIZE: + if (!stream->input.KnownSize()) + return -1; + + return stream->input.GetSize(); + + default: + return -1; + } + + if (!stream->input.LockSeek(pos, IgnoreError())) return -1; - return stream->input.offset; + return stream->input.GetOffset(); } bool @@ -135,7 +156,7 @@ AvioStream::Open() io = avio_alloc_context(buffer, sizeof(buffer), false, this, mpd_ffmpeg_stream_read, nullptr, - input.seekable + input.IsSeekable() ? mpd_ffmpeg_stream_seek : nullptr); return io != nullptr; } @@ -189,11 +210,19 @@ time_from_ffmpeg(int64_t t, const AVRational time_base) / (double)1024; } +template<typename Ratio> +static constexpr AVRational +RatioToAVRational() +{ + return { Ratio::num, Ratio::den }; +} + gcc_const static int64_t -time_to_ffmpeg(double t, const AVRational time_base) +time_to_ffmpeg(SongTime t, const AVRational time_base) { - return av_rescale_q((int64_t)(t * 1024), (AVRational){1, 1024}, + return av_rescale_q(t.count(), + RatioToAVRational<SongTime::period>(), time_base); } @@ -345,6 +374,7 @@ ffmpeg_sample_format(enum AVSampleFormat sample_fmt) case AV_SAMPLE_FMT_S32P: return SampleFormat::S32; + case AV_SAMPLE_FMT_FLT: case AV_SAMPLE_FMT_FLTP: return SampleFormat::FLOAT; @@ -394,7 +424,7 @@ ffmpeg_probe(Decoder *decoder, InputStream &is) avpd.buf = buffer; avpd.buf_size = nbytes; - avpd.filename = is.uri.c_str(); + avpd.filename = is.GetURI(); #ifdef AVPROBE_SCORE_MIME #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(56, 5, 1) @@ -430,7 +460,7 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) //ffmpeg works with ours "fileops" helper AVFormatContext *format_context = nullptr; if (mpd_ffmpeg_open_input(&format_context, stream.io, - input.uri.c_str(), + input.GetURI(), input_format) != 0) { LogError(ffmpeg_domain, "Open failed"); return; @@ -477,8 +507,11 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) const SampleFormat sample_format = ffmpeg_sample_format(codec_context->sample_fmt); - if (sample_format == SampleFormat::UNDEFINED) + if (sample_format == SampleFormat::UNDEFINED) { + // (error message already done by ffmpeg_sample_format()) + avformat_close_input(&format_context); return; + } Error error; AudioFormat audio_format; @@ -503,12 +536,14 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) return; } - int total_time = format_context->duration != (int64_t)AV_NOPTS_VALUE - ? format_context->duration / AV_TIME_BASE - : 0; + const SignedSongTime total_time = + format_context->duration != (int64_t)AV_NOPTS_VALUE + ? SignedSongTime::FromScale<uint64_t>(format_context->duration, + AV_TIME_BASE) + : SignedSongTime::Negative(); decoder_initialized(decoder, audio_format, - input.seekable, total_time); + input.IsSeekable(), total_time); #if LIBAVUTIL_VERSION_MAJOR >= 53 AVFrame *frame = av_frame_alloc(); @@ -544,7 +579,7 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) if (cmd == DecoderCommand::SEEK) { int64_t where = - time_to_ffmpeg(decoder_seek_where(decoder), + time_to_ffmpeg(decoder_seek_time(decoder), av_stream->time_base) + start_time_fallback(*av_stream); @@ -585,7 +620,7 @@ ffmpeg_scan_stream(InputStream &is, return false; AVFormatContext *f = nullptr; - if (mpd_ffmpeg_open_input(&f, stream.io, is.uri.c_str(), + if (mpd_ffmpeg_open_input(&f, stream.io, is.GetURI(), input_format) != 0) return false; @@ -596,9 +631,12 @@ ffmpeg_scan_stream(InputStream &is, return false; } - if (f->duration != (int64_t)AV_NOPTS_VALUE) - tag_handler_invoke_duration(handler, handler_ctx, - f->duration / AV_TIME_BASE); + if (f->duration != (int64_t)AV_NOPTS_VALUE) { + const auto duration = + SongTime::FromScale<uint64_t>(f->duration, + AV_TIME_BASE); + tag_handler_invoke_duration(handler, handler_ctx, duration); + } ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx); int idx = ffmpeg_find_audio_stream(f); diff --git a/src/decoder/FfmpegDecoderPlugin.hxx b/src/decoder/plugins/FfmpegDecoderPlugin.hxx index 23bf74fce..0a3e78e4b 100644 --- a/src/decoder/FfmpegDecoderPlugin.hxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/FfmpegMetaData.cxx b/src/decoder/plugins/FfmpegMetaData.cxx index 6e92b4a13..a39466945 100644 --- a/src/decoder/FfmpegMetaData.cxx +++ b/src/decoder/plugins/FfmpegMetaData.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/FfmpegMetaData.hxx b/src/decoder/plugins/FfmpegMetaData.hxx index 998cdf5a8..5eb41db68 100644 --- a/src/decoder/FfmpegMetaData.hxx +++ b/src/decoder/plugins/FfmpegMetaData.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/FlacCommon.cxx b/src/decoder/plugins/FlacCommon.cxx index e4b906c12..e86f85569 100644 --- a/src/decoder/FlacCommon.cxx +++ b/src/decoder/plugins/FlacCommon.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,14 +25,10 @@ #include "FlacCommon.hxx" #include "FlacMetadata.hxx" #include "FlacPcm.hxx" -#include "MixRampInfo.hxx" #include "CheckAudioFormat.hxx" #include "util/Error.hxx" -#include "util/Domain.hxx" #include "Log.hxx" -#include <assert.h> - flac_data::flac_data(Decoder &_decoder, InputStream &_input_stream) :FlacInput(_input_stream, &_decoder), @@ -102,13 +98,14 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block, break; case FLAC__METADATA_TYPE_VORBIS_COMMENT: - if (flac_parse_replay_gain(rgi, block)) + if (flac_parse_replay_gain(rgi, block->data.vorbis_comment)) decoder_replay_gain(data->decoder, &rgi); - decoder_mixramp(data->decoder, flac_parse_mixramp(block)); + decoder_mixramp(data->decoder, + flac_parse_mixramp(block->data.vorbis_comment)); - flac_vorbis_comments_to_tag(data->tag, - &block->data.vorbis_comment); + data->tag = flac_vorbis_comments_to_tag(&block->data.vorbis_comment); + break; default: break; @@ -140,10 +137,12 @@ flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header) data->frame_size = data->audio_format.GetFrameSize(); + const auto duration = SongTime::FromScale<uint64_t>(data->total_frames, + data->audio_format.sample_rate); + decoder_initialized(data->decoder, data->audio_format, - data->input_stream.seekable, - (float)data->total_frames / - (float)data->audio_format.sample_rate); + data->input_stream.IsSeekable(), + duration); data->initialized = true; diff --git a/src/decoder/FlacCommon.hxx b/src/decoder/plugins/FlacCommon.hxx index de000dfa1..34ce0a3fc 100644 --- a/src/decoder/FlacCommon.hxx +++ b/src/decoder/plugins/FlacCommon.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,11 +25,10 @@ #define MPD_FLAC_COMMON_HXX #include "FlacInput.hxx" -#include "DecoderAPI.hxx" +#include "../DecoderAPI.hxx" #include "pcm/PcmBuffer.hxx" #include <FLAC/stream_decoder.h> -#include <FLAC/metadata.h> struct flac_data : public FlacInput { PcmBuffer buffer; diff --git a/src/decoder/FlacDecoderPlugin.cxx b/src/decoder/plugins/FlacDecoderPlugin.cxx index 1b5734434..eea813401 100644 --- a/src/decoder/FlacDecoderPlugin.cxx +++ b/src/decoder/plugins/FlacDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,13 +23,10 @@ #include "FlacCommon.hxx" #include "FlacMetadata.hxx" #include "OggCodec.hxx" +#include "fs/Path.hxx" #include "util/Error.hxx" #include "Log.hxx" -#include <glib.h> - -#include <assert.h> - #if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 #error libFLAC is too old #endif @@ -83,11 +80,11 @@ flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame, } static bool -flac_scan_file(const char *file, +flac_scan_file(Path path_fs, const struct tag_handler *handler, void *handler_ctx) { FlacMetadataChain chain; - if (!chain.Read(file)) { + if (!chain.Read(path_fs.c_str())) { FormatDebug(flac_domain, "Failed to read FLAC tags: %s", chain.GetStatusString()); @@ -147,14 +144,18 @@ flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd, if (data->initialized) { /* done */ + + const auto duration2 = + SongTime::FromScale<uint64_t>(data->total_frames, + data->audio_format.sample_rate); + decoder_initialized(data->decoder, data->audio_format, - data->input_stream.seekable, - (float)data->total_frames / - (float)data->audio_format.sample_rate); + data->input_stream.IsSeekable(), + duration2); return true; } - if (data->input_stream.seekable) + if (data->input_stream.IsSeekable()) /* allow the workaround below only for nonseekable streams*/ return false; @@ -184,8 +185,7 @@ flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, if (cmd == DecoderCommand::SEEK) { FLAC__uint64 seek_sample = t_start + - decoder_seek_where(decoder) * - data->audio_format.sample_rate; + decoder_seek_where_frame(decoder); if (seek_sample >= t_start && (t_end == 0 || seek_sample <= t_end) && FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) { @@ -297,11 +297,11 @@ oggflac_init(gcc_unused const config_param ¶m) } static bool -oggflac_scan_file(const char *file, +oggflac_scan_file(Path path_fs, const struct tag_handler *handler, void *handler_ctx) { FlacMetadataChain chain; - if (!chain.ReadOgg(file)) { + if (!chain.ReadOgg(path_fs.c_str())) { FormatDebug(flac_domain, "Failed to read OggFLAC tags: %s", chain.GetStatusString()); diff --git a/src/decoder/FlacDecoderPlugin.h b/src/decoder/plugins/FlacDecoderPlugin.h index 936423fbf..fcdecf869 100644 --- a/src/decoder/FlacDecoderPlugin.h +++ b/src/decoder/plugins/FlacDecoderPlugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/FlacDomain.cxx b/src/decoder/plugins/FlacDomain.cxx index 5858004de..fc5cc5498 100644 --- a/src/decoder/FlacDomain.cxx +++ b/src/decoder/plugins/FlacDomain.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/FlacDomain.hxx b/src/decoder/plugins/FlacDomain.hxx index cf357332f..a06c6c6b4 100644 --- a/src/decoder/FlacDomain.hxx +++ b/src/decoder/plugins/FlacDomain.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/FlacIOHandle.cxx b/src/decoder/plugins/FlacIOHandle.cxx index b471ecf64..0dd895798 100644 --- a/src/decoder/FlacIOHandle.cxx +++ b/src/decoder/plugins/FlacIOHandle.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,6 +23,7 @@ #include "Compiler.h" #include <errno.h> +#include <stdio.h> static size_t FlacIORead(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle) @@ -62,12 +63,31 @@ FlacIORead(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle) } static int -FlacIOSeek(FLAC__IOHandle handle, FLAC__int64 offset, int whence) +FlacIOSeek(FLAC__IOHandle handle, FLAC__int64 _offset, int whence) { InputStream *is = (InputStream *)handle; - Error error; - return is->LockSeek(offset, whence, error) ? 0 : -1; + offset_type offset = _offset; + switch (whence) { + case SEEK_SET: + break; + + case SEEK_CUR: + offset += is->GetOffset(); + break; + + case SEEK_END: + if (!is->KnownSize()) + return -1; + + offset += is->GetSize(); + break; + + default: + return -1; + } + + return is->LockSeek(offset, IgnoreError()) ? 0 : -1; } static FLAC__int64 @@ -75,7 +95,7 @@ FlacIOTell(FLAC__IOHandle handle) { InputStream *is = (InputStream *)handle; - return is->offset; + return is->GetOffset(); } static int diff --git a/src/decoder/FlacIOHandle.hxx b/src/decoder/plugins/FlacIOHandle.hxx index b6e563fa3..90acc66af 100644 --- a/src/decoder/FlacIOHandle.hxx +++ b/src/decoder/plugins/FlacIOHandle.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,7 +21,7 @@ #define MPD_FLAC_IO_HANDLE_HXX #include "Compiler.h" -#include "InputStream.hxx" +#include "input/InputStream.hxx" #include <FLAC/callback.h> @@ -37,7 +37,7 @@ ToFlacIOHandle(InputStream &is) static inline const FLAC__IOCallbacks & GetFlacIOCallbacks(const InputStream &is) { - return is.seekable + return is.IsSeekable() ? flac_io_callbacks_seekable : flac_io_callbacks; } diff --git a/src/decoder/FlacInput.cxx b/src/decoder/plugins/FlacInput.cxx index ce193101d..5b4c3104d 100644 --- a/src/decoder/FlacInput.cxx +++ b/src/decoder/plugins/FlacInput.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,8 +20,8 @@ #include "config.h" #include "FlacInput.hxx" #include "FlacDomain.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" #include "util/Error.hxx" #include "Log.hxx" #include "Compiler.h" @@ -47,11 +47,11 @@ FlacInput::Read(FLAC__byte buffer[], size_t *bytes) FLAC__StreamDecoderSeekStatus FlacInput::Seek(FLAC__uint64 absolute_byte_offset) { - if (!input_stream.seekable) + if (!input_stream.IsSeekable()) return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; ::Error error; - if (!input_stream.LockSeek(absolute_byte_offset, SEEK_SET, error)) { + if (!input_stream.LockSeek(absolute_byte_offset, error)) { LogError(error); return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; } @@ -62,20 +62,20 @@ FlacInput::Seek(FLAC__uint64 absolute_byte_offset) FLAC__StreamDecoderTellStatus FlacInput::Tell(FLAC__uint64 *absolute_byte_offset) { - if (!input_stream.seekable) + if (!input_stream.IsSeekable()) return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED; - *absolute_byte_offset = (FLAC__uint64)input_stream.offset; + *absolute_byte_offset = (FLAC__uint64)input_stream.GetOffset(); return FLAC__STREAM_DECODER_TELL_STATUS_OK; } FLAC__StreamDecoderLengthStatus FlacInput::Length(FLAC__uint64 *stream_length) { - if (input_stream.size < 0) + if (!input_stream.KnownSize()) return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; - *stream_length = (FLAC__uint64)input_stream.size; + *stream_length = (FLAC__uint64)input_stream.GetSize(); return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; } diff --git a/src/decoder/FlacInput.hxx b/src/decoder/plugins/FlacInput.hxx index ddd5649f8..427abccb4 100644 --- a/src/decoder/FlacInput.hxx +++ b/src/decoder/plugins/FlacInput.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,7 +23,7 @@ #include <FLAC/stream_decoder.h> struct Decoder; -struct InputStream; +class InputStream; /** * This class wraps an #InputStream in libFLAC stream decoder diff --git a/src/decoder/FlacMetadata.cxx b/src/decoder/plugins/FlacMetadata.cxx index 17cc4cd8d..03e276dce 100644 --- a/src/decoder/FlacMetadata.cxx +++ b/src/decoder/plugins/FlacMetadata.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,122 +21,56 @@ #include "FlacMetadata.hxx" #include "XiphTags.hxx" #include "MixRampInfo.hxx" -#include "tag/Tag.hxx" #include "tag/TagHandler.hxx" #include "tag/TagTable.hxx" #include "tag/TagBuilder.hxx" +#include "tag/Tag.hxx" +#include "tag/VorbisComment.hxx" +#include "tag/ReplayGain.hxx" +#include "tag/MixRamp.hxx" #include "ReplayGainInfo.hxx" #include "util/ASCII.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -static bool -flac_find_float_comment(const FLAC__StreamMetadata *block, - const char *cmnt, float *fl) -{ - int offset; - size_t pos; - int len; - unsigned char tmp, *p; - - offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, - cmnt); - if (offset < 0) - return false; - - pos = strlen(cmnt) + 1; /* 1 is for '=' */ - len = block->data.vorbis_comment.comments[offset].length - pos; - if (len <= 0) - return false; - - p = &block->data.vorbis_comment.comments[offset].entry[pos]; - tmp = p[len]; - p[len] = '\0'; - *fl = (float)atof((char *)p); - p[len] = tmp; - - return true; -} +#include "util/SplitString.hxx" bool flac_parse_replay_gain(ReplayGainInfo &rgi, - const FLAC__StreamMetadata *block) + const FLAC__StreamMetadata_VorbisComment &vc) { rgi.Clear(); bool found = false; - if (flac_find_float_comment(block, "replaygain_album_gain", - &rgi.tuples[REPLAY_GAIN_ALBUM].gain)) - found = true; - if (flac_find_float_comment(block, "replaygain_album_peak", - &rgi.tuples[REPLAY_GAIN_ALBUM].peak)) - found = true; - if (flac_find_float_comment(block, "replaygain_track_gain", - &rgi.tuples[REPLAY_GAIN_TRACK].gain)) - found = true; - if (flac_find_float_comment(block, "replaygain_track_peak", - &rgi.tuples[REPLAY_GAIN_TRACK].peak)) - found = true; - return found; -} + const auto *comments = vc.comments; + for (FLAC__uint32 i = 0, n = vc.num_comments; i < n; ++i) + if (ParseReplayGainVorbis(rgi, + (const char *)comments[i].entry)) + found = true; -gcc_pure -static std::string -flac_find_string_comment(const FLAC__StreamMetadata *block, const char *cmnt) -{ - int offset; - size_t pos; - int len; - const unsigned char *p; - - offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, - cmnt); - if (offset < 0) - return std::string(); - - pos = strlen(cmnt) + 1; /* 1 is for '=' */ - len = block->data.vorbis_comment.comments[offset].length - pos; - if (len <= 0) - return std::string(); - - p = &block->data.vorbis_comment.comments[offset].entry[pos]; - return std::string((const char *)p, len); + return found; } MixRampInfo -flac_parse_mixramp(const FLAC__StreamMetadata *block) +flac_parse_mixramp(const FLAC__StreamMetadata_VorbisComment &vc) { MixRampInfo mix_ramp; - mix_ramp.SetStart(flac_find_string_comment(block, "mixramp_start")); - mix_ramp.SetEnd(flac_find_string_comment(block, "mixramp_end")); + + const auto *comments = vc.comments; + for (FLAC__uint32 i = 0, n = vc.num_comments; i < n; ++i) + ParseMixRampVorbis(mix_ramp, + (const char *)comments[i].entry); + return mix_ramp; } /** * Checks if the specified name matches the entry's name, and if yes, - * returns the comment value (not null-temrinated). + * returns the comment value; */ static const char * flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const char *name, size_t *length_r) + const char *name) { - size_t name_length = strlen(name); - const char *comment = (const char*)entry->entry; - - if (entry->length <= name_length || - !StringEqualsCaseASCII(comment, name, name_length)) - return nullptr; - - if (comment[name_length] == '=') { - *length_r = entry->length - name_length - 1; - return comment + name_length + 1; - } - - return nullptr; + return vorbis_comment_value((const char *)entry->entry, name); } /** @@ -148,14 +82,9 @@ flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, const char *name, TagType tag_type, const struct tag_handler *handler, void *handler_ctx) { - const char *value; - size_t value_length; - - value = flac_comment_value(entry, name, &value_length); + const char *value = flac_comment_value(entry, name); if (value != nullptr) { - char *p = g_strndup(value, value_length); - tag_handler_invoke_tag(handler, handler_ctx, tag_type, p); - g_free(p); + tag_handler_invoke_tag(handler, handler_ctx, tag_type, value); return true; } @@ -167,16 +96,12 @@ flac_scan_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, const struct tag_handler *handler, void *handler_ctx) { if (handler->pair != nullptr) { - char *name = g_strdup((const char*)entry->entry); - char *value = strchr(name, '='); - - if (value != nullptr && value > name) { - *value++ = 0; + const char *comment = (const char *)entry->entry; + const SplitString split(comment, '='); + if (split.IsDefined() && !split.IsEmpty()) tag_handler_invoke_pair(handler, handler_ctx, - name, value); - } - - g_free(name); + split.GetFirst(), + split.GetSecond()); } for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) @@ -200,6 +125,16 @@ flac_scan_comments(const FLAC__StreamMetadata_VorbisComment *comment, handler, handler_ctx); } +gcc_pure +static inline SongTime +flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info) +{ + assert(stream_info->sample_rate > 0); + + return SongTime::FromScale<uint64_t>(stream_info->total_samples, + stream_info->sample_rate); +} + void flac_scan_metadata(const FLAC__StreamMetadata *block, const struct tag_handler *handler, void *handler_ctx) @@ -221,13 +156,12 @@ flac_scan_metadata(const FLAC__StreamMetadata *block, } } -void -flac_vorbis_comments_to_tag(Tag &tag, - const FLAC__StreamMetadata_VorbisComment *comment) +Tag +flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment) { TagBuilder tag_builder; flac_scan_comments(comment, &add_tag_handler, &tag_builder); - tag_builder.Commit(tag); + return tag_builder.Commit(); } void diff --git a/src/decoder/FlacMetadata.hxx b/src/decoder/plugins/FlacMetadata.hxx index 96c61b8e6..d791fa34e 100644 --- a/src/decoder/FlacMetadata.hxx +++ b/src/decoder/plugins/FlacMetadata.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,6 +27,7 @@ #include <assert.h> +struct tag_handler; class MixRampInfo; class FlacMetadataChain { @@ -81,7 +82,7 @@ public: return FLAC__Metadata_ChainStatusString[GetStatus()]; } - void Scan(const struct tag_handler *handler, void *handler_ctx); + void Scan(const tag_handler *handler, void *handler_ctx); }; class FLACMetadataIterator { @@ -110,32 +111,21 @@ public: } }; -struct tag_handler; struct Tag; struct ReplayGainInfo; -static inline unsigned -flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info) -{ - assert(stream_info->sample_rate > 0); - - return (stream_info->total_samples + stream_info->sample_rate - 1) / - stream_info->sample_rate; -} - bool flac_parse_replay_gain(ReplayGainInfo &rgi, - const FLAC__StreamMetadata *block); + const FLAC__StreamMetadata_VorbisComment &vc); MixRampInfo -flac_parse_mixramp(const FLAC__StreamMetadata *block); +flac_parse_mixramp(const FLAC__StreamMetadata_VorbisComment &vc); -void -flac_vorbis_comments_to_tag(Tag &tag, - const FLAC__StreamMetadata_VorbisComment *comment); +Tag +flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment); void flac_scan_metadata(const FLAC__StreamMetadata *block, - const struct tag_handler *handler, void *handler_ctx); + const tag_handler *handler, void *handler_ctx); #endif diff --git a/src/decoder/FlacPcm.cxx b/src/decoder/plugins/FlacPcm.cxx index 569879371..311500f26 100644 --- a/src/decoder/FlacPcm.cxx +++ b/src/decoder/plugins/FlacPcm.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/FlacPcm.hxx b/src/decoder/plugins/FlacPcm.hxx index 6cb2d5062..30c318725 100644 --- a/src/decoder/FlacPcm.hxx +++ b/src/decoder/plugins/FlacPcm.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/FluidsynthDecoderPlugin.cxx b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx index fa946f219..f19ac5bf4 100644 --- a/src/decoder/FluidsynthDecoderPlugin.cxx +++ b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,8 +19,9 @@ #include "config.h" #include "FluidsynthDecoderPlugin.hxx" -#include "DecoderAPI.hxx" +#include "../DecoderAPI.hxx" #include "CheckAudioFormat.hxx" +#include "fs/Path.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "util/Macros.hxx" @@ -92,7 +93,7 @@ fluidsynth_init(const config_param ¶m) } static void -fluidsynth_file_decode(Decoder &decoder, const char *path_fs) +fluidsynth_file_decode(Decoder &decoder, Path path_fs) { char setting_sample_rate[] = "synth.sample-rate"; /* @@ -141,7 +142,7 @@ fluidsynth_file_decode(Decoder &decoder, const char *path_fs) return; } - ret = fluid_player_add(player, path_fs); + ret = fluid_player_add(player, path_fs.c_str()); if (ret != 0) { LogWarning(fluidsynth_domain, "fluid_player_add() failed"); delete_fluid_player(player); @@ -165,7 +166,8 @@ fluidsynth_file_decode(Decoder &decoder, const char *path_fs) MPD core */ const AudioFormat audio_format(sample_rate, SampleFormat::S16, 2); - decoder_initialized(decoder, audio_format, false, -1); + decoder_initialized(decoder, audio_format, false, + SignedSongTime::Negative()); DecoderCommand cmd; while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) { @@ -198,11 +200,11 @@ fluidsynth_file_decode(Decoder &decoder, const char *path_fs) } static bool -fluidsynth_scan_file(const char *file, +fluidsynth_scan_file(Path path_fs, gcc_unused const struct tag_handler *handler, gcc_unused void *handler_ctx) { - return fluid_is_midifile(file); + return fluid_is_midifile(path_fs.c_str()); } static const char *const fluidsynth_suffixes[] = { diff --git a/src/decoder/FluidsynthDecoderPlugin.hxx b/src/decoder/plugins/FluidsynthDecoderPlugin.hxx index 9771898a5..cd8ec2d62 100644 --- a/src/decoder/FluidsynthDecoderPlugin.hxx +++ b/src/decoder/plugins/FluidsynthDecoderPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/GmeDecoderPlugin.cxx b/src/decoder/plugins/GmeDecoderPlugin.cxx index 9c9b19478..cc6ce5e5d 100644 --- a/src/decoder/GmeDecoderPlugin.cxx +++ b/src/decoder/plugins/GmeDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,9 +19,11 @@ #include "config.h" #include "GmeDecoderPlugin.hxx" -#include "DecoderAPI.hxx" +#include "../DecoderAPI.hxx" #include "CheckAudioFormat.hxx" #include "tag/TagHandler.hxx" +#include "fs/Path.hxx" +#include "util/Alloc.hxx" #include "util/FormatString.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" @@ -50,10 +52,10 @@ static constexpr unsigned GME_BUFFER_SAMPLES = * suffix */ static char * -get_container_name(const char *path_fs) +get_container_name(Path path_fs) { - const char *subtune_suffix = uri_get_suffix(path_fs); - char *path_container = g_strdup(path_fs); + const char *subtune_suffix = uri_get_suffix(path_fs.c_str()); + char *path_container = xstrdup(path_fs.c_str()); char pat[64]; snprintf(pat, sizeof(pat), "%s%s", @@ -79,9 +81,9 @@ get_container_name(const char *path_fs) * is appended. */ static int -get_song_num(const char *path_fs) +get_song_num(Path path_fs) { - const char *subtune_suffix = uri_get_suffix(path_fs); + const char *subtune_suffix = uri_get_suffix(path_fs.c_str()); char pat[64]; snprintf(pat, sizeof(pat), "%s%s", @@ -90,8 +92,8 @@ get_song_num(const char *path_fs) GPatternSpec *path_with_subtune = g_pattern_spec_new(pat); if (g_pattern_match(path_with_subtune, - strlen(path_fs), path_fs, nullptr)) { - char *sub = g_strrstr(path_fs, "/" SUBTUNE_PREFIX); + path_fs.length(), path_fs.data(), nullptr)) { + char *sub = g_strrstr(path_fs.c_str(), "/" SUBTUNE_PREFIX); g_pattern_spec_free(path_with_subtune); if (!sub) return 0; @@ -107,10 +109,11 @@ get_song_num(const char *path_fs) } static char * -gme_container_scan(const char *path_fs, const unsigned int tnum) +gme_container_scan(Path path_fs, const unsigned int tnum) { Music_Emu *emu; - const char *gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE); + const char *gme_err = gme_open_file(path_fs.c_str(), &emu, + GME_SAMPLE_RATE); if (gme_err != nullptr) { LogWarning(gme_domain, gme_err); return nullptr; @@ -122,7 +125,7 @@ gme_container_scan(const char *path_fs, const unsigned int tnum) if (num_songs < 2) return nullptr; - const char *subtune_suffix = uri_get_suffix(path_fs); + const char *subtune_suffix = uri_get_suffix(path_fs.c_str()); if (tnum <= num_songs){ return FormatNew(SUBTUNE_PREFIX "%03u.%s", tnum, subtune_suffix); @@ -131,14 +134,14 @@ gme_container_scan(const char *path_fs, const unsigned int tnum) } static void -gme_file_decode(Decoder &decoder, const char *path_fs) +gme_file_decode(Decoder &decoder, Path path_fs) { char *path_container = get_container_name(path_fs); Music_Emu *emu; const char *gme_err = gme_open_file(path_container, &emu, GME_SAMPLE_RATE); - g_free(path_container); + free(path_container); if (gme_err != nullptr) { LogWarning(gme_domain, gme_err); return; @@ -153,9 +156,9 @@ gme_file_decode(Decoder &decoder, const char *path_fs) return; } - const float song_len = ti->length > 0 - ? ti->length / 1000.0 - : -1.0; + const SignedSongTime song_len = ti->length > 0 + ? SignedSongTime::FromMS(ti->length) + : SignedSongTime::Negative(); /* initialize the MPD decoder */ @@ -191,8 +194,8 @@ gme_file_decode(Decoder &decoder, const char *path_fs) cmd = decoder_data(decoder, nullptr, buf, sizeof(buf), 0); if (cmd == DecoderCommand::SEEK) { - float where = decoder_seek_where(decoder); - gme_err = gme_seek(emu, int(where * 1000)); + unsigned where = decoder_seek_time(decoder).ToMS(); + gme_err = gme_seek(emu, where); if (gme_err != nullptr) LogWarning(gme_domain, gme_err); decoder_command_finished(decoder); @@ -207,7 +210,7 @@ gme_file_decode(Decoder &decoder, const char *path_fs) } static bool -gme_scan_file(const char *path_fs, +gme_scan_file(Path path_fs, const struct tag_handler *handler, void *handler_ctx) { char *path_container = get_container_name(path_fs); @@ -215,7 +218,7 @@ gme_scan_file(const char *path_fs, Music_Emu *emu; const char *gme_err = gme_open_file(path_container, &emu, GME_SAMPLE_RATE); - g_free(path_container); + free(path_container); if (gme_err != nullptr) { LogWarning(gme_domain, gme_err); return false; @@ -235,7 +238,7 @@ gme_scan_file(const char *path_fs, if (ti->length > 0) tag_handler_invoke_duration(handler, handler_ctx, - ti->length / 1000); + SongTime::FromMS(ti->length)); if (ti->song != nullptr) { if (gme_track_count(emu) > 1) { diff --git a/src/decoder/GmeDecoderPlugin.hxx b/src/decoder/plugins/GmeDecoderPlugin.hxx index e46dc766a..f4885b6e4 100644 --- a/src/decoder/GmeDecoderPlugin.hxx +++ b/src/decoder/plugins/GmeDecoderPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/MadDecoderPlugin.cxx b/src/decoder/plugins/MadDecoderPlugin.cxx index 6f619b34b..de6c9b127 100644 --- a/src/decoder/MadDecoderPlugin.cxx +++ b/src/decoder/plugins/MadDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,33 +19,33 @@ #include "config.h" #include "MadDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "ConfigGlobal.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "config/ConfigGlobal.hxx" #include "tag/TagId3.hxx" #include "tag/TagRva2.hxx" #include "tag/TagHandler.hxx" +#include "tag/ReplayGain.hxx" +#include "tag/MixRamp.hxx" #include "CheckAudioFormat.hxx" +#include "util/StringUtil.hxx" #include "util/ASCII.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" -#include <assert.h> -#include <unistd.h> -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <glib.h> #include <mad.h> #ifdef HAVE_ID3TAG #include <id3tag.h> #endif -#define FRAMES_CUSHION 2000 +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> -#define READ_BUFFER_SIZE 40960 +static constexpr unsigned long FRAMES_CUSHION = 2000; enum mp3_action { DECODE_SKIP = -3, @@ -61,22 +61,27 @@ enum muteframe { }; /* the number of samples of silence the decoder inserts at start */ -#define DECODERDELAY 529 +static constexpr unsigned DECODERDELAY = 529; -#define DEFAULT_GAPLESS_MP3_PLAYBACK true +static constexpr bool DEFAULT_GAPLESS_MP3_PLAYBACK = true; static constexpr Domain mad_domain("mad"); static bool gapless_playback; +gcc_const +static SongTime +ToSongTime(mad_timer_t t) +{ + return SongTime::FromMS(mad_timer_count(t, MAD_UNITS_MILLISECONDS)); +} + static inline int32_t mad_fixed_to_24_sample(mad_fixed_t sample) { - enum { - bits = 24, - MIN = -MAD_F_ONE, - MAX = MAD_F_ONE - 1 - }; + static constexpr unsigned bits = 24; + static constexpr mad_fixed_t MIN = -MAD_F_ONE; + static constexpr mad_fixed_t MAX = MAD_F_ONE - 1; /* round */ sample = sample + (1L << (MAD_F_FRACBITS - bits)); @@ -96,12 +101,9 @@ mad_fixed_to_24_buffer(int32_t *dest, const struct mad_synth *synth, unsigned int start, unsigned int end, unsigned int num_channels) { - unsigned int i, c; - - for (i = start; i < end; ++i) { - for (c = 0; c < num_channels; ++c) + for (unsigned i = start; i < end; ++i) + for (unsigned c = 0; c < num_channels; ++c) *dest++ = mad_fixed_to_24_sample(synth->pcm.samples[c][i]); - } } static bool @@ -112,18 +114,19 @@ mp3_plugin_init(gcc_unused const config_param ¶m) return true; } -#define MP3_DATA_OUTPUT_BUFFER_SIZE 2048 - struct MadDecoder { + static constexpr size_t READ_BUFFER_SIZE = 40960; + static constexpr size_t MP3_DATA_OUTPUT_BUFFER_SIZE = 2048; + struct mad_stream stream; struct mad_frame frame; struct mad_synth synth; mad_timer_t timer; unsigned char input_buffer[READ_BUFFER_SIZE]; int32_t output_buffer[MP3_DATA_OUTPUT_BUFFER_SIZE]; - float total_time; - float elapsed_time; - float seek_where; + SignedSongTime total_time; + SongTime elapsed_time; + SongTime seek_time; enum muteframe mute_frame; long *frame_offsets; mad_timer_t *times; @@ -135,7 +138,6 @@ struct MadDecoder { unsigned int drop_start_samples; unsigned int drop_end_samples; bool found_replay_gain; - bool found_xing; bool found_first_frame; bool decoded_first_frame; unsigned long bit_rate; @@ -153,10 +155,10 @@ struct MadDecoder { enum mp3_action DecodeNextFrame(); gcc_pure - InputStream::offset_type ThisFrameOffset() const; + offset_type ThisFrameOffset() const; gcc_pure - InputStream::offset_type RestIncludingThisFrame() const; + offset_type RestIncludingThisFrame() const; /** * Attempt to calulcate the length of the song from filesize @@ -166,7 +168,7 @@ struct MadDecoder { bool DecodeFirstFrame(Tag **tag); gcc_pure - long TimeToFrame(double t) const; + long TimeToFrame(SongTime t) const; void UpdateTimerNextFrame(); @@ -192,7 +194,7 @@ MadDecoder::MadDecoder(Decoder *_decoder, highest_frame(0), max_frames(0), current_frame(0), drop_start_frames(0), drop_end_frames(0), drop_start_samples(0), drop_end_samples(0), - found_replay_gain(false), found_xing(false), + found_replay_gain(false), found_first_frame(false), decoded_first_frame(false), decoder(_decoder), input_stream(_input_stream), layer(mad_layer(0)) @@ -208,7 +210,7 @@ inline bool MadDecoder::Seek(long offset) { Error error; - if (!input_stream.LockSeek(offset, SEEK_SET, error)) + if (!input_stream.LockSeek(offset, error)) return false; mad_stream_buffer(&stream, input_buffer, 0); @@ -254,38 +256,24 @@ static bool parse_id3_replay_gain_info(ReplayGainInfo &rgi, struct id3_tag *tag) { - int i; - char *key; - char *value; - struct id3_frame *frame; bool found = false; rgi.Clear(); - for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) { + struct id3_frame *frame; + for (unsigned i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) { if (frame->nfields < 3) continue; - key = (char *) + char *const key = (char *) id3_ucs4_latin1duplicate(id3_field_getstring (&frame->fields[1])); - value = (char *) + char *const value = (char *) id3_ucs4_latin1duplicate(id3_field_getstring (&frame->fields[2])); - if (StringEqualsCaseASCII(key, "replaygain_track_gain")) { - rgi.tuples[REPLAY_GAIN_TRACK].gain = atof(value); - found = true; - } else if (StringEqualsCaseASCII(key, "replaygain_album_gain")) { - rgi.tuples[REPLAY_GAIN_ALBUM].gain = atof(value); - found = true; - } else if (StringEqualsCaseASCII(key, "replaygain_track_peak")) { - rgi.tuples[REPLAY_GAIN_TRACK].peak = atof(value); + if (ParseReplayGainTag(rgi, key, value)) found = true; - } else if (StringEqualsCaseASCII(key, "replaygain_album_peak")) { - rgi.tuples[REPLAY_GAIN_ALBUM].peak = atof(value); - found = true; - } free(key); free(value); @@ -302,29 +290,21 @@ gcc_pure static MixRampInfo parse_id3_mixramp(struct id3_tag *tag) { - int i; - char *key; - char *value; - struct id3_frame *frame; - MixRampInfo result; - for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) { + struct id3_frame *frame; + for (unsigned i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) { if (frame->nfields < 3) continue; - key = (char *) + char *const key = (char *) id3_ucs4_latin1duplicate(id3_field_getstring (&frame->fields[1])); - value = (char *) + char *const value = (char *) id3_ucs4_latin1duplicate(id3_field_getstring (&frame->fields[2])); - if (StringEqualsCaseASCII(key, "mixramp_start")) { - result.SetStart(value); - } else if (StringEqualsCaseASCII(key, "mixramp_end")) { - result.SetEnd(value); - } + ParseMixRampTag(result, key, value); free(key); free(value); @@ -338,34 +318,32 @@ inline void MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag) { #ifdef HAVE_ID3TAG - struct id3_tag *id3_tag = nullptr; - id3_length_t count; - id3_byte_t const *id3_data; id3_byte_t *allocated = nullptr; - count = stream.bufend - stream.this_frame; + const id3_length_t count = stream.bufend - stream.this_frame; + const id3_byte_t *id3_data; if (tagsize <= count) { id3_data = stream.this_frame; mad_stream_skip(&(stream), tagsize); } else { - allocated = (id3_byte_t *)g_malloc(tagsize); + allocated = new id3_byte_t[tagsize]; memcpy(allocated, stream.this_frame, count); mad_stream_skip(&(stream), count); if (!decoder_read_full(decoder, input_stream, allocated + count, tagsize - count)) { LogDebug(mad_domain, "error parsing ID3 tag"); - g_free(allocated); + delete[] allocated; return; } id3_data = allocated; } - id3_tag = id3_tag_parse(id3_data, tagsize); + struct id3_tag *const id3_tag = id3_tag_parse(id3_data, tagsize); if (id3_tag == nullptr) { - g_free(allocated); + delete[] allocated; return; } @@ -390,7 +368,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag) id3_tag_delete(id3_tag); - g_free(allocated); + delete[] allocated; #else /* !HAVE_ID3TAG */ (void)mpd_tag; @@ -426,6 +404,20 @@ id3_tag_query(const void *p0, size_t length) } #endif /* !HAVE_ID3TAG */ +static enum mp3_action +RecoverFrameError(struct mad_stream &stream) +{ + if (MAD_RECOVERABLE(stream.error)) + return DECODE_SKIP; + else if (stream.error == MAD_ERROR_BUFLEN) + return DECODE_CONT; + + FormatWarning(mad_domain, + "unrecoverable frame level error: %s", + mad_stream_errorstr(&stream)); + return DECODE_BREAK; +} + enum mp3_action MadDecoder::DecodeNextFrameHeader(Tag **tag) { @@ -448,18 +440,8 @@ MadDecoder::DecodeNextFrameHeader(Tag **tag) return DECODE_CONT; } } - if (MAD_RECOVERABLE(stream.error)) { - return DECODE_SKIP; - } else { - if (stream.error == MAD_ERROR_BUFLEN) - return DECODE_CONT; - else { - FormatWarning(mad_domain, - "unrecoverable frame level error: %s", - mad_stream_errorstr(&stream)); - return DECODE_BREAK; - } - } + + return RecoverFrameError(stream); } enum mad_layer new_layer = frame.header.layer; @@ -495,28 +477,18 @@ MadDecoder::DecodeNextFrame() return DECODE_CONT; } } - if (MAD_RECOVERABLE(stream.error)) { - return DECODE_SKIP; - } else { - if (stream.error == MAD_ERROR_BUFLEN) - return DECODE_CONT; - else { - FormatWarning(mad_domain, - "unrecoverable frame level error: %s", - mad_stream_errorstr(&stream)); - return DECODE_BREAK; - } - } + + return RecoverFrameError(stream); } return DECODE_OK; } /* xing stuff stolen from alsaplayer, and heavily modified by jat */ -#define XI_MAGIC (('X' << 8) | 'i') -#define NG_MAGIC (('n' << 8) | 'g') -#define IN_MAGIC (('I' << 8) | 'n') -#define FO_MAGIC (('f' << 8) | 'o') +static constexpr unsigned XI_MAGIC = (('X' << 8) | 'i'); +static constexpr unsigned NG_MAGIC = (('n' << 8) | 'g'); +static constexpr unsigned IN_MAGIC = (('I' << 8) | 'n'); +static constexpr unsigned FO_MAGIC = (('f' << 8) | 'o'); enum xing_magic { XING_MAGIC_XING, /* VBR */ @@ -532,12 +504,10 @@ struct xing { enum xing_magic magic; /* header magic */ }; -enum { - XING_FRAMES = 0x00000001L, - XING_BYTES = 0x00000002L, - XING_TOC = 0x00000004L, - XING_SCALE = 0x00000008L -}; +static const unsigned XING_FRAMES = 1; +static const unsigned XING_BYTES = 2; +static const unsigned XING_TOC = 4; +static const unsigned XING_SCALE = 8; struct lame_version { unsigned major; @@ -558,17 +528,12 @@ struct lame { static bool parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen) { - unsigned long bits; - int bitlen; - int bitsleft; - int i; - - bitlen = *oldbitlen; + int bitlen = *oldbitlen; if (bitlen < 16) return false; - bits = mad_bit_read(ptr, 16); + const unsigned long bits = mad_bit_read(ptr, 16); bitlen -= 16; if (bits == XI_MAGIC) { @@ -617,7 +582,8 @@ parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen) if (xing->flags & XING_TOC) { if (bitlen < 800) return false; - for (i = 0; i < 100; ++i) xing->toc[i] = mad_bit_read(ptr, 8); + for (unsigned i = 0; i < 100; ++i) + xing->toc[i] = mad_bit_read(ptr, 8); bitlen -= 800; } @@ -630,7 +596,7 @@ parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen) /* Make sure we consume no less than 120 bytes (960 bits) in hopes that * the LAME tag is found there, and not right after the Xing header */ - bitsleft = 960 - ((*oldbitlen) - bitlen); + const int bitsleft = 960 - (*oldbitlen - bitlen); if (bitsleft < 0) return false; else if (bitsleft > 0) { @@ -646,19 +612,12 @@ parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen) static bool parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) { - int adj = 0; - int name; - int orig; - int sign; - int gain; - int i; - /* Unlike the xing header, the lame tag has a fixed length. Fail if * not all 36 bytes (288 bits) are there. */ if (*bitlen < 288) return false; - for (i = 0; i < 9; i++) + for (unsigned i = 0; i < 9; i++) lame->encoder[i] = (char)mad_bit_read(ptr, 8); lame->encoder[9] = '\0'; @@ -667,7 +626,7 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) /* This is technically incorrect, since the encoder might not be lame. * But there's no other way to determine if this is a lame tag, and we * wouldn't want to go reading a tag that's not there. */ - if (!g_str_has_prefix(lame->encoder, "LAME")) + if (!StringStartsWith(lame->encoder, "LAME")) return false; if (sscanf(lame->encoder+4, "%u.%u", @@ -684,6 +643,7 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) * it's impossible to make the proper adjustment for 3.95. * Fortunately, 3.95 was only out for about a day before 3.95.1 was * released. -- tmz */ + int adj = 0; if (lame->version.major < 3 || (lame->version.major == 3 && lame->version.minor < 95)) adj = 6; @@ -694,10 +654,10 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) FormatDebug(mad_domain, "LAME peak found: %f", lame->peak); lame->track_gain = 0; - name = mad_bit_read(ptr, 3); /* gain name */ - orig = mad_bit_read(ptr, 3); /* gain originator */ - sign = mad_bit_read(ptr, 1); /* sign bit */ - gain = mad_bit_read(ptr, 9); /* gain*10 */ + unsigned name = mad_bit_read(ptr, 3); /* gain name */ + unsigned orig = mad_bit_read(ptr, 3); /* gain originator */ + unsigned sign = mad_bit_read(ptr, 1); /* sign bit */ + int gain = mad_bit_read(ptr, 9); /* gain*10 */ if (gain && name == 1 && orig != 0) { lame->track_gain = ((sign ? -gain : gain) / 10.0) + adj; FormatDebug(mad_domain, "LAME track gain found: %f", @@ -740,14 +700,13 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) return true; } -static inline float +static inline SongTime mp3_frame_duration(const struct mad_frame *frame) { - return mad_timer_count(frame->header.duration, - MAD_UNITS_MILLISECONDS) / 1000.0; + return ToSongTime(frame->header.duration); } -inline InputStream::offset_type +inline offset_type MadDecoder::ThisFrameOffset() const { auto offset = input_stream.GetOffset(); @@ -760,7 +719,7 @@ MadDecoder::ThisFrameOffset() const return offset; } -inline InputStream::offset_type +inline offset_type MadDecoder::RestIncludingThisFrame() const { return input_stream.GetSize() - ThisFrameOffset(); @@ -769,16 +728,22 @@ MadDecoder::RestIncludingThisFrame() const inline void MadDecoder::FileSizeToSongLength() { - InputStream::offset_type rest = RestIncludingThisFrame(); - - if (rest > 0) { - float frame_duration = mp3_frame_duration(&frame); - - total_time = (rest * 8.0) / frame.header.bitrate; - max_frames = total_time / frame_duration + FRAMES_CUSHION; + if (input_stream.KnownSize()) { + offset_type rest = RestIncludingThisFrame(); + + const SongTime frame_duration = mp3_frame_duration(&frame); + const SongTime duration = + SongTime::FromScale<uint64_t>(rest, + frame.header.bitrate / 8); + total_time = duration; + + max_frames = (frame_duration.IsPositive() + ? duration.count() / frame_duration.count() + : 0) + + FRAMES_CUSHION; } else { max_frames = FRAMES_CUSHION; - total_time = 0; + total_time = SignedSongTime::Negative(); } } @@ -786,16 +751,10 @@ inline bool MadDecoder::DecodeFirstFrame(Tag **tag) { struct xing xing; - struct lame lame; - struct mad_bitptr ptr; - int bitlen; - enum mp3_action ret; - - /* stfu gcc */ - memset(&xing, 0, sizeof(struct xing)); - xing.flags = 0; + xing.frames = 0; while (true) { + enum mp3_action ret; do { ret = DecodeNextFrameHeader(tag); } while (ret == DECODE_CONT); @@ -811,8 +770,8 @@ MadDecoder::DecodeFirstFrame(Tag **tag) if (ret == DECODE_OK) break; } - ptr = stream.anc_ptr; - bitlen = stream.anc_bitlen; + struct mad_bitptr ptr = stream.anc_ptr; + int bitlen = stream.anc_bitlen; FileSizeToSongLength(); @@ -820,16 +779,16 @@ MadDecoder::DecodeFirstFrame(Tag **tag) * if an xing tag exists, use that! */ if (parse_xing(&xing, &ptr, &bitlen)) { - found_xing = true; mute_frame = MUTEFRAME_SKIP; if ((xing.flags & XING_FRAMES) && xing.frames) { mad_timer_t duration = frame.header.duration; mad_timer_multiply(&duration, xing.frames); - total_time = ((float)mad_timer_count(duration, MAD_UNITS_MILLISECONDS)) / 1000; + total_time = ToSongTime(duration); max_frames = xing.frames; } + struct lame lame; if (parse_lame(&lame, &ptr, &bitlen)) { if (gapless_playback && input_stream.IsSeekable()) { drop_start_samples = lame.encoder_delay + @@ -877,24 +836,22 @@ MadDecoder::~MadDecoder() } /* this is primarily used for getting total time for tags */ -static int +static std::pair<bool, SignedSongTime> mad_decoder_total_file_time(InputStream &is) { MadDecoder data(nullptr, is); return data.DecodeFirstFrame(nullptr) - ? data.total_time + 0.5 - : -1; + ? std::make_pair(true, data.total_time) + : std::make_pair(false, SignedSongTime::Negative()); } long -MadDecoder::TimeToFrame(double t) const +MadDecoder::TimeToFrame(SongTime t) const { unsigned long i; for (i = 0; i < highest_frame; ++i) { - double frame_time = - mad_timer_count(times[i], - MAD_UNITS_MILLISECONDS) / 1000.; + auto frame_time = ToSongTime(times[i]); if (frame_time >= t) break; } @@ -925,15 +882,13 @@ MadDecoder::UpdateTimerNextFrame() timer = times[current_frame]; current_frame++; - elapsed_time = mad_timer_count(timer, MAD_UNITS_MILLISECONDS) / 1000.0; + elapsed_time = ToSongTime(timer); } DecoderCommand MadDecoder::SendPCM(unsigned i, unsigned pcm_length) { - unsigned max_samples; - - max_samples = sizeof(output_buffer) / + unsigned max_samples = sizeof(output_buffer) / sizeof(output_buffer[0]) / MAD_NCHANNELS(&frame.header); @@ -1014,8 +969,6 @@ MadDecoder::SyncAndSend() inline bool MadDecoder::Read() { - enum mp3_action ret; - UpdateTimerNextFrame(); switch (mute_frame) { @@ -1025,17 +978,16 @@ MadDecoder::Read() mute_frame = MUTEFRAME_NONE; break; case MUTEFRAME_SEEK: - if (elapsed_time >= seek_where) + if (elapsed_time >= seek_time) mute_frame = MUTEFRAME_NONE; break; case MUTEFRAME_NONE: cmd = SyncAndSend(); if (cmd == DecoderCommand::SEEK) { - unsigned long j; - assert(input_stream.IsSeekable()); - j = TimeToFrame(decoder_seek_where(*decoder)); + unsigned long j = + TimeToFrame(decoder_seek_time(*decoder)); if (j < highest_frame) { if (Seek(frame_offsets[j])) { current_frame = j; @@ -1043,7 +995,7 @@ MadDecoder::Read() } else decoder_seek_error(*decoder); } else { - seek_where = decoder_seek_where(*decoder); + seek_time = decoder_seek_time(*decoder); mute_frame = MUTEFRAME_SEEK; decoder_command_finished(*decoder); } @@ -1052,8 +1004,7 @@ MadDecoder::Read() } while (true) { - bool skip = false; - + enum mp3_action ret; do { Tag *tag = nullptr; @@ -1067,8 +1018,8 @@ MadDecoder::Read() } while (ret == DECODE_CONT); if (ret == DECODE_BREAK) return false; - else if (ret == DECODE_SKIP) - skip = true; + + const bool skip = ret == DECODE_SKIP; if (mute_frame == MUTEFRAME_NONE) { do { @@ -1079,10 +1030,8 @@ MadDecoder::Read() } if (!skip && ret == DECODE_OK) - break; + return true; } - - return ret != DECODE_BREAK; } static void @@ -1096,7 +1045,7 @@ mp3_decode(Decoder &decoder, InputStream &input_stream) if (decoder_get_command(decoder) == DecoderCommand::NONE) LogError(mad_domain, - "Input does not appear to be a mp3 bit stream"); + "input/Input does not appear to be a mp3 bit stream"); return; } @@ -1128,13 +1077,13 @@ static bool mad_decoder_scan_stream(InputStream &is, const struct tag_handler *handler, void *handler_ctx) { - int total_time; - - total_time = mad_decoder_total_file_time(is); - if (total_time < 0) + const auto result = mad_decoder_total_file_time(is); + if (!result.first) return false; - tag_handler_invoke_duration(handler, handler_ctx, total_time); + if (!result.second.IsNegative()) + tag_handler_invoke_duration(handler, handler_ctx, + SongTime(result.second)); return true; } diff --git a/src/decoder/MadDecoderPlugin.hxx b/src/decoder/plugins/MadDecoderPlugin.hxx index 450323670..eb2a10d6f 100644 --- a/src/decoder/MadDecoderPlugin.hxx +++ b/src/decoder/plugins/MadDecoderPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/MikmodDecoderPlugin.cxx b/src/decoder/plugins/MikmodDecoderPlugin.cxx index 34381aafa..85633f1fc 100644 --- a/src/decoder/MikmodDecoderPlugin.cxx +++ b/src/decoder/plugins/MikmodDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,14 +19,15 @@ #include "config.h" #include "MikmodDecoderPlugin.hxx" -#include "DecoderAPI.hxx" +#include "../DecoderAPI.hxx" #include "tag/TagHandler.hxx" #include "system/FatalError.hxx" +#include "fs/Path.hxx" #include "util/Domain.hxx" #include "Log.hxx" -#include <glib.h> #include <mikmod.h> + #include <assert.h> static constexpr Domain mikmod_domain("mikmod"); @@ -146,11 +147,11 @@ mikmod_decoder_finish(void) } static void -mikmod_decoder_file_decode(Decoder &decoder, const char *path_fs) +mikmod_decoder_file_decode(Decoder &decoder, Path path_fs) { /* deconstify the path because libmikmod wants a non-const string pointer */ - char *const path2 = const_cast<char *>(path_fs); + char *const path2 = const_cast<char *>(path_fs.c_str()); MODULE *handle; int ret; @@ -160,7 +161,7 @@ mikmod_decoder_file_decode(Decoder &decoder, const char *path_fs) if (handle == nullptr) { FormatError(mikmod_domain, - "failed to open mod: %s", path_fs); + "failed to open mod: %s", path_fs.c_str()); return; } @@ -169,7 +170,8 @@ mikmod_decoder_file_decode(Decoder &decoder, const char *path_fs) const AudioFormat audio_format(mikmod_sample_rate, SampleFormat::S16, 2); assert(audio_format.IsValid()); - decoder_initialized(decoder, audio_format, false, 0); + decoder_initialized(decoder, audio_format, false, + SignedSongTime::Negative()); Player_Start(handle); @@ -184,18 +186,18 @@ mikmod_decoder_file_decode(Decoder &decoder, const char *path_fs) } static bool -mikmod_decoder_scan_file(const char *path_fs, +mikmod_decoder_scan_file(Path path_fs, const struct tag_handler *handler, void *handler_ctx) { /* deconstify the path because libmikmod wants a non-const string pointer */ - char *const path2 = const_cast<char *>(path_fs); + char *const path2 = const_cast<char *>(path_fs.c_str()); MODULE *handle = Player_Load(path2, 128, 0); if (handle == nullptr) { FormatDebug(mikmod_domain, - "Failed to open file: %s", path_fs); + "Failed to open file: %s", path_fs.c_str()); return false; } diff --git a/src/decoder/MikmodDecoderPlugin.hxx b/src/decoder/plugins/MikmodDecoderPlugin.hxx index d25c5f6e7..27ba2a823 100644 --- a/src/decoder/MikmodDecoderPlugin.hxx +++ b/src/decoder/plugins/MikmodDecoderPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/ModplugDecoderPlugin.cxx b/src/decoder/plugins/ModplugDecoderPlugin.cxx index e75f5479c..3e0a41550 100644 --- a/src/decoder/ModplugDecoderPlugin.cxx +++ b/src/decoder/plugins/ModplugDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,8 +19,8 @@ #include "config.h" #include "ModplugDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" #include "tag/TagHandler.hxx" #include "system/FatalError.hxx" #include "util/WritableBuffer.hxx" @@ -36,7 +36,7 @@ static constexpr Domain modplug_domain("modplug"); static constexpr size_t MODPLUG_FRAME_SIZE = 4096; static constexpr size_t MODPLUG_PREALLOC_BLOCK = 256 * 1024; -static constexpr InputStream::offset_type MODPLUG_FILE_LIMIT = 100 * 1024 * 1024; +static constexpr offset_type MODPLUG_FILE_LIMIT = 100 * 1024 * 1024; static int modplug_loop_count; @@ -54,24 +54,29 @@ modplug_decoder_init(const config_param ¶m) static WritableBuffer<uint8_t> mod_loadfile(Decoder *decoder, InputStream &is) { - const InputStream::offset_type size = is.GetSize(); + //known/unknown size, preallocate array, lets read in chunks - if (size == 0) { - LogWarning(modplug_domain, "file is empty"); - return { nullptr, 0 }; - } + const bool is_stream = !is.KnownSize(); - if (size > MODPLUG_FILE_LIMIT) { - LogWarning(modplug_domain, "file too large"); - return { nullptr, 0 }; - } + WritableBuffer<uint8_t> buffer; + if (is_stream) + buffer.size = MODPLUG_PREALLOC_BLOCK; + else { + const auto size = is.GetSize(); + + if (size == 0) { + LogWarning(modplug_domain, "file is empty"); + return { nullptr, 0 }; + } - //known/unknown size, preallocate array, lets read in chunks + if (size > MODPLUG_FILE_LIMIT) { + LogWarning(modplug_domain, "file too large"); + return { nullptr, 0 }; + } - const bool is_stream = size < 0; + buffer.size = size; + } - WritableBuffer<uint8_t> buffer; - buffer.size = is_stream ? MODPLUG_PREALLOC_BLOCK : size; buffer.data = new uint8_t[buffer.size]; uint8_t *const end = buffer.end(); @@ -148,7 +153,7 @@ mod_decode(Decoder &decoder, InputStream &is) decoder_initialized(decoder, audio_format, is.IsSeekable(), - ModPlug_GetLength(f) / 1000.0); + SongTime::FromMS(ModPlug_GetLength(f))); DecoderCommand cmd; do { @@ -161,10 +166,7 @@ mod_decode(Decoder &decoder, InputStream &is) 0); if (cmd == DecoderCommand::SEEK) { - float where = decoder_seek_where(decoder); - - ModPlug_Seek(f, (int)(where * 1000.0)); - + ModPlug_Seek(f, decoder_seek_time(decoder).ToMS()); decoder_command_finished(decoder); } @@ -182,7 +184,7 @@ modplug_scan_stream(InputStream &is, return false; tag_handler_invoke_duration(handler, handler_ctx, - ModPlug_GetLength(f) / 1000); + SongTime::FromMS(ModPlug_GetLength(f))); const char *title = ModPlug_GetName(f); if (title != nullptr) diff --git a/src/decoder/ModplugDecoderPlugin.hxx b/src/decoder/plugins/ModplugDecoderPlugin.hxx index 4cd9f5b25..08f2ecb12 100644 --- a/src/decoder/ModplugDecoderPlugin.hxx +++ b/src/decoder/plugins/ModplugDecoderPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/MpcdecDecoderPlugin.cxx b/src/decoder/plugins/MpcdecDecoderPlugin.cxx index dc258623c..befed0f3b 100644 --- a/src/decoder/MpcdecDecoderPlugin.cxx +++ b/src/decoder/plugins/MpcdecDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,8 +19,8 @@ #include "config.h" #include "MpcdecDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" #include "CheckAudioFormat.hxx" #include "tag/TagHandler.hxx" #include "util/Error.hxx" @@ -30,8 +30,6 @@ #include <mpc/mpcdec.h> -#include <assert.h> -#include <unistd.h> #include <math.h> struct mpc_decoder_data { @@ -59,7 +57,7 @@ mpc_seek_cb(mpc_reader *reader, mpc_int32_t offset) struct mpc_decoder_data *data = (struct mpc_decoder_data *)reader->data; - return data->is.LockSeek(offset, SEEK_SET, IgnoreError()); + return data->is.LockSeek(offset, IgnoreError()); } static mpc_int32_t @@ -86,6 +84,9 @@ mpc_getsize_cb(mpc_reader *reader) struct mpc_decoder_data *data = (struct mpc_decoder_data *)reader->data; + if (!data->is.KnownSize()) + return -1; + return data->is.GetSize(); } @@ -179,13 +180,13 @@ mpcdec_decode(Decoder &mpd_decoder, InputStream &is) decoder_initialized(mpd_decoder, audio_format, is.IsSeekable(), - mpc_streaminfo_get_length(&info)); + SongTime::FromS(mpc_streaminfo_get_length(&info))); DecoderCommand cmd = DecoderCommand::NONE; do { if (cmd == DecoderCommand::SEEK) { - mpc_int64_t where = decoder_seek_where(mpd_decoder) * - audio_format.sample_rate; + mpc_int64_t where = + decoder_seek_where_frame(mpd_decoder); bool success; success = mpc_demux_seek_sample(demux, where) @@ -227,7 +228,7 @@ mpcdec_decode(Decoder &mpd_decoder, InputStream &is) mpc_demux_exit(demux); } -static float +static SignedSongTime mpcdec_get_file_duration(InputStream &is) { mpc_decoder_data data(is, nullptr); @@ -242,25 +243,24 @@ mpcdec_get_file_duration(InputStream &is) mpc_demux *demux = mpc_demux_init(&reader); if (demux == nullptr) - return -1; + return SignedSongTime::Negative(); mpc_streaminfo info; mpc_demux_get_info(demux, &info); mpc_demux_exit(demux); - return mpc_streaminfo_get_length(&info); + return SongTime::FromS(mpc_streaminfo_get_length(&info)); } static bool mpcdec_scan_stream(InputStream &is, const struct tag_handler *handler, void *handler_ctx) { - float total_time = mpcdec_get_file_duration(is); - - if (total_time < 0) + const auto duration = mpcdec_get_file_duration(is); + if (duration.IsNegative()) return false; - tag_handler_invoke_duration(handler, handler_ctx, total_time); + tag_handler_invoke_duration(handler, handler_ctx, SongTime(duration)); return true; } diff --git a/src/decoder/MpcdecDecoderPlugin.hxx b/src/decoder/plugins/MpcdecDecoderPlugin.hxx index 23ecc801e..7f71311fa 100644 --- a/src/decoder/MpcdecDecoderPlugin.hxx +++ b/src/decoder/plugins/MpcdecDecoderPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/Mpg123DecoderPlugin.cxx b/src/decoder/plugins/Mpg123DecoderPlugin.cxx index df23f7847..166529a4d 100644 --- a/src/decoder/Mpg123DecoderPlugin.cxx +++ b/src/decoder/plugins/Mpg123DecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,16 +19,19 @@ #include "config.h" /* must be first for large file support */ #include "Mpg123DecoderPlugin.hxx" -#include "DecoderAPI.hxx" +#include "../DecoderAPI.hxx" #include "CheckAudioFormat.hxx" #include "tag/TagHandler.hxx" +#include "tag/TagBuilder.hxx" +#include "tag/ReplayGain.hxx" +#include "tag/MixRamp.hxx" +#include "fs/Path.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" -#include <glib.h> - #include <mpg123.h> + #include <stdio.h> static constexpr Domain mpg123_domain("mpg123"); @@ -60,14 +63,10 @@ static bool mpd_mpg123_open(mpg123_handle *handle, const char *path_fs, AudioFormat &audio_format) { - int error; - int channels, encoding; - long rate; - /* mpg123_open() wants a writable string :-( */ char *const path2 = const_cast<char *>(path_fs); - error = mpg123_open(handle, path2); + int error = mpg123_open(handle, path2); if (error != MPG123_OK) { FormatWarning(mpg123_domain, "libmpg123 failed to open %s: %s", @@ -77,6 +76,8 @@ mpd_mpg123_open(mpg123_handle *handle, const char *path_fs, /* obtain the audio format */ + long rate; + int channels, encoding; error = mpg123_getformat(handle, &rate, &channels, &encoding); if (error != MPG123_OK) { FormatWarning(mpg123_domain, @@ -104,16 +105,96 @@ mpd_mpg123_open(mpg123_handle *handle, const char *path_fs, } static void -mpd_mpg123_file_decode(Decoder &decoder, const char *path_fs) +AddTagItem(TagBuilder &tag, TagType type, const mpg123_string &s) { - mpg123_handle *handle; - int error; - off_t num_samples; - struct mpg123_frameinfo info; + assert(s.p != nullptr); + assert(s.size >= s.fill); + assert(s.fill > 0); + + tag.AddItem(type, s.p, s.fill - 1); +} + +static void +AddTagItem(TagBuilder &tag, TagType type, const mpg123_string *s) +{ + if (s != nullptr) + AddTagItem(tag, type, *s); +} + +static void +mpd_mpg123_id3v2_tag(Decoder &decoder, const mpg123_id3v2 &id3v2) +{ + TagBuilder tag; + + AddTagItem(tag, TAG_TITLE, id3v2.title); + AddTagItem(tag, TAG_ARTIST, id3v2.artist); + AddTagItem(tag, TAG_ALBUM, id3v2.album); + AddTagItem(tag, TAG_DATE, id3v2.year); + AddTagItem(tag, TAG_GENRE, id3v2.genre); + + for (size_t i = 0, n = id3v2.comments; i < n; ++i) + AddTagItem(tag, TAG_COMMENT, id3v2.comment_list[i].text); + + decoder_tag(decoder, nullptr, tag.Commit()); +} + +static void +mpd_mpg123_id3v2_extras(Decoder &decoder, const mpg123_id3v2 &id3v2) +{ + ReplayGainInfo replay_gain; + replay_gain.Clear(); + + MixRampInfo mix_ramp; + + bool found_replay_gain = false, found_mixramp = false; + + for (size_t i = 0, n = id3v2.extras; i < n; ++i) { + if (ParseReplayGainTag(replay_gain, + id3v2.extra[i].description.p, + id3v2.extra[i].text.p)) + found_replay_gain = true; + else if (ParseMixRampTag(mix_ramp, + id3v2.extra[i].description.p, + id3v2.extra[i].text.p)) + found_mixramp = true; + } + if (found_replay_gain) + decoder_replay_gain(decoder, &replay_gain); + + if (found_mixramp) + decoder_mixramp(decoder, std::move(mix_ramp)); +} + +static void +mpd_mpg123_id3v2(Decoder &decoder, const mpg123_id3v2 &id3v2) +{ + mpd_mpg123_id3v2_tag(decoder, id3v2); + mpd_mpg123_id3v2_extras(decoder, id3v2); +} + +static void +mpd_mpg123_meta(Decoder &decoder, mpg123_handle *const handle) +{ + if ((mpg123_meta_check(handle) & MPG123_NEW_ID3) == 0) + return; + + mpg123_id3v1 *v1; + mpg123_id3v2 *v2; + if (mpg123_id3(handle, &v1, &v2) != MPG123_OK) + return; + + if (v2 != nullptr) + mpd_mpg123_id3v2(decoder, *v2); +} + +static void +mpd_mpg123_file_decode(Decoder &decoder, Path path_fs) +{ /* open the file */ - handle = mpg123_new(nullptr, &error); + int error; + mpg123_handle *const handle = mpg123_new(nullptr, &error); if (handle == nullptr) { FormatError(mpg123_domain, "mpg123_new() failed: %s", @@ -122,19 +203,22 @@ mpd_mpg123_file_decode(Decoder &decoder, const char *path_fs) } AudioFormat audio_format; - if (!mpd_mpg123_open(handle, path_fs, audio_format)) { + if (!mpd_mpg123_open(handle, path_fs.c_str(), audio_format)) { mpg123_delete(handle); return; } - num_samples = mpg123_length(handle); + const off_t num_samples = mpg123_length(handle); /* tell MPD core we're ready */ - decoder_initialized(decoder, audio_format, true, - (float)num_samples / - (float)audio_format.sample_rate); + const auto duration = + SongTime::FromScale<uint64_t>(num_samples, + audio_format.sample_rate); + decoder_initialized(decoder, audio_format, true, duration); + + struct mpg123_frameinfo info; if (mpg123_info(handle, &info) != MPG123_OK) { info.vbr = MPG123_CBR; info.bitrate = 0; @@ -151,14 +235,15 @@ mpd_mpg123_file_decode(Decoder &decoder, const char *path_fs) } /* the decoder main loop */ - DecoderCommand cmd; do { - unsigned char buffer[8192]; - size_t nbytes; + /* read metadata */ + mpd_mpg123_meta(decoder, handle); /* decode */ + unsigned char buffer[8192]; + size_t nbytes; error = mpg123_read(handle, buffer, sizeof(buffer), &nbytes); if (error != MPG123_OK) { if (error != MPG123_DONE) @@ -181,7 +266,7 @@ mpd_mpg123_file_decode(Decoder &decoder, const char *path_fs) cmd = decoder_data(decoder, nullptr, buffer, nbytes, info.bitrate); if (cmd == DecoderCommand::SEEK) { - off_t c = decoder_seek_where(decoder)*audio_format.sample_rate; + off_t c = decoder_seek_where_frame(decoder); c = mpg123_seek(handle, c, SEEK_SET); if (c < 0) decoder_seek_error(decoder); @@ -200,14 +285,11 @@ mpd_mpg123_file_decode(Decoder &decoder, const char *path_fs) } static bool -mpd_mpg123_scan_file(const char *path_fs, +mpd_mpg123_scan_file(Path path_fs, const struct tag_handler *handler, void *handler_ctx) { - mpg123_handle *handle; int error; - off_t num_samples; - - handle = mpg123_new(nullptr, &error); + mpg123_handle *const handle = mpg123_new(nullptr, &error); if (handle == nullptr) { FormatError(mpg123_domain, "mpg123_new() failed: %s", @@ -216,12 +298,12 @@ mpd_mpg123_scan_file(const char *path_fs, } AudioFormat audio_format; - if (!mpd_mpg123_open(handle, path_fs, audio_format)) { + if (!mpd_mpg123_open(handle, path_fs.c_str(), audio_format)) { mpg123_delete(handle); return false; } - num_samples = mpg123_length(handle); + const off_t num_samples = mpg123_length(handle); if (num_samples <= 0) { mpg123_delete(handle); return false; @@ -231,8 +313,11 @@ mpd_mpg123_scan_file(const char *path_fs, mpg123_delete(handle); - tag_handler_invoke_duration(handler, handler_ctx, - num_samples / audio_format.sample_rate); + const auto duration = + SongTime::FromScale<uint64_t>(num_samples, + audio_format.sample_rate); + + tag_handler_invoke_duration(handler, handler_ctx, duration); return true; } diff --git a/src/decoder/Mpg123DecoderPlugin.hxx b/src/decoder/plugins/Mpg123DecoderPlugin.hxx index 10f7c37f5..fd089c6a4 100644 --- a/src/decoder/Mpg123DecoderPlugin.hxx +++ b/src/decoder/plugins/Mpg123DecoderPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/OggCodec.cxx b/src/decoder/plugins/OggCodec.cxx index 565dbafcf..c7f39586e 100644 --- a/src/decoder/OggCodec.cxx +++ b/src/decoder/plugins/OggCodec.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,6 +23,7 @@ #include "config.h" #include "OggCodec.hxx" +#include "../DecoderAPI.hxx" #include <string.h> diff --git a/src/decoder/OggCodec.hxx b/src/decoder/plugins/OggCodec.hxx index 857871607..3b096561c 100644 --- a/src/decoder/OggCodec.hxx +++ b/src/decoder/plugins/OggCodec.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,7 +24,8 @@ #ifndef MPD_OGG_CODEC_HXX #define MPD_OGG_CODEC_HXX -#include "DecoderAPI.hxx" +struct Decoder; +class InputStream; enum ogg_codec { OGG_CODEC_UNKNOWN, diff --git a/src/decoder/OggFind.cxx b/src/decoder/plugins/OggFind.cxx index 65c7fa3ce..978e1d7cf 100644 --- a/src/decoder/OggFind.cxx +++ b/src/decoder/plugins/OggFind.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,10 +20,9 @@ #include "config.h" #include "OggFind.hxx" #include "OggSyncState.hxx" +#include "input/InputStream.hxx" #include "util/Error.hxx" -#include <stdio.h> - bool OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet) { @@ -41,7 +40,7 @@ OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet) bool OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is, - InputStream::offset_type offset, int whence) + offset_type offset) { oy.Reset(); @@ -49,7 +48,7 @@ OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is, data */ ogg_stream_reset(&os); - return is.LockSeek(offset, whence, IgnoreError()) && + return is.LockSeek(offset, IgnoreError()) && oy.ExpectPageSeekIn(os); } @@ -57,12 +56,15 @@ bool OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet, InputStream &is) { - if (is.size > 0 && is.size - is.offset < 65536) + if (!is.KnownSize()) + return false; + + if (is.GetRest() < 65536) return OggFindEOS(oy, os, packet); if (!is.CheapSeeking()) return false; - return OggSeekPageAtOffset(oy, os, is, -65536, SEEK_END) && + return OggSeekPageAtOffset(oy, os, is, is.GetSize() - 65536) && OggFindEOS(oy, os, packet); } diff --git a/src/decoder/OggFind.hxx b/src/decoder/plugins/OggFind.hxx index ad51ccdf3..2aa4f6c06 100644 --- a/src/decoder/OggFind.hxx +++ b/src/decoder/plugins/OggFind.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,12 +21,12 @@ #define MPD_OGG_FIND_HXX #include "check.h" -#include "InputStream.hxx" +#include "input/Offset.hxx" #include <ogg/ogg.h> -struct InputStream; class OggSyncState; +class InputStream; /** * Skip all pages/packets until an end-of-stream (EOS) packet for the @@ -42,7 +42,7 @@ OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet); */ bool OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is, - InputStream::offset_type offset, int whence); + offset_type offset); /** * Try to find the end-of-stream (EOS) packet. Seek to the end of the diff --git a/src/decoder/OggSyncState.hxx b/src/decoder/plugins/OggSyncState.hxx index 5235c1bd8..024902fff 100644 --- a/src/decoder/OggSyncState.hxx +++ b/src/decoder/plugins/OggSyncState.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/OggUtil.cxx b/src/decoder/plugins/OggUtil.cxx index 8f181ce57..6341b0008 100644 --- a/src/decoder/OggUtil.cxx +++ b/src/decoder/plugins/OggUtil.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,7 +19,7 @@ #include "config.h" #include "OggUtil.hxx" -#include "DecoderAPI.hxx" +#include "../DecoderAPI.hxx" bool OggFeed(ogg_sync_state &oy, Decoder *decoder, diff --git a/src/decoder/OggUtil.hxx b/src/decoder/plugins/OggUtil.hxx index 41fc755ba..94c380ef4 100644 --- a/src/decoder/OggUtil.hxx +++ b/src/decoder/plugins/OggUtil.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,7 +26,7 @@ #include <stddef.h> -struct InputStream; +class InputStream; struct Decoder; /** diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/plugins/OpusDecoderPlugin.cxx index 01ea3687a..e14827e38 100644 --- a/src/decoder/OpusDecoderPlugin.cxx +++ b/src/decoder/plugins/OpusDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,28 +22,30 @@ #include "OpusDomain.hxx" #include "OpusHead.hxx" #include "OpusTags.hxx" -#include "OggUtil.hxx" #include "OggFind.hxx" #include "OggSyncState.hxx" -#include "DecoderAPI.hxx" +#include "../DecoderAPI.hxx" #include "OggCodec.hxx" -#include "CheckAudioFormat.hxx" #include "tag/TagHandler.hxx" #include "tag/TagBuilder.hxx" -#include "InputStream.hxx" +#include "input/InputStream.hxx" #include "util/Error.hxx" #include "Log.hxx" #include <opus.h> #include <ogg/ogg.h> -#include <glib.h> - #include <string.h> #include <stdio.h> static constexpr opus_int32 opus_sample_rate = 48000; +/** + * Allocate an output buffer for 16 bit PCM samples big enough to hold + * a quarter second, larger than 120ms required by libopus. + */ +static constexpr unsigned opus_output_buffer_frames = opus_sample_rate / 4; + gcc_pure static bool IsOpusHead(const ogg_packet &packet) @@ -74,10 +76,16 @@ class MPDOpusDecoder { OpusDecoder *opus_decoder; opus_int16 *output_buffer; - unsigned output_size; + + /** + * If non-zero, then a previous Opus stream has been found + * already with this number of channels. If opus_decoder is + * nullptr, then its end-of-stream packet has been found + * already. + */ + unsigned previous_channels; bool os_initialized; - bool found_opus; int opus_serialno; @@ -90,8 +98,9 @@ public: InputStream &_input_stream) :decoder(_decoder), input_stream(_input_stream), opus_decoder(nullptr), - output_buffer(nullptr), output_size(0), - os_initialized(false), found_opus(false) {} + output_buffer(nullptr), + previous_channels(0), + os_initialized(false) {} ~MPDOpusDecoder(); bool ReadFirstPage(OggSyncState &oy); @@ -100,15 +109,16 @@ public: DecoderCommand HandlePackets(); DecoderCommand HandlePacket(const ogg_packet &packet); DecoderCommand HandleBOS(const ogg_packet &packet); + DecoderCommand HandleEOS(); DecoderCommand HandleTags(const ogg_packet &packet); DecoderCommand HandleAudio(const ogg_packet &packet); - bool Seek(OggSyncState &oy, double where); + bool Seek(OggSyncState &oy, uint64_t where_frame); }; MPDOpusDecoder::~MPDOpusDecoder() { - g_free(output_buffer); + delete[] output_buffer; if (opus_decoder != nullptr) opus_decoder_destroy(opus_decoder); @@ -163,12 +173,14 @@ inline DecoderCommand MPDOpusDecoder::HandlePacket(const ogg_packet &packet) { if (packet.e_o_s) - return DecoderCommand::STOP; + return HandleEOS(); if (packet.b_o_s) return HandleBOS(packet); - else if (!found_opus) + else if (opus_decoder == nullptr) { + LogDebug(opus_domain, "BOS packet expected"); return DecoderCommand::STOP; + } if (IsOpusTags(packet)) return HandleTags(packet); @@ -188,11 +200,9 @@ LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno, /* we do this for local files only, because seeking around remote files is expensive and not worth the troubl */ - return -1; + return false; - const auto old_offset = is.offset; - if (old_offset < 0) - return -1; + const auto old_offset = is.GetOffset(); /* create temporary Ogg objects for seeking and parsing the EOS packet */ @@ -204,7 +214,7 @@ LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno, ogg_stream_clear(&os); /* restore the previous file position */ - is.Seek(old_offset, SEEK_SET, IgnoreError()); + is.LockSeek(old_offset, IgnoreError()); return result; } @@ -231,19 +241,29 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet) { assert(packet.b_o_s); - if (found_opus || !IsOpusHead(packet)) + if (opus_decoder != nullptr || !IsOpusHead(packet)) { + LogDebug(opus_domain, "BOS packet must be OpusHead"); return DecoderCommand::STOP; + } unsigned channels; if (!ScanOpusHeader(packet.packet, packet.bytes, channels) || - !audio_valid_channel_count(channels)) + !audio_valid_channel_count(channels)) { + LogDebug(opus_domain, "Malformed BOS packet"); return DecoderCommand::STOP; + } assert(opus_decoder == nullptr); - assert(output_buffer == nullptr); + assert((previous_channels == 0) == (output_buffer == nullptr)); + + if (previous_channels != 0 && channels != previous_channels) { + FormatWarning(opus_domain, + "Next stream has different channels (%u -> %u)", + previous_channels, channels); + return DecoderCommand::STOP; + } opus_serialno = os.serialno; - found_opus = true; /* TODO: parse attributes from the OpusHead (sample rate, channels, ...) */ @@ -257,30 +277,51 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet) return DecoderCommand::STOP; } + if (previous_channels != 0) { + /* decoder was already initialized by the previous + stream; skip the rest of this method */ + LogDebug(opus_domain, "Found another stream"); + return decoder_get_command(decoder); + } + eos_granulepos = LoadEOSGranulePos(input_stream, &decoder, opus_serialno); - const double duration = eos_granulepos >= 0 - ? double(eos_granulepos) / opus_sample_rate - : -1.0; + const auto duration = eos_granulepos >= 0 + ? SignedSongTime::FromScale<uint64_t>(eos_granulepos, + opus_sample_rate) + : SignedSongTime::Negative(); + previous_channels = channels; const AudioFormat audio_format(opus_sample_rate, SampleFormat::S16, channels); decoder_initialized(decoder, audio_format, eos_granulepos > 0, duration); frame_size = audio_format.GetFrameSize(); - /* allocate an output buffer for 16 bit PCM samples big enough - to hold a quarter second, larger than 120ms required by - libopus */ - output_size = audio_format.sample_rate / 4; - output_buffer = (opus_int16 *) - g_malloc(sizeof(*output_buffer) * output_size * - audio_format.channels); + output_buffer = new opus_int16[opus_output_buffer_frames + * audio_format.channels]; return decoder_get_command(decoder); } inline DecoderCommand +MPDOpusDecoder::HandleEOS() +{ + if (eos_granulepos < 0 && previous_channels != 0) { + /* allow chaining of (unseekable) streams */ + assert(opus_decoder != nullptr); + assert(output_buffer != nullptr); + + opus_decoder_destroy(opus_decoder); + opus_decoder = nullptr; + + return decoder_get_command(decoder); + } + + return DecoderCommand::STOP; +} + +inline DecoderCommand MPDOpusDecoder::HandleTags(const ogg_packet &packet) { ReplayGainInfo rgi; @@ -295,8 +336,7 @@ MPDOpusDecoder::HandleTags(const ogg_packet &packet) !tag_builder.IsEmpty()) { decoder_replay_gain(decoder, &rgi); - Tag tag; - tag_builder.Commit(tag); + Tag tag = tag_builder.Commit(); cmd = decoder_tag(decoder, input_stream, std::move(tag)); } else cmd = decoder_get_command(decoder); @@ -312,10 +352,11 @@ MPDOpusDecoder::HandleAudio(const ogg_packet &packet) int nframes = opus_decode(opus_decoder, (const unsigned char*)packet.packet, packet.bytes, - output_buffer, output_size, + output_buffer, opus_output_buffer_frames, 0); if (nframes < 0) { - LogError(opus_domain, opus_strerror(nframes)); + FormatError(opus_domain, "libopus error: %s", + opus_strerror(nframes)); return DecoderCommand::STOP; } @@ -337,26 +378,21 @@ MPDOpusDecoder::HandleAudio(const ogg_packet &packet) } bool -MPDOpusDecoder::Seek(OggSyncState &oy, double where_s) +MPDOpusDecoder::Seek(OggSyncState &oy, uint64_t where_frame) { assert(eos_granulepos > 0); - assert(input_stream.seekable); - assert(input_stream.size > 0); - assert(input_stream.offset >= 0); + assert(input_stream.IsSeekable()); + assert(input_stream.KnownSize()); - const ogg_int64_t where_granulepos(where_s * opus_sample_rate); + const ogg_int64_t where_granulepos(where_frame); /* interpolate the file offset where we expect to find the given granule position */ /* TODO: implement binary search */ - InputStream::offset_type offset(where_granulepos * input_stream.size - / eos_granulepos); - - if (!OggSeekPageAtOffset(oy, os, input_stream, offset, SEEK_SET)) - return false; + offset_type offset(where_granulepos * input_stream.GetSize() + / eos_granulepos); - decoder_timestamp(decoder, where_s); - return true; + return OggSeekPageAtOffset(oy, os, input_stream, offset); } static void @@ -379,7 +415,7 @@ mpd_opus_stream_decode(Decoder &decoder, while (true) { auto cmd = d.HandlePackets(); if (cmd == DecoderCommand::SEEK) { - if (d.Seek(oy, decoder_seek_where(decoder))) + if (d.Seek(oy, decoder_seek_where_frame(decoder))) decoder_command_finished(decoder); else decoder_seek_error(decoder); @@ -454,9 +490,12 @@ mpd_opus_scan_stream(InputStream &is, } } - if (packet.e_o_s || OggSeekFindEOS(oy, os, packet, is)) - tag_handler_invoke_duration(handler, handler_ctx, - packet.granulepos / opus_sample_rate); + if (packet.e_o_s || OggSeekFindEOS(oy, os, packet, is)) { + const auto duration = + SongTime::FromScale<uint64_t>(packet.granulepos, + opus_sample_rate); + tag_handler_invoke_duration(handler, handler_ctx, duration); + } ogg_stream_clear(&os); @@ -471,6 +510,13 @@ static const char *const opus_suffixes[] = { }; static const char *const opus_mime_types[] = { + /* the official MIME type (RFC 5334) */ + "audio/ogg", + + /* deprecated (RFC 5334) */ + "application/ogg", + + /* deprecated; from an early draft */ "audio/opus", nullptr }; diff --git a/src/decoder/OpusDecoderPlugin.h b/src/decoder/plugins/OpusDecoderPlugin.h index 263ac6e2d..260dab99a 100644 --- a/src/decoder/OpusDecoderPlugin.h +++ b/src/decoder/plugins/OpusDecoderPlugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/OpusDomain.cxx b/src/decoder/plugins/OpusDomain.cxx index b00e2a553..1efd64a48 100644 --- a/src/decoder/OpusDomain.cxx +++ b/src/decoder/plugins/OpusDomain.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/OpusDomain.hxx b/src/decoder/plugins/OpusDomain.hxx index 2b56c427c..fb19e0301 100644 --- a/src/decoder/OpusDomain.hxx +++ b/src/decoder/plugins/OpusDomain.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/OpusHead.cxx b/src/decoder/plugins/OpusHead.cxx index 0417d3905..bfa41d618 100644 --- a/src/decoder/OpusHead.cxx +++ b/src/decoder/plugins/OpusHead.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,7 +21,6 @@ #include "OpusHead.hxx" #include <stdint.h> -#include <string.h> struct OpusHead { char signature[8]; diff --git a/src/decoder/OpusHead.hxx b/src/decoder/plugins/OpusHead.hxx index fa6a2b666..c478d8d90 100644 --- a/src/decoder/OpusHead.hxx +++ b/src/decoder/plugins/OpusHead.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/OpusReader.hxx b/src/decoder/plugins/OpusReader.hxx index 2bb39b748..c5b8e9107 100644 --- a/src/decoder/OpusReader.hxx +++ b/src/decoder/plugins/OpusReader.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/OpusTags.cxx b/src/decoder/plugins/OpusTags.cxx index f7729e5ad..aff5479c0 100644 --- a/src/decoder/OpusTags.cxx +++ b/src/decoder/plugins/OpusTags.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/OpusTags.hxx b/src/decoder/plugins/OpusTags.hxx index e1f1a1ff1..be3ac3a8d 100644 --- a/src/decoder/OpusTags.hxx +++ b/src/decoder/plugins/OpusTags.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/PcmDecoderPlugin.cxx b/src/decoder/plugins/PcmDecoderPlugin.cxx index dbc38fb76..c07a7b9b1 100644 --- a/src/decoder/PcmDecoderPlugin.cxx +++ b/src/decoder/plugins/PcmDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,17 +18,14 @@ */ #include "config.h" -#include "decoder/PcmDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" +#include "PcmDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" #include "util/Error.hxx" #include "util/ByteReverse.hxx" #include "Log.hxx" -#include <glib.h> -#include <unistd.h> #include <string.h> -#include <stdio.h> /* for SEEK_SET */ static void pcm_stream_decode(Decoder &decoder, InputStream &is) @@ -43,12 +40,12 @@ pcm_stream_decode(Decoder &decoder, InputStream &is) const bool reverse_endian = mime != nullptr && strcmp(mime, "audio/x-mpd-cdda-pcm-reverse") == 0; - const double time_to_size = audio_format.GetTimeToSize(); + const auto frame_size = audio_format.GetFrameSize(); - float total_time = -1; - const auto size = is.GetSize(); - if (size >= 0) - total_time = size / time_to_size; + const auto total_time = is.KnownSize() + ? SignedSongTime::FromScale<uint64_t>(is.GetSize() / frame_size, + audio_format.sample_rate) + : SignedSongTime::Negative(); decoder_initialized(decoder, audio_format, is.IsSeekable(), total_time); @@ -74,11 +71,11 @@ pcm_stream_decode(Decoder &decoder, InputStream &is) buffer, nbytes, 0) : decoder_get_command(decoder); if (cmd == DecoderCommand::SEEK) { - InputStream::offset_type offset(time_to_size * - decoder_seek_where(decoder)); + uint64_t frame = decoder_seek_where_frame(decoder); + offset_type offset = frame * frame_size; Error error; - if (is.LockSeek(offset, SEEK_SET, error)) { + if (is.LockSeek(offset, error)) { decoder_command_finished(decoder); } else { LogError(error); diff --git a/src/decoder/PcmDecoderPlugin.hxx b/src/decoder/plugins/PcmDecoderPlugin.hxx index 38e4a5020..3582e5856 100644 --- a/src/decoder/PcmDecoderPlugin.hxx +++ b/src/decoder/plugins/PcmDecoderPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/SidplayDecoderPlugin.cxx b/src/decoder/plugins/SidplayDecoderPlugin.cxx index 160337594..8435f095f 100644 --- a/src/decoder/SidplayDecoderPlugin.cxx +++ b/src/decoder/plugins/SidplayDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,6 +21,8 @@ #include "SidplayDecoderPlugin.hxx" #include "../DecoderAPI.hxx" #include "tag/TagHandler.hxx" +#include "fs/Path.hxx" +#include "util/FormatString.hxx" #include "util/Domain.hxx" #include "system/ByteOrder.hxx" #include "Log.hxx" @@ -119,9 +121,9 @@ sidplay_finish() * suffix */ static char * -get_container_name(const char *path_fs) +get_container_name(Path path_fs) { - char *path_container=g_strdup(path_fs); + char *path_container = strdup(path_fs.c_str()); if(!g_pattern_match(path_with_subtune, strlen(path_container), path_container, nullptr)) @@ -157,31 +159,31 @@ get_song_num(const char *path_fs) } /* get the song length in seconds */ -static int -get_song_length(const char *path_fs) +static SignedSongTime +get_song_length(Path path_fs) { if (songlength_database == nullptr) - return -1; + return SignedSongTime::Negative(); - gchar *sid_file=get_container_name(path_fs); + char *sid_file = get_container_name(path_fs); SidTuneMod tune(sid_file); - g_free(sid_file); + free(sid_file); if(!tune) { LogWarning(sidplay_domain, "failed to load file for calculating md5 sum"); - return -1; + return SignedSongTime::Negative(); } char md5sum[SIDTUNE_MD5_LENGTH+1]; tune.createMD5(md5sum); - const unsigned song_num = get_song_num(path_fs); + const unsigned song_num = get_song_num(path_fs.c_str()); gsize num_items; gchar **values=g_key_file_get_string_list(songlength_database, "Database", md5sum, &num_items, nullptr); if(!values || song_num>num_items) { g_strfreev(values); - return -1; + return SignedSongTime::Negative(); } int minutes=strtol(values[song_num-1], nullptr, 10); @@ -197,11 +199,11 @@ get_song_length(const char *path_fs) g_strfreev(values); - return (minutes*60)+seconds; + return SignedSongTime::FromS((minutes * 60) + seconds); } static void -sidplay_file_decode(Decoder &decoder, const char *path_fs) +sidplay_file_decode(Decoder &decoder, Path path_fs) { int channels; @@ -209,17 +211,18 @@ sidplay_file_decode(Decoder &decoder, const char *path_fs) char *path_container=get_container_name(path_fs); SidTune tune(path_container, nullptr, true); - g_free(path_container); + free(path_container); if (!tune) { LogWarning(sidplay_domain, "failed to load file"); return; } - int song_num=get_song_num(path_fs); + const int song_num = get_song_num(path_fs.c_str()); tune.selectSong(song_num); - int song_len=get_song_length(path_fs); - if(song_len==-1) song_len=default_songlength; + auto duration = get_song_length(path_fs); + if (duration.IsNegative() && default_songlength > 0) + duration = SongTime::FromS(default_songlength); /* initialize the player */ @@ -290,12 +293,14 @@ sidplay_file_decode(Decoder &decoder, const char *path_fs) const AudioFormat audio_format(48000, SampleFormat::S16, channels); assert(audio_format.IsValid()); - decoder_initialized(decoder, audio_format, true, (float)song_len); + decoder_initialized(decoder, audio_format, true, duration); /* .. and play */ const unsigned timebase = player.timebase(); - song_len *= timebase; + const unsigned end = duration.IsNegative() + ? 0u + : duration.ToScale<uint64_t>(timebase); DecoderCommand cmd; do { @@ -312,8 +317,8 @@ sidplay_file_decode(Decoder &decoder, const char *path_fs) if (cmd == DecoderCommand::SEEK) { unsigned data_time = player.time(); - unsigned target_time = (unsigned) - (decoder_seek_where(decoder) * timebase); + unsigned target_time = + decoder_seek_time(decoder).ToScale(timebase); /* can't rewind so return to zero and seek forward */ if(target_time<data_time) { @@ -332,21 +337,21 @@ sidplay_file_decode(Decoder &decoder, const char *path_fs) decoder_command_finished(decoder); } - if (song_len > 0 && player.time() >= (unsigned)song_len) + if (end > 0 && player.time() >= end) break; } while (cmd != DecoderCommand::STOP); } static bool -sidplay_scan_file(const char *path_fs, +sidplay_scan_file(Path path_fs, const struct tag_handler *handler, void *handler_ctx) { - int song_num=get_song_num(path_fs); + const int song_num = get_song_num(path_fs.c_str()); char *path_container=get_container_name(path_fs); SidTune tune(path_container, nullptr, true); - g_free(path_container); + free(path_container); if (!tune) return false; @@ -380,17 +385,18 @@ sidplay_scan_file(const char *path_fs, tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track); /* time */ - int song_len=get_song_length(path_fs); - if (song_len >= 0) - tag_handler_invoke_duration(handler, handler_ctx, song_len); + const auto duration = get_song_length(path_fs); + if (!duration.IsNegative()) + tag_handler_invoke_duration(handler, handler_ctx, + SongTime(duration)); return true; } static char * -sidplay_container_scan(const char *path_fs, const unsigned int tnum) +sidplay_container_scan(Path path_fs, const unsigned int tnum) { - SidTune tune(path_fs, nullptr, true); + SidTune tune(path_fs.c_str(), nullptr, true); if (!tune) return nullptr; @@ -404,9 +410,7 @@ sidplay_container_scan(const char *path_fs, const unsigned int tnum) /* Construct container/tune path names, eg. Delta.sid/tune_001.sid */ if(tnum<=info.songs) { - char *subtune= g_strdup_printf( - SUBTUNE_PREFIX "%03u.sid", tnum); - return subtune; + return FormatNew(SUBTUNE_PREFIX "%03u.sid", tnum); } else return nullptr; } diff --git a/src/decoder/SidplayDecoderPlugin.hxx b/src/decoder/plugins/SidplayDecoderPlugin.hxx index 16544801f..58786e646 100644 --- a/src/decoder/SidplayDecoderPlugin.hxx +++ b/src/decoder/plugins/SidplayDecoderPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/SndfileDecoderPlugin.cxx b/src/decoder/plugins/SndfileDecoderPlugin.cxx index bcdf6d7ca..5518c70be 100644 --- a/src/decoder/SndfileDecoderPlugin.cxx +++ b/src/decoder/plugins/SndfileDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,8 +19,8 @@ #include "config.h" #include "SndfileDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" #include "CheckAudioFormat.hxx" #include "tag/TagHandler.hxx" #include "util/Error.hxx" @@ -31,6 +31,13 @@ static constexpr Domain sndfile_domain("sndfile"); +static bool +sndfile_init(gcc_unused const config_param ¶m) +{ + LogDebug(sndfile_domain, sf_version_string()); + return true; +} + struct SndfileInputStream { Decoder *const decoder; InputStream &is; @@ -50,17 +57,40 @@ sndfile_vio_get_filelen(void *user_data) SndfileInputStream &sis = *(SndfileInputStream *)user_data; const InputStream &is = sis.is; + if (!is.KnownSize()) + return -1; + return is.GetSize(); } static sf_count_t -sndfile_vio_seek(sf_count_t offset, int whence, void *user_data) +sndfile_vio_seek(sf_count_t _offset, int whence, void *user_data) { SndfileInputStream &sis = *(SndfileInputStream *)user_data; InputStream &is = sis.is; + offset_type offset = _offset; + switch (whence) { + case SEEK_SET: + break; + + case SEEK_CUR: + offset += is.GetOffset(); + break; + + case SEEK_END: + if (!is.KnownSize()) + return -1; + + offset += is.GetSize(); + break; + + default: + return -1; + } + Error error; - if (!is.LockSeek(offset, whence, error)) { + if (!is.LockSeek(offset, error)) { LogError(error, "Seek failed"); return -1; } @@ -109,60 +139,89 @@ static SF_VIRTUAL_IO vio = { /** * Converts a frame number to a timestamp (in seconds). */ -static float -frame_to_time(sf_count_t frame, const AudioFormat *audio_format) +static constexpr SongTime +sndfile_duration(const SF_INFO &info) { - return (float)frame / (float)audio_format->sample_rate; + return SongTime::FromScale<uint64_t>(info.frames, info.samplerate); +} + +gcc_pure +static SampleFormat +sndfile_sample_format(const SF_INFO &info) +{ + switch (info.format & SF_FORMAT_SUBMASK) { + case SF_FORMAT_PCM_S8: + case SF_FORMAT_PCM_U8: + case SF_FORMAT_PCM_16: + return SampleFormat::S16; + + case SF_FORMAT_FLOAT: + case SF_FORMAT_DOUBLE: + return SampleFormat::FLOAT; + + default: + return SampleFormat::S32; + } } -/** - * Converts a timestamp (in seconds) to a frame number. - */ static sf_count_t -time_to_frame(float t, const AudioFormat *audio_format) +sndfile_read_frames(SNDFILE *sf, SampleFormat format, + void *buffer, sf_count_t n_frames) { - return (sf_count_t)(t * audio_format->sample_rate); + switch (format) { + case SampleFormat::S16: + return sf_readf_short(sf, (short *)buffer, n_frames); + + case SampleFormat::S32: + return sf_readf_int(sf, (int *)buffer, n_frames); + + case SampleFormat::FLOAT: + return sf_readf_float(sf, (float *)buffer, n_frames); + + default: + assert(false); + gcc_unreachable(); + } } static void sndfile_stream_decode(Decoder &decoder, InputStream &is) { - SNDFILE *sf; SF_INFO info; - size_t frame_size; - sf_count_t read_frames, num_frames; - int buffer[4096]; info.format = 0; SndfileInputStream sis{&decoder, is}; - sf = sf_open_virtual(&vio, SFM_READ, &info, &sis); + SNDFILE *const sf = sf_open_virtual(&vio, SFM_READ, &info, &sis); if (sf == nullptr) { - LogWarning(sndfile_domain, "sf_open_virtual() failed"); + FormatWarning(sndfile_domain, "sf_open_virtual() failed: %s", + sf_strerror(nullptr)); return; } - /* for now, always read 32 bit samples. Later, we could lower - MPD's CPU usage by reading 16 bit samples with - sf_readf_short() on low-quality source files. */ Error error; AudioFormat audio_format; if (!audio_format_init_checked(audio_format, info.samplerate, - SampleFormat::S32, + sndfile_sample_format(info), info.channels, error)) { LogError(error); return; } decoder_initialized(decoder, audio_format, info.seekable, - frame_to_time(info.frames, &audio_format)); + sndfile_duration(info)); - frame_size = audio_format.GetFrameSize(); - read_frames = sizeof(buffer) / frame_size; + char buffer[16384]; + + const size_t frame_size = audio_format.GetFrameSize(); + const sf_count_t read_frames = sizeof(buffer) / frame_size; DecoderCommand cmd; do { - num_frames = sf_readf_int(sf, buffer, read_frames); + sf_count_t num_frames = + sndfile_read_frames(sf, + audio_format.format, + buffer, read_frames); if (num_frames <= 0) break; @@ -170,9 +229,7 @@ sndfile_stream_decode(Decoder &decoder, InputStream &is) buffer, num_frames * frame_size, 0); if (cmd == DecoderCommand::SEEK) { - sf_count_t c = - time_to_frame(decoder_seek_where(decoder), - &audio_format); + sf_count_t c = decoder_seek_where_frame(decoder); c = sf_seek(sf, c, SEEK_SET); if (c < 0) decoder_seek_error(decoder); @@ -185,44 +242,53 @@ sndfile_stream_decode(Decoder &decoder, InputStream &is) sf_close(sf); } +static void +sndfile_handle_tag(SNDFILE *sf, int str, TagType tag, + const struct tag_handler *handler, void *handler_ctx) +{ + const char *value = sf_get_string(sf, str); + if (value != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, tag, value); +} + +static constexpr struct { + int8_t str; + TagType tag; +} sndfile_tags[] = { + { SF_STR_TITLE, TAG_TITLE }, + { SF_STR_ARTIST, TAG_ARTIST }, + { SF_STR_COMMENT, TAG_COMMENT }, + { SF_STR_DATE, TAG_DATE }, + { SF_STR_ALBUM, TAG_ALBUM }, + { SF_STR_TRACKNUMBER, TAG_TRACK }, + { SF_STR_GENRE, TAG_GENRE }, +}; + static bool -sndfile_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) +sndfile_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) { - SNDFILE *sf; SF_INFO info; - const char *p; info.format = 0; - sf = sf_open(path_fs, SFM_READ, &info); + SndfileInputStream sis{nullptr, is}; + SNDFILE *const sf = sf_open_virtual(&vio, SFM_READ, &info, &sis); if (sf == nullptr) return false; if (!audio_valid_sample_rate(info.samplerate)) { sf_close(sf); FormatWarning(sndfile_domain, - "Invalid sample rate in %s", path_fs); + "Invalid sample rate in %s", is.GetURI()); return false; } tag_handler_invoke_duration(handler, handler_ctx, - info.frames / info.samplerate); - - p = sf_get_string(sf, SF_STR_TITLE); - if (p != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, p); + sndfile_duration(info)); - p = sf_get_string(sf, SF_STR_ARTIST); - if (p != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_ARTIST, p); - - p = sf_get_string(sf, SF_STR_DATE); - if (p != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_DATE, p); + for (auto i : sndfile_tags) + sndfile_handle_tag(sf, i.str, i.tag, handler, handler_ctx); sf_close(sf); @@ -261,12 +327,12 @@ static const char *const sndfile_mime_types[] = { const struct DecoderPlugin sndfile_decoder_plugin = { "sndfile", - nullptr, + sndfile_init, nullptr, sndfile_stream_decode, nullptr, - sndfile_scan_file, nullptr, + sndfile_scan_stream, nullptr, sndfile_suffixes, sndfile_mime_types, diff --git a/src/decoder/SndfileDecoderPlugin.hxx b/src/decoder/plugins/SndfileDecoderPlugin.hxx index f8aa65680..d56acdd5a 100644 --- a/src/decoder/SndfileDecoderPlugin.hxx +++ b/src/decoder/plugins/SndfileDecoderPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/VorbisComments.cxx b/src/decoder/plugins/VorbisComments.cxx index d4f019b58..062f46acf 100644 --- a/src/decoder/VorbisComments.cxx +++ b/src/decoder/plugins/VorbisComments.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,58 +20,28 @@ #include "config.h" #include "VorbisComments.hxx" #include "XiphTags.hxx" -#include "tag/Tag.hxx" #include "tag/TagTable.hxx" #include "tag/TagHandler.hxx" #include "tag/TagBuilder.hxx" +#include "tag/VorbisComment.hxx" +#include "tag/ReplayGain.hxx" #include "ReplayGainInfo.hxx" #include "util/ASCII.hxx" +#include "util/SplitString.hxx" -#include <glib.h> - -#include <assert.h> #include <stddef.h> -#include <string.h> #include <stdlib.h> -static const char * -vorbis_comment_value(const char *comment, const char *needle) -{ - size_t len = strlen(needle); - - if (StringEqualsCaseASCII(comment, needle, len) && - comment[len] == '=') - return comment + len + 1; - - return nullptr; -} - bool vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments) { rgi.Clear(); - const char *temp; bool found = false; while (*comments) { - if ((temp = - vorbis_comment_value(*comments, "replaygain_track_gain"))) { - rgi.tuples[REPLAY_GAIN_TRACK].gain = atof(temp); + if (ParseReplayGainVorbis(rgi, *comments)) found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_album_gain"))) { - rgi.tuples[REPLAY_GAIN_ALBUM].gain = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_track_peak"))) { - rgi.tuples[REPLAY_GAIN_TRACK].peak = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_album_peak"))) { - rgi.tuples[REPLAY_GAIN_ALBUM].peak = atof(temp); - found = true; - } comments++; } @@ -104,16 +74,11 @@ vorbis_scan_comment(const char *comment, const struct tag_handler *handler, void *handler_ctx) { if (handler->pair != nullptr) { - char *name = g_strdup((const char*)comment); - char *value = strchr(name, '='); - - if (value != nullptr && value > name) { - *value++ = 0; + const SplitString split(comment, '='); + if (split.IsDefined() && !split.IsEmpty()) tag_handler_invoke_pair(handler, handler_ctx, - name, value); - } - - g_free(name); + split.GetFirst(), + split.GetSecond()); } for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) @@ -145,5 +110,5 @@ vorbis_comments_to_tag(char **comments) vorbis_comments_scan(comments, &add_tag_handler, &tag_builder); return tag_builder.IsEmpty() ? nullptr - : tag_builder.Commit(); + : tag_builder.CommitNew(); } diff --git a/src/decoder/VorbisComments.hxx b/src/decoder/plugins/VorbisComments.hxx index e5a48ef6b..893c89277 100644 --- a/src/decoder/VorbisComments.hxx +++ b/src/decoder/plugins/VorbisComments.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -31,7 +31,7 @@ vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments); void vorbis_comments_scan(char **comments, - const struct tag_handler *handler, void *handler_ctx); + const tag_handler *handler, void *handler_ctx); Tag * vorbis_comments_to_tag(char **comments); diff --git a/src/decoder/VorbisDecoderPlugin.cxx b/src/decoder/plugins/VorbisDecoderPlugin.cxx index 4d3e48528..e0d3d1374 100644 --- a/src/decoder/VorbisDecoderPlugin.cxx +++ b/src/decoder/plugins/VorbisDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,13 +21,11 @@ #include "VorbisDecoderPlugin.h" #include "VorbisComments.hxx" #include "VorbisDomain.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" #include "OggCodec.hxx" #include "util/Error.hxx" -#include "util/UriUtil.hxx" #include "util/Macros.hxx" -#include "system/ByteOrder.hxx" #include "CheckAudioFormat.hxx" #include "tag/TagHandler.hxx" #include "Log.hxx" @@ -48,20 +46,23 @@ #define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000)) #endif /* HAVE_TREMOR */ -#include <assert.h> #include <errno.h> -struct vorbis_input_stream { - Decoder *decoder; +struct VorbisInputStream { + Decoder *const decoder; - InputStream *input_stream; + InputStream &input_stream; bool seekable; + + VorbisInputStream(Decoder *_decoder, InputStream &_is) + :decoder(_decoder), input_stream(_is), + seekable(input_stream.CheapSeeking()) {} }; static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data) { - struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; - size_t ret = decoder_read(vis->decoder, *vis->input_stream, + VorbisInputStream *vis = (VorbisInputStream *)data; + size_t ret = decoder_read(vis->decoder, vis->input_stream, ptr, size * nmemb); errno = 0; @@ -69,15 +70,37 @@ static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data) return ret / size; } -static int ogg_seek_cb(void *data, ogg_int64_t offset, int whence) +static int ogg_seek_cb(void *data, ogg_int64_t _offset, int whence) { - struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; + VorbisInputStream *vis = (VorbisInputStream *)data; + InputStream &is = vis->input_stream; - Error error; - return vis->seekable && - (vis->decoder == nullptr || - decoder_get_command(*vis->decoder) != DecoderCommand::STOP) && - vis->input_stream->LockSeek(offset, whence, error) + if (!vis->seekable || + (vis->decoder != nullptr && + decoder_get_command(*vis->decoder) == DecoderCommand::STOP)) + return -1; + + offset_type offset = _offset; + switch (whence) { + case SEEK_SET: + break; + + case SEEK_CUR: + offset += is.GetOffset(); + break; + + case SEEK_END: + if (!is.KnownSize()) + return -1; + + offset += is.GetSize(); + break; + + default: + return -1; + } + + return is.LockSeek(offset, IgnoreError()) ? 0 : -1; } @@ -89,9 +112,9 @@ static int ogg_close_cb(gcc_unused void *data) static long ogg_tell_cb(void *data) { - struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; + VorbisInputStream *vis = (VorbisInputStream *)data; - return (long)vis->input_stream->offset; + return (long)vis->input_stream.GetOffset(); } static const ov_callbacks vorbis_is_callbacks = { @@ -126,17 +149,12 @@ vorbis_strerror(int code) } static bool -vorbis_is_open(struct vorbis_input_stream *vis, OggVorbis_File *vf, - Decoder *decoder, InputStream &input_stream) +vorbis_is_open(VorbisInputStream *vis, OggVorbis_File *vf) { - vis->decoder = decoder; - vis->input_stream = &input_stream; - vis->seekable = input_stream.CheapSeeking(); - int ret = ov_open_callbacks(vis, vf, nullptr, 0, vorbis_is_callbacks); if (ret < 0) { - if (decoder == nullptr || - decoder_get_command(*decoder) == DecoderCommand::NONE) + if (vis->decoder == nullptr || + decoder_get_command(*vis->decoder) == DecoderCommand::NONE) FormatWarning(vorbis_domain, "Failed to open Ogg Vorbis stream: %s", vorbis_strerror(ret)); @@ -165,8 +183,8 @@ vorbis_interleave(float *dest, const float *const*src, { for (const float *const*src_end = src + channels; src != src_end; ++src, ++dest) { - float *d = dest; - for (const float *s = *src, *s_end = s + nframes; + float *gcc_restrict d = dest; + for (const float *gcc_restrict s = *src, *s_end = s + nframes; s != s_end; ++s, d += channels) *d = *s; } @@ -174,6 +192,26 @@ vorbis_interleave(float *dest, const float *const*src, #endif /* public */ + +static bool +vorbis_init(gcc_unused const config_param ¶m) +{ +#ifndef HAVE_TREMOR + LogDebug(vorbis_domain, vorbis_version_string()); +#endif + return true; +} + +gcc_pure +static SignedSongTime +vorbis_duration(OggVorbis_File &vf) +{ + auto total = ov_time_total(&vf, -1); + return total >= 0 + ? SignedSongTime::FromS(total) + : SignedSongTime::Negative(); +} + static void vorbis_stream_decode(Decoder &decoder, InputStream &input_stream) @@ -185,9 +223,9 @@ vorbis_stream_decode(Decoder &decoder, moved it */ input_stream.LockRewind(IgnoreError()); - struct vorbis_input_stream vis; + VorbisInputStream vis(&decoder, input_stream); OggVorbis_File vf; - if (!vorbis_is_open(&vis, &vf, &decoder, input_stream)) + if (!vorbis_is_open(&vis, &vf)) return; const vorbis_info *vi = ov_info(&vf, -1); @@ -209,11 +247,8 @@ vorbis_stream_decode(Decoder &decoder, return; } - float total_time = ov_time_total(&vf, -1); - if (total_time < 0) - total_time = 0; - - decoder_initialized(decoder, audio_format, vis.seekable, total_time); + decoder_initialized(decoder, audio_format, vis.seekable, + vorbis_duration(vf)); #ifdef HAVE_TREMOR char buffer[4096]; @@ -230,8 +265,8 @@ vorbis_stream_decode(Decoder &decoder, DecoderCommand cmd = decoder_get_command(decoder); do { if (cmd == DecoderCommand::SEEK) { - double seek_where = decoder_seek_where(decoder); - if (0 == ov_time_seek_page(&vf, seek_where)) { + auto seek_where = decoder_seek_where_frame(decoder); + if (0 == ov_pcm_seek_page(&vf, seek_where)) { decoder_command_finished(decoder); } else decoder_seek_error(decoder); @@ -306,14 +341,16 @@ static bool vorbis_scan_stream(InputStream &is, const struct tag_handler *handler, void *handler_ctx) { - struct vorbis_input_stream vis; + VorbisInputStream vis(nullptr, is); OggVorbis_File vf; - if (!vorbis_is_open(&vis, &vf, nullptr, is)) + if (!vorbis_is_open(&vis, &vf)) return false; - tag_handler_invoke_duration(handler, handler_ctx, - (int)(ov_time_total(&vf, -1) + 0.5)); + const auto total = ov_time_total(&vf, -1); + if (total >= 0) + tag_handler_invoke_duration(handler, handler_ctx, + SongTime::FromS(total)); vorbis_comments_scan(ov_comment(&vf, -1)->user_comments, handler, handler_ctx); @@ -340,7 +377,7 @@ static const char *const vorbis_mime_types[] = { const struct DecoderPlugin vorbis_decoder_plugin = { "vorbis", - nullptr, + vorbis_init, nullptr, vorbis_stream_decode, nullptr, diff --git a/src/decoder/VorbisDecoderPlugin.h b/src/decoder/plugins/VorbisDecoderPlugin.h index 54953d83a..b54df2e97 100644 --- a/src/decoder/VorbisDecoderPlugin.h +++ b/src/decoder/plugins/VorbisDecoderPlugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/VorbisDomain.cxx b/src/decoder/plugins/VorbisDomain.cxx index 32ff4d6b7..e3d880efa 100644 --- a/src/decoder/VorbisDomain.cxx +++ b/src/decoder/plugins/VorbisDomain.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/VorbisDomain.hxx b/src/decoder/plugins/VorbisDomain.hxx index a35edd041..48715e328 100644 --- a/src/decoder/VorbisDomain.hxx +++ b/src/decoder/plugins/VorbisDomain.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,6 +22,8 @@ #include "check.h" -extern const class Domain vorbis_domain; +class Domain; + +extern const Domain vorbis_domain; #endif diff --git a/src/decoder/WavpackDecoderPlugin.cxx b/src/decoder/plugins/WavpackDecoderPlugin.cxx index 98555c5e8..67859bbd2 100644 --- a/src/decoder/WavpackDecoderPlugin.cxx +++ b/src/decoder/plugins/WavpackDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,11 +19,12 @@ #include "config.h" #include "WavpackDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" #include "CheckAudioFormat.hxx" #include "tag/TagHandler.hxx" #include "tag/ApeTag.hxx" +#include "fs/Path.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "util/Macros.hxx" @@ -159,8 +160,9 @@ wavpack_decode(Decoder &decoder, WavpackContext *wpc, bool can_seek) ? format_samples_float : format_samples_int; - const float total_time = float(WavpackGetNumSamples(wpc)) - / audio_format.sample_rate; + const auto total_time = + SongTime::FromScale<uint64_t>(WavpackGetNumSamples(wpc), + audio_format.sample_rate); const int bytes_per_sample = WavpackGetBytesPerSample(wpc); const int output_sample_size = audio_format.GetFrameSize(); @@ -176,8 +178,7 @@ wavpack_decode(Decoder &decoder, WavpackContext *wpc, bool can_seek) while (cmd != DecoderCommand::STOP) { if (cmd == DecoderCommand::SEEK) { if (can_seek) { - unsigned where = decoder_seek_where(decoder) * - audio_format.sample_rate; + auto where = decoder_seek_where_frame(decoder); if (WavpackSeekSample(wpc, where)) { decoder_command_finished(decoder); @@ -269,21 +270,23 @@ wavpack_scan_pair(WavpackContext *wpc, const char *name, * Reads metainfo from the specified file. */ static bool -wavpack_scan_file(const char *fname, +wavpack_scan_file(Path path_fs, const struct tag_handler *handler, void *handler_ctx) { char error[ERRORLEN]; - WavpackContext *wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0); + WavpackContext *wpc = WavpackOpenFileInput(path_fs.c_str(), error, + OPEN_TAGS, 0); if (wpc == nullptr) { FormatError(wavpack_domain, "failed to open WavPack file \"%s\": %s", - fname, error); + path_fs.c_str(), error); return false; } - tag_handler_invoke_duration(handler, handler_ctx, - WavpackGetNumSamples(wpc) / - WavpackGetSampleRate(wpc)); + const auto duration = + SongTime::FromScale<uint64_t>(WavpackGetNumSamples(wpc), + WavpackGetSampleRate(wpc)); + tag_handler_invoke_duration(handler, handler_ctx, duration); /* the WavPack format implies APEv2 tags, which means we can reuse the mapping from tag_ape.c */ @@ -323,32 +326,43 @@ wavpack_scan_file(const char *fname, */ /* This struct is needed for per-stream last_byte storage. */ -struct wavpack_input { - Decoder *decoder; - InputStream *is; +struct WavpackInput { + Decoder &decoder; + InputStream &is; /* Needed for push_back_byte() */ int last_byte; + + constexpr WavpackInput(Decoder &_decoder, InputStream &_is) + :decoder(_decoder), is(_is), last_byte(EOF) {} + + int32_t ReadBytes(void *data, size_t bcount); }; /** - * Little wrapper for struct wavpack_input to cast from void *. + * Little wrapper for struct WavpackInput to cast from void *. */ -static struct wavpack_input * +static WavpackInput * wpin(void *id) { assert(id); - return (struct wavpack_input *)id; + return (WavpackInput *)id; } static int32_t wavpack_input_read_bytes(void *id, void *data, int32_t bcount) { + return wpin(id)->ReadBytes(data, bcount); +} + +int32_t +WavpackInput::ReadBytes(void *data, size_t bcount) +{ uint8_t *buf = (uint8_t *)data; int32_t i = 0; - if (wpin(id)->last_byte != EOF) { - *buf++ = wpin(id)->last_byte; - wpin(id)->last_byte = EOF; + if (last_byte != EOF) { + *buf++ = last_byte; + last_byte = EOF; --bcount; ++i; } @@ -356,9 +370,7 @@ wavpack_input_read_bytes(void *id, void *data, int32_t bcount) /* wavpack fails if we return a partial read, so we just wait until the buffer is full */ while (bcount > 0) { - size_t nbytes = decoder_read( - wpin(id)->decoder, *wpin(id)->is, buf, bcount - ); + size_t nbytes = decoder_read(&decoder, is, buf, bcount); if (nbytes == 0) { /* EOF, error or a decoder command */ break; @@ -375,26 +387,55 @@ wavpack_input_read_bytes(void *id, void *data, int32_t bcount) static uint32_t wavpack_input_get_pos(void *id) { - return wpin(id)->is->offset; + WavpackInput &wpi = *wpin(id); + + return wpi.is.GetOffset(); } static int wavpack_input_set_pos_abs(void *id, uint32_t pos) { - return wpin(id)->is->LockSeek(pos, SEEK_SET, IgnoreError()) ? 0 : -1; + WavpackInput &wpi = *wpin(id); + + return wpi.is.LockSeek(pos, IgnoreError()) ? 0 : -1; } static int wavpack_input_set_pos_rel(void *id, int32_t delta, int mode) { - return wpin(id)->is->LockSeek(delta, mode, IgnoreError()) ? 0 : -1; + WavpackInput &wpi = *wpin(id); + InputStream &is = wpi.is; + + offset_type offset = delta; + switch (mode) { + case SEEK_SET: + break; + + case SEEK_CUR: + offset += is.GetOffset(); + break; + + case SEEK_END: + if (!is.KnownSize()) + return -1; + + offset += is.GetSize(); + break; + + default: + return -1; + } + + return is.LockSeek(offset, IgnoreError()) ? 0 : -1; } static int wavpack_input_push_back_byte(void *id, int c) { - if (wpin(id)->last_byte == EOF) { - wpin(id)->last_byte = c; + WavpackInput &wpi = *wpin(id); + + if (wpi.last_byte == EOF) { + wpi.last_byte = c; return c; } else { return EOF; @@ -404,16 +445,22 @@ wavpack_input_push_back_byte(void *id, int c) static uint32_t wavpack_input_get_length(void *id) { - if (wpin(id)->is->size < 0) + WavpackInput &wpi = *wpin(id); + InputStream &is = wpi.is; + + if (!is.KnownSize()) return 0; - return wpin(id)->is->size; + return is.GetSize(); } static int wavpack_input_can_seek(void *id) { - return wpin(id)->is->seekable; + WavpackInput &wpi = *wpin(id); + InputStream &is = wpi.is; + + return is.IsSeekable(); } static WavpackStreamReader mpd_is_reader = { @@ -427,19 +474,8 @@ static WavpackStreamReader mpd_is_reader = { nullptr /* no need to write edited tags */ }; -static void -wavpack_input_init(struct wavpack_input *isp, Decoder &decoder, - InputStream &is) -{ - isp->decoder = &decoder; - isp->is = &is; - isp->last_byte = EOF; -} - -static InputStream * -wavpack_open_wvc(Decoder &decoder, const char *uri, - Mutex &mutex, Cond &cond, - struct wavpack_input *wpi) +static WavpackInput * +wavpack_open_wvc(Decoder &decoder, const char *uri) { /* * As we use dc->utf8url, this function will be bad for @@ -450,29 +486,13 @@ wavpack_open_wvc(Decoder &decoder, const char *uri, char *wvc_url = g_strconcat(uri, "c", nullptr); - InputStream *is_wvc = InputStream::Open(wvc_url, mutex, cond, - IgnoreError()); + InputStream *is_wvc = decoder_open_uri(decoder, uri, IgnoreError()); g_free(wvc_url); if (is_wvc == nullptr) return nullptr; - /* - * And we try to buffer in order to get know - * about a possible 404 error. - */ - char first_byte; - size_t nbytes = decoder_read(decoder, *is_wvc, - &first_byte, sizeof(first_byte)); - if (nbytes == 0) { - is_wvc->Close(); - return nullptr; - } - - /* push it back */ - wavpack_input_init(wpi, decoder, *is_wvc); - wpi->last_byte = first_byte; - return is_wvc; + return new WavpackInput(decoder, *is_wvc); } /* @@ -482,29 +502,23 @@ static void wavpack_streamdecode(Decoder &decoder, InputStream &is) { int open_flags = OPEN_NORMALIZE; - bool can_seek = is.seekable; + bool can_seek = is.IsSeekable(); - wavpack_input isp_wvc; - InputStream *is_wvc = wavpack_open_wvc(decoder, is.uri.c_str(), - is.mutex, is.cond, - &isp_wvc); - if (is_wvc != nullptr) { + WavpackInput *wvc = wavpack_open_wvc(decoder, is.GetURI()); + if (wvc != nullptr) { open_flags |= OPEN_WVC; - can_seek &= is_wvc->seekable; + can_seek &= wvc->is.IsSeekable(); } if (!can_seek) { open_flags |= OPEN_STREAMING; } - wavpack_input isp; - wavpack_input_init(&isp, decoder, is); + WavpackInput isp(decoder, is); char error[ERRORLEN]; WavpackContext *wpc = - WavpackOpenFileInputEx(&mpd_is_reader, &isp, - open_flags & OPEN_WVC - ? &isp_wvc : nullptr, + WavpackOpenFileInputEx(&mpd_is_reader, &isp, wvc, error, open_flags, 23); if (wpc == nullptr) { @@ -516,8 +530,10 @@ wavpack_streamdecode(Decoder &decoder, InputStream &is) wavpack_decode(decoder, wpc, can_seek); WavpackCloseFile(wpc); - if (open_flags & OPEN_WVC) { - is_wvc->Close(); + + if (wvc != nullptr) { + delete &wvc->is; + delete wvc; } } @@ -525,16 +541,16 @@ wavpack_streamdecode(Decoder &decoder, InputStream &is) * Decodes a file. */ static void -wavpack_filedecode(Decoder &decoder, const char *fname) +wavpack_filedecode(Decoder &decoder, Path path_fs) { char error[ERRORLEN]; - WavpackContext *wpc = WavpackOpenFileInput(fname, error, + WavpackContext *wpc = WavpackOpenFileInput(path_fs.c_str(), error, OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, 23); if (wpc == nullptr) { FormatWarning(wavpack_domain, "failed to open WavPack file \"%s\": %s", - fname, error); + path_fs.c_str(), error); return; } diff --git a/src/decoder/WavpackDecoderPlugin.hxx b/src/decoder/plugins/WavpackDecoderPlugin.hxx index 3a2d94532..2e5f9bd42 100644 --- a/src/decoder/WavpackDecoderPlugin.hxx +++ b/src/decoder/plugins/WavpackDecoderPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/WildmidiDecoderPlugin.cxx b/src/decoder/plugins/WildmidiDecoderPlugin.cxx index 3da3f1387..fc58f0977 100644 --- a/src/decoder/WildmidiDecoderPlugin.cxx +++ b/src/decoder/plugins/WildmidiDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,12 +19,13 @@ #include "config.h" #include "WildmidiDecoderPlugin.hxx" -#include "DecoderAPI.hxx" +#include "../DecoderAPI.hxx" #include "tag/TagHandler.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "fs/AllocatedPath.hxx" #include "fs/FileSystem.hxx" +#include "fs/Path.hxx" #include "system/FatalError.hxx" #include "Log.hxx" @@ -65,7 +66,7 @@ wildmidi_finish(void) } static void -wildmidi_file_decode(Decoder &decoder, const char *path_fs) +wildmidi_file_decode(Decoder &decoder, Path path_fs) { static constexpr AudioFormat audio_format = { WILDMIDI_SAMPLE_RATE, @@ -75,7 +76,7 @@ wildmidi_file_decode(Decoder &decoder, const char *path_fs) midi *wm; const struct _WM_Info *info; - wm = WildMidi_Open(path_fs); + wm = WildMidi_Open(path_fs.c_str()); if (wm == nullptr) return; @@ -85,8 +86,11 @@ wildmidi_file_decode(Decoder &decoder, const char *path_fs) return; } - decoder_initialized(decoder, audio_format, true, - info->approx_total_samples / WILDMIDI_SAMPLE_RATE); + const auto duration = + SongTime::FromScale<uint64_t>(info->approx_total_samples, + WILDMIDI_SAMPLE_RATE); + + decoder_initialized(decoder, audio_format, true, duration); DecoderCommand cmd; do { @@ -104,8 +108,8 @@ wildmidi_file_decode(Decoder &decoder, const char *path_fs) cmd = decoder_data(decoder, nullptr, buffer, len, 0); if (cmd == DecoderCommand::SEEK) { - unsigned long seek_where = WILDMIDI_SAMPLE_RATE * - decoder_seek_where(decoder); + unsigned long seek_where = + decoder_seek_where_frame(decoder); WildMidi_FastSeek(wm, &seek_where); decoder_command_finished(decoder); @@ -118,10 +122,10 @@ wildmidi_file_decode(Decoder &decoder, const char *path_fs) } static bool -wildmidi_scan_file(const char *path_fs, +wildmidi_scan_file(Path path_fs, const struct tag_handler *handler, void *handler_ctx) { - midi *wm = WildMidi_Open(path_fs); + midi *wm = WildMidi_Open(path_fs.c_str()); if (wm == nullptr) return false; @@ -131,7 +135,9 @@ wildmidi_scan_file(const char *path_fs, return false; } - int duration = info->approx_total_samples / WILDMIDI_SAMPLE_RATE; + const auto duration = + SongTime::FromScale<uint64_t>(info->approx_total_samples, + WILDMIDI_SAMPLE_RATE); tag_handler_invoke_duration(handler, handler_ctx, duration); WildMidi_Close(wm); diff --git a/src/decoder/WildmidiDecoderPlugin.hxx b/src/decoder/plugins/WildmidiDecoderPlugin.hxx index a6289612e..fc87aab80 100644 --- a/src/decoder/WildmidiDecoderPlugin.hxx +++ b/src/decoder/plugins/WildmidiDecoderPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/XiphTags.cxx b/src/decoder/plugins/XiphTags.cxx index b9958a19a..11a0bcd42 100644 --- a/src/decoder/XiphTags.cxx +++ b/src/decoder/plugins/XiphTags.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +/* This File contains additional Tags for Xiph-Based Formats like Ogg-Vorbis, + * Flac and Opus which will be used in addition to the Tags in tag/TagNames.c + * see https://www.xiph.org/vorbis/doc/v-comment.html for further Info + */ #include "config.h" #include "XiphTags.hxx" @@ -24,5 +28,6 @@ const struct tag_table xiph_tags[] = { { "tracknumber", TAG_TRACK }, { "discnumber", TAG_DISC }, { "album artist", TAG_ALBUM_ARTIST }, + { "description", TAG_COMMENT }, { nullptr, TAG_NUM_OF_ITEM_TYPES } }; diff --git a/src/decoder/XiphTags.hxx b/src/decoder/plugins/XiphTags.hxx index 606dfef10..48a27425f 100644 --- a/src/decoder/XiphTags.hxx +++ b/src/decoder/plugins/XiphTags.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify |