Merge tag 'v0.19.3'
This commit is contained in:
12
NEWS
12
NEWS
@ -4,12 +4,22 @@ ver 0.20 (not yet released)
|
|||||||
* output
|
* output
|
||||||
- pulse: set channel map to WAVE-EX
|
- 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
|
* database
|
||||||
- upnp: fix breakage due to malformed URIs
|
- upnp: fix breakage due to malformed URIs
|
||||||
|
* input
|
||||||
|
- curl: another fix for redirected streams
|
||||||
* decoder
|
* decoder
|
||||||
- audiofile: fix crash while playing streams
|
- audiofile: fix crash while playing streams
|
||||||
|
- audiofile: fix bit rate calculation
|
||||||
- ffmpeg: support opus
|
- 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)
|
ver 0.19.2 (2014/11/02)
|
||||||
* input
|
* input
|
||||||
|
@ -114,7 +114,7 @@
|
|||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __BLOCKS__
|
||||||
#include <dispatch/dispatch.h>
|
#include <dispatch/dispatch.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -517,7 +517,7 @@ int mpd_main(int argc, char *argv[])
|
|||||||
daemonize_begin(options.daemon);
|
daemonize_begin(options.daemon);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __BLOCKS__
|
||||||
/* Runs the OS X native event loop in the main thread, and runs
|
/* 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
|
the rest of mpd_main on a new thread. This lets CoreAudio receive
|
||||||
route change notifications (e.g. plugging or unplugging headphones).
|
route change notifications (e.g. plugging or unplugging headphones).
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
#include "Interface.hxx"
|
#include "Interface.hxx"
|
||||||
#include "client/Client.hxx"
|
#include "client/Client.hxx"
|
||||||
#include "LightSong.hxx"
|
#include "LightSong.hxx"
|
||||||
#include "tag/Set.hxx"
|
#include "tag/Tag.hxx"
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
@ -209,7 +209,7 @@ audiofile_stream_decode(Decoder &decoder, InputStream &is)
|
|||||||
const auto total_time = audiofile_get_duration(fh);
|
const auto total_time = audiofile_get_duration(fh);
|
||||||
|
|
||||||
const uint16_t kbit_rate = (uint16_t)
|
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)
|
const unsigned frame_size = (unsigned)
|
||||||
afGetVirtualFrameSize(fh, AF_DEFAULT_TRACK, true);
|
afGetVirtualFrameSize(fh, AF_DEFAULT_TRACK, true);
|
||||||
|
@ -40,6 +40,12 @@
|
|||||||
|
|
||||||
static constexpr opus_int32 opus_sample_rate = 48000;
|
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
|
gcc_pure
|
||||||
static bool
|
static bool
|
||||||
IsOpusHead(const ogg_packet &packet)
|
IsOpusHead(const ogg_packet &packet)
|
||||||
@ -70,10 +76,16 @@ class MPDOpusDecoder {
|
|||||||
|
|
||||||
OpusDecoder *opus_decoder;
|
OpusDecoder *opus_decoder;
|
||||||
opus_int16 *output_buffer;
|
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 os_initialized;
|
||||||
bool found_opus;
|
|
||||||
|
|
||||||
int opus_serialno;
|
int opus_serialno;
|
||||||
|
|
||||||
@ -86,8 +98,9 @@ public:
|
|||||||
InputStream &_input_stream)
|
InputStream &_input_stream)
|
||||||
:decoder(_decoder), input_stream(_input_stream),
|
:decoder(_decoder), input_stream(_input_stream),
|
||||||
opus_decoder(nullptr),
|
opus_decoder(nullptr),
|
||||||
output_buffer(nullptr), output_size(0),
|
output_buffer(nullptr),
|
||||||
os_initialized(false), found_opus(false) {}
|
previous_channels(0),
|
||||||
|
os_initialized(false) {}
|
||||||
~MPDOpusDecoder();
|
~MPDOpusDecoder();
|
||||||
|
|
||||||
bool ReadFirstPage(OggSyncState &oy);
|
bool ReadFirstPage(OggSyncState &oy);
|
||||||
@ -96,6 +109,7 @@ public:
|
|||||||
DecoderCommand HandlePackets();
|
DecoderCommand HandlePackets();
|
||||||
DecoderCommand HandlePacket(const ogg_packet &packet);
|
DecoderCommand HandlePacket(const ogg_packet &packet);
|
||||||
DecoderCommand HandleBOS(const ogg_packet &packet);
|
DecoderCommand HandleBOS(const ogg_packet &packet);
|
||||||
|
DecoderCommand HandleEOS();
|
||||||
DecoderCommand HandleTags(const ogg_packet &packet);
|
DecoderCommand HandleTags(const ogg_packet &packet);
|
||||||
DecoderCommand HandleAudio(const ogg_packet &packet);
|
DecoderCommand HandleAudio(const ogg_packet &packet);
|
||||||
|
|
||||||
@ -159,12 +173,14 @@ inline DecoderCommand
|
|||||||
MPDOpusDecoder::HandlePacket(const ogg_packet &packet)
|
MPDOpusDecoder::HandlePacket(const ogg_packet &packet)
|
||||||
{
|
{
|
||||||
if (packet.e_o_s)
|
if (packet.e_o_s)
|
||||||
return DecoderCommand::STOP;
|
return HandleEOS();
|
||||||
|
|
||||||
if (packet.b_o_s)
|
if (packet.b_o_s)
|
||||||
return HandleBOS(packet);
|
return HandleBOS(packet);
|
||||||
else if (!found_opus)
|
else if (opus_decoder == nullptr) {
|
||||||
|
LogDebug(opus_domain, "BOS packet expected");
|
||||||
return DecoderCommand::STOP;
|
return DecoderCommand::STOP;
|
||||||
|
}
|
||||||
|
|
||||||
if (IsOpusTags(packet))
|
if (IsOpusTags(packet))
|
||||||
return HandleTags(packet);
|
return HandleTags(packet);
|
||||||
@ -184,7 +200,7 @@ LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno,
|
|||||||
/* we do this for local files only, because seeking
|
/* we do this for local files only, because seeking
|
||||||
around remote files is expensive and not worth the
|
around remote files is expensive and not worth the
|
||||||
troubl */
|
troubl */
|
||||||
return -1;
|
return false;
|
||||||
|
|
||||||
const auto old_offset = is.GetOffset();
|
const auto old_offset = is.GetOffset();
|
||||||
|
|
||||||
@ -225,19 +241,29 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
|
|||||||
{
|
{
|
||||||
assert(packet.b_o_s);
|
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;
|
return DecoderCommand::STOP;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned channels;
|
unsigned channels;
|
||||||
if (!ScanOpusHeader(packet.packet, packet.bytes, 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;
|
return DecoderCommand::STOP;
|
||||||
|
}
|
||||||
|
|
||||||
assert(opus_decoder == nullptr);
|
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;
|
opus_serialno = os.serialno;
|
||||||
found_opus = true;
|
|
||||||
|
|
||||||
/* TODO: parse attributes from the OpusHead (sample rate,
|
/* TODO: parse attributes from the OpusHead (sample rate,
|
||||||
channels, ...) */
|
channels, ...) */
|
||||||
@ -251,6 +277,13 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
|
|||||||
return DecoderCommand::STOP;
|
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,
|
eos_granulepos = LoadEOSGranulePos(input_stream, &decoder,
|
||||||
opus_serialno);
|
opus_serialno);
|
||||||
const auto duration = eos_granulepos >= 0
|
const auto duration = eos_granulepos >= 0
|
||||||
@ -258,21 +291,36 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
|
|||||||
opus_sample_rate)
|
opus_sample_rate)
|
||||||
: SignedSongTime::Negative();
|
: SignedSongTime::Negative();
|
||||||
|
|
||||||
|
previous_channels = channels;
|
||||||
const AudioFormat audio_format(opus_sample_rate,
|
const AudioFormat audio_format(opus_sample_rate,
|
||||||
SampleFormat::S16, channels);
|
SampleFormat::S16, channels);
|
||||||
decoder_initialized(decoder, audio_format,
|
decoder_initialized(decoder, audio_format,
|
||||||
eos_granulepos > 0, duration);
|
eos_granulepos > 0, duration);
|
||||||
frame_size = audio_format.GetFrameSize();
|
frame_size = audio_format.GetFrameSize();
|
||||||
|
|
||||||
/* allocate an output buffer for 16 bit PCM samples big enough
|
output_buffer = new opus_int16[opus_output_buffer_frames
|
||||||
to hold a quarter second, larger than 120ms required by
|
* audio_format.channels];
|
||||||
libopus */
|
|
||||||
output_size = audio_format.sample_rate / 4;
|
|
||||||
output_buffer = new opus_int16[output_size * audio_format.channels];
|
|
||||||
|
|
||||||
return decoder_get_command(decoder);
|
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
|
inline DecoderCommand
|
||||||
MPDOpusDecoder::HandleTags(const ogg_packet &packet)
|
MPDOpusDecoder::HandleTags(const ogg_packet &packet)
|
||||||
{
|
{
|
||||||
@ -304,10 +352,11 @@ MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
|
|||||||
int nframes = opus_decode(opus_decoder,
|
int nframes = opus_decode(opus_decoder,
|
||||||
(const unsigned char*)packet.packet,
|
(const unsigned char*)packet.packet,
|
||||||
packet.bytes,
|
packet.bytes,
|
||||||
output_buffer, output_size,
|
output_buffer, opus_output_buffer_frames,
|
||||||
0);
|
0);
|
||||||
if (nframes < 0) {
|
if (nframes < 0) {
|
||||||
LogError(opus_domain, opus_strerror(nframes));
|
FormatError(opus_domain, "libopus error: %s",
|
||||||
|
opus_strerror(nframes));
|
||||||
return DecoderCommand::STOP;
|
return DecoderCommand::STOP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -610,6 +610,10 @@ CurlInputStream::ResponseBoundary()
|
|||||||
/* undo all effects of HeaderReceived() because the previous
|
/* undo all effects of HeaderReceived() because the previous
|
||||||
response was not applicable for this stream */
|
response was not applicable for this stream */
|
||||||
|
|
||||||
|
if (IsSeekPending())
|
||||||
|
/* don't update metadata while seeking */
|
||||||
|
return;
|
||||||
|
|
||||||
seekable = false;
|
seekable = false;
|
||||||
size = UNKNOWN_SIZE;
|
size = UNKNOWN_SIZE;
|
||||||
ClearMimeType();
|
ClearMimeType();
|
||||||
|
@ -147,7 +147,8 @@ SoxrPcmResampler::Resample(ConstBuffer<void> src, Error &error)
|
|||||||
|
|
||||||
const size_t n_frames = src.size / frame_size;
|
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);
|
float *output_buffer = (float *)buffer.Get(o_frames * frame_size);
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "Set.hxx"
|
#include "Set.hxx"
|
||||||
#include "TagBuilder.hxx"
|
#include "TagBuilder.hxx"
|
||||||
|
#include "TagSettings.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
@ -109,6 +110,7 @@ TagSet::InsertUnique(const Tag &tag,
|
|||||||
|
|
||||||
if (!CheckUnique(type, tag, type, group_mask) &&
|
if (!CheckUnique(type, tag, type, group_mask) &&
|
||||||
(type != TAG_ALBUM_ARTIST ||
|
(type != TAG_ALBUM_ARTIST ||
|
||||||
|
ignore_tag_items[TAG_ALBUM_ARTIST] ||
|
||||||
/* fall back to "Artist" if no "AlbumArtist" was found */
|
/* fall back to "Artist" if no "AlbumArtist" was found */
|
||||||
!CheckUnique(type, tag, TAG_ARTIST, group_mask)))
|
!CheckUnique(type, tag, TAG_ARTIST, group_mask)))
|
||||||
InsertUnique(tag, type, nullptr, group_mask);
|
InsertUnique(tag, type, nullptr, group_mask);
|
||||||
|
@ -132,6 +132,12 @@ decoder_data(gcc_unused Decoder &decoder,
|
|||||||
const void *data, size_t datalen,
|
const void *data, size_t datalen,
|
||||||
gcc_unused uint16_t kbit_rate)
|
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);
|
gcc_unused ssize_t nbytes = write(1, data, datalen);
|
||||||
return DecoderCommand::NONE;
|
return DecoderCommand::NONE;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user