diff options
-rw-r--r-- | NEWS | 12 | ||||
-rw-r--r-- | src/Main.cxx | 4 | ||||
-rw-r--r-- | src/db/Count.cxx | 2 | ||||
-rw-r--r-- | src/decoder/plugins/AudiofileDecoderPlugin.cxx | 2 | ||||
-rw-r--r-- | src/decoder/plugins/OpusDecoderPlugin.cxx | 85 | ||||
-rw-r--r-- | src/input/plugins/CurlInputPlugin.cxx | 4 | ||||
-rw-r--r-- | src/pcm/SoxrResampler.cxx | 3 | ||||
-rw-r--r-- | src/tag/Set.cxx | 2 | ||||
-rw-r--r-- | test/FakeDecoderAPI.cxx | 6 |
9 files changed, 96 insertions, 24 deletions
@@ -4,12 +4,22 @@ ver 0.20 (not yet released) * output - pulse: set channel map to WAVE-EX -ver 0.19.3 (not yet released) +ver 0.19.3 (2014/11/11) +* protocol + - fix "(null)" result string to "list" when AlbumArtist is disabled * database - upnp: fix breakage due to malformed URIs +* input + - curl: another fix for redirected streams * decoder - audiofile: fix crash while playing streams + - audiofile: fix bit rate calculation - ffmpeg: support opus + - opus: fix bogus duration on streams + - opus: support chained streams + - opus: improved error logging +* fix distorted audio with soxr resampler +* fix build failure on Mac OS X with non-Apple compilers ver 0.19.2 (2014/11/02) * input diff --git a/src/Main.cxx b/src/Main.cxx index 2719c05e0..26d4e7ae4 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -114,7 +114,7 @@ #include <ws2tcpip.h> #endif -#ifdef __APPLE__ +#ifdef __BLOCKS__ #include <dispatch/dispatch.h> #endif @@ -517,7 +517,7 @@ int mpd_main(int argc, char *argv[]) daemonize_begin(options.daemon); #endif -#ifdef __APPLE__ +#ifdef __BLOCKS__ /* Runs the OS X native event loop in the main thread, and runs the rest of mpd_main on a new thread. This lets CoreAudio receive route change notifications (e.g. plugging or unplugging headphones). diff --git a/src/db/Count.cxx b/src/db/Count.cxx index b58ac53e9..e5e244a6a 100644 --- a/src/db/Count.cxx +++ b/src/db/Count.cxx @@ -23,7 +23,7 @@ #include "Interface.hxx" #include "client/Client.hxx" #include "LightSong.hxx" -#include "tag/Set.hxx" +#include "tag/Tag.hxx" #include <functional> #include <map> diff --git a/src/decoder/plugins/AudiofileDecoderPlugin.cxx b/src/decoder/plugins/AudiofileDecoderPlugin.cxx index 4b347817e..a0ef71e49 100644 --- a/src/decoder/plugins/AudiofileDecoderPlugin.cxx +++ b/src/decoder/plugins/AudiofileDecoderPlugin.cxx @@ -209,7 +209,7 @@ audiofile_stream_decode(Decoder &decoder, InputStream &is) const auto total_time = audiofile_get_duration(fh); const uint16_t kbit_rate = (uint16_t) - (is.GetSize() * uint64_t(8000) / total_time.ToMS()); + (is.GetSize() * uint64_t(8) / total_time.ToMS()); const unsigned frame_size = (unsigned) afGetVirtualFrameSize(fh, AF_DEFAULT_TRACK, true); diff --git a/src/decoder/plugins/OpusDecoderPlugin.cxx b/src/decoder/plugins/OpusDecoderPlugin.cxx index 97c81984c..b6b2e0465 100644 --- a/src/decoder/plugins/OpusDecoderPlugin.cxx +++ b/src/decoder/plugins/OpusDecoderPlugin.cxx @@ -40,6 +40,12 @@ 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) @@ -70,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; @@ -86,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); @@ -96,6 +109,7 @@ 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); @@ -159,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); @@ -184,7 +200,7 @@ 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.GetOffset(); @@ -225,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, ...) */ @@ -251,6 +277,13 @@ 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 auto duration = eos_granulepos >= 0 @@ -258,22 +291,37 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet) 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 = new opus_int16[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; @@ -304,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; } diff --git a/src/input/plugins/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx index 1e1a46108..abb7e312c 100644 --- a/src/input/plugins/CurlInputPlugin.cxx +++ b/src/input/plugins/CurlInputPlugin.cxx @@ -610,6 +610,10 @@ CurlInputStream::ResponseBoundary() /* undo all effects of HeaderReceived() because the previous response was not applicable for this stream */ + if (IsSeekPending()) + /* don't update metadata while seeking */ + return; + seekable = false; size = UNKNOWN_SIZE; ClearMimeType(); diff --git a/src/pcm/SoxrResampler.cxx b/src/pcm/SoxrResampler.cxx index 56b9760d5..b9d6fc099 100644 --- a/src/pcm/SoxrResampler.cxx +++ b/src/pcm/SoxrResampler.cxx @@ -147,7 +147,8 @@ SoxrPcmResampler::Resample(ConstBuffer<void> src, Error &error) const size_t n_frames = src.size / frame_size; - const size_t o_frames = size_t(n_frames * ratio + 0.5); + /* always round up: worst case output buffer size */ + const size_t o_frames = size_t(n_frames * ratio) + 1; float *output_buffer = (float *)buffer.Get(o_frames * frame_size); diff --git a/src/tag/Set.cxx b/src/tag/Set.cxx index 47a8423bf..6a55a450f 100644 --- a/src/tag/Set.cxx +++ b/src/tag/Set.cxx @@ -19,6 +19,7 @@ #include "Set.hxx" #include "TagBuilder.hxx" +#include "TagSettings.h" #include <assert.h> @@ -109,6 +110,7 @@ TagSet::InsertUnique(const Tag &tag, if (!CheckUnique(type, tag, type, group_mask) && (type != TAG_ALBUM_ARTIST || + ignore_tag_items[TAG_ALBUM_ARTIST] || /* fall back to "Artist" if no "AlbumArtist" was found */ !CheckUnique(type, tag, TAG_ARTIST, group_mask))) InsertUnique(tag, type, nullptr, group_mask); diff --git a/test/FakeDecoderAPI.cxx b/test/FakeDecoderAPI.cxx index dcc78125b..3c7453a4f 100644 --- a/test/FakeDecoderAPI.cxx +++ b/test/FakeDecoderAPI.cxx @@ -132,6 +132,12 @@ decoder_data(gcc_unused Decoder &decoder, const void *data, size_t datalen, gcc_unused uint16_t kbit_rate) { + static uint16_t prev_kbit_rate; + if (kbit_rate != prev_kbit_rate) { + prev_kbit_rate = kbit_rate; + fprintf(stderr, "%u kbit/s\n", kbit_rate); + } + gcc_unused ssize_t nbytes = write(1, data, datalen); return DecoderCommand::NONE; } |