release v0.19.3

-----BEGIN PGP SIGNATURE-----
 Version: GnuPG v2
 
 iQIcBAABAgAGBQJUYeOEAAoJECNuiljG20USToUP/2g/M7jKlM1MpADeFh2TOYEC
 dnSDPFCMosu1Hjxyz/rzwqOh7FlMnYPtANtVn90v4D6XVkuhv5WMwSnlMBWJXspy
 VWA6bewGihbWvDvOcLcYsb31VH78JnvNjxxDaeakVZ7Bq43wQBJRNi2VKEKMmg27
 Shp8MUL+UXsMMtLABvZlhajPQS+CNQB1DijP7wlemrbPpmUtmeHEgbpmQk9BKBq0
 eXJt5QRQRgI9J51j1yaXSaeKsPFk/2AV36KMBv3kh+rgCGWetd1XeBkknN/HjNw4
 2qWsQ8Ng3s/kOCZhEa0nXqnR+w2RtpOJ4bOQaIDeBSzCdXSeDWq5yh/kHKHsXzO4
 5uq++GwCcm/Odwj17rJ4wJDGnl4DkP8abQIPb4AiwfpuUtQ+fHvQuNU0Dc2SY4Oz
 KkNU4nIcJwaHlDAQDsWjowAJJC2W0GPSGzVKCWjH2W3kmJZq2hV3o3y3YOMH8gpN
 wmdmqE8JKCOJm5QxnfoiMKoUxUgNMxN3iceMnDkNqz/g1uOq5TUk8B5adLuY0OSg
 n+2eDBaUNe2tCbxgIeKxVlXrWVBZyrzwFk3FGyVuGhobmVJ1Wwvx5kOIVvwo0JTC
 LSc8FP4R8/87G1XlVMo0alqChLpXbH+ncaGhdN74pKVP8TFtJCoTPgRPj+svF9YY
 A7AiNXCXCCOD78gKYCtu
 =GH2D
 -----END PGP SIGNATURE-----

Merge tag 'v0.19.3'
This commit is contained in:
Max Kellermann 2014-11-11 11:43:52 +01:00
commit c3f6502be2
9 changed files with 96 additions and 24 deletions

12
NEWS
View File

@ -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

View File

@ -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).

View File

@ -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>

View File

@ -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);

View File

@ -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,21 +291,36 @@ 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)
{
@ -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;
}

View File

@ -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();

View File

@ -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);

View File

@ -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);

View File

@ -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;
}