Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c240f667c | ||
|
|
3040ddb5ec | ||
|
|
fdb28eb0c4 | ||
|
|
7ded244a61 | ||
|
|
8ed533acf3 | ||
|
|
a27580d0cc | ||
|
|
905db05cf9 | ||
|
|
4242aee21e | ||
|
|
e71bd2a08b | ||
|
|
e53a4d0a9e | ||
|
|
159389164a | ||
|
|
0a92fbc18e | ||
|
|
138c29320b | ||
|
|
8f00dbea45 | ||
|
|
f3fd2eb618 | ||
|
|
fc92db83cf | ||
|
|
3b0f8d5516 | ||
|
|
a5273d6992 | ||
|
|
b18074f899 | ||
|
|
3d8067a041 | ||
|
|
f6fe001fa9 | ||
|
|
32a5bf043b | ||
|
|
8d2079482f | ||
|
|
c331c75fde | ||
|
|
6080c3b4ba |
18
NEWS
18
NEWS
@@ -1,3 +1,21 @@
|
|||||||
|
ver 0.21.23 (2020/04/23)
|
||||||
|
* protocol
|
||||||
|
- add tag fallback for AlbumSort
|
||||||
|
* storage
|
||||||
|
- curl: fix corrupt "href" values in the presence of XML entities
|
||||||
|
- curl: unescape "href" values
|
||||||
|
* input
|
||||||
|
- nfs: fix crash bug
|
||||||
|
- nfs: fix freeze bug on reconnect
|
||||||
|
* decoder
|
||||||
|
- gme: adapt to API change in the upcoming version 0.7.0
|
||||||
|
* output
|
||||||
|
- alsa: implement channel mapping for 5.0 and 7.0
|
||||||
|
* player
|
||||||
|
- drain outputs at end of song in "single" mode
|
||||||
|
* Windows
|
||||||
|
- fix case insensitive search
|
||||||
|
|
||||||
ver 0.21.22 (2020/04/02)
|
ver 0.21.22 (2020/04/02)
|
||||||
* database
|
* database
|
||||||
- simple: optimize startup
|
- simple: optimize startup
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.musicpd"
|
package="org.musicpd"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:versionCode="45"
|
android:versionCode="46"
|
||||||
android:versionName="0.21.22">
|
android:versionName="0.21.23">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ author = 'Max Kellermann'
|
|||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.21.22'
|
version = '0.21.23'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = version
|
release = version
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
project(
|
project(
|
||||||
'mpd',
|
'mpd',
|
||||||
['c', 'cpp'],
|
['c', 'cpp'],
|
||||||
version: '0.21.22',
|
version: '0.21.23',
|
||||||
meson_version: '>= 0.49.0',
|
meson_version: '>= 0.49.0',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c99',
|
'c_std=c99',
|
||||||
|
|||||||
@@ -185,7 +185,11 @@ gme_file_decode(DecoderClient &client, Path path_fs)
|
|||||||
LogWarning(gme_domain, gme_err);
|
LogWarning(gme_domain, gme_err);
|
||||||
|
|
||||||
if (length > 0)
|
if (length > 0)
|
||||||
gme_set_fade(emu, length);
|
gme_set_fade(emu, length
|
||||||
|
#if GME_VERSION >= 0x000700
|
||||||
|
, 8000
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
|
||||||
/* play */
|
/* play */
|
||||||
DecoderCommand cmd;
|
DecoderCommand cmd;
|
||||||
|
|||||||
@@ -23,8 +23,8 @@
|
|||||||
|
|
||||||
#include "PollGroupWinSelect.hxx"
|
#include "PollGroupWinSelect.hxx"
|
||||||
|
|
||||||
constexpr int EVENT_READ = 0;
|
static constexpr int EVENT_READ = 0;
|
||||||
constexpr int EVENT_WRITE = 1;
|
static constexpr int EVENT_WRITE = 1;
|
||||||
|
|
||||||
static constexpr
|
static constexpr
|
||||||
bool HasEvent(unsigned events, int event_id) noexcept
|
bool HasEvent(unsigned events, int event_id) noexcept
|
||||||
|
|||||||
@@ -20,6 +20,10 @@
|
|||||||
#include "SocketMonitor.hxx"
|
#include "SocketMonitor.hxx"
|
||||||
#include "Loop.hxx"
|
#include "Loop.hxx"
|
||||||
|
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
#include <cerrno>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -86,6 +90,21 @@ SocketMonitor::Schedule(unsigned flags) noexcept
|
|||||||
|
|
||||||
if (success)
|
if (success)
|
||||||
scheduled_flags = flags;
|
scheduled_flags = flags;
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
else if (errno == EBADF || errno == ENOENT)
|
||||||
|
/* the socket was probably closed by somebody else
|
||||||
|
(EBADF) or a new file descriptor with the same
|
||||||
|
number was created but not registered already
|
||||||
|
(ENOENT) - we can assume that there are no
|
||||||
|
scheduled events */
|
||||||
|
/* note that when this happens, we're actually lucky
|
||||||
|
that it has failed - imagine another thread may
|
||||||
|
meanwhile have created something on the same file
|
||||||
|
descriptor number, and has registered it; the
|
||||||
|
epoll_ctl() call above would then have succeeded,
|
||||||
|
but broke the other thread's epoll registration */
|
||||||
|
scheduled_flags = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ScheduleRead() noexcept {
|
bool ScheduleRead() noexcept {
|
||||||
return Schedule(GetScheduledFlags() | READ | HANGUP | ERROR);
|
return Schedule(GetScheduledFlags() | READ);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ScheduleWrite() noexcept {
|
bool ScheduleWrite() noexcept {
|
||||||
@@ -117,7 +117,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CancelRead() noexcept {
|
void CancelRead() noexcept {
|
||||||
Schedule(GetScheduledFlags() & ~(READ|HANGUP|ERROR));
|
Schedule(GetScheduledFlags() & ~READ);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CancelWrite() noexcept {
|
void CancelWrite() noexcept {
|
||||||
|
|||||||
@@ -90,6 +90,11 @@ public:
|
|||||||
constexpr
|
constexpr
|
||||||
#endif
|
#endif
|
||||||
operator Path() const noexcept {
|
operator Path() const noexcept {
|
||||||
|
#ifdef _UNICODE
|
||||||
|
if (value.IsNull())
|
||||||
|
return nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,11 +36,6 @@
|
|||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include "Win32.hxx"
|
|
||||||
#include <windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@@ -72,25 +67,6 @@ try {
|
|||||||
folded.SetSize(folded_length);
|
folded.SetSize(folded_length);
|
||||||
return UCharToUTF8({folded.begin(), folded.size()});
|
return UCharToUTF8({folded.begin(), folded.size()});
|
||||||
|
|
||||||
#elif defined(_WIN32)
|
|
||||||
const auto u = MultiByteToWideChar(CP_UTF8, src);
|
|
||||||
|
|
||||||
const int size = LCMapStringEx(LOCALE_NAME_INVARIANT,
|
|
||||||
LCMAP_SORTKEY|LINGUISTIC_IGNORECASE,
|
|
||||||
u.c_str(), -1, nullptr, 0,
|
|
||||||
nullptr, nullptr, 0);
|
|
||||||
if (size <= 0)
|
|
||||||
return AllocatedString<>::Duplicate(src);
|
|
||||||
|
|
||||||
std::unique_ptr<wchar_t[]> buffer(new wchar_t[size]);
|
|
||||||
if (LCMapStringEx(LOCALE_NAME_INVARIANT,
|
|
||||||
LCMAP_SORTKEY|LINGUISTIC_IGNORECASE,
|
|
||||||
u.c_str(), -1, buffer.get(), size,
|
|
||||||
nullptr, nullptr, 0) <= 0)
|
|
||||||
return AllocatedString<>::Duplicate(src);
|
|
||||||
|
|
||||||
return WideCharToMultiByte(CP_UTF8, buffer.get());
|
|
||||||
|
|
||||||
#else
|
#else
|
||||||
#error not implemented
|
#error not implemented
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#if defined(HAVE_ICU) || defined(_WIN32)
|
#ifdef HAVE_ICU
|
||||||
#define HAVE_ICU_CASE_FOLD
|
#define HAVE_ICU_CASE_FOLD
|
||||||
|
|
||||||
#include "util/Compiler.h"
|
#include "util/Compiler.h"
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ IcuCollate(const char *a, const char *b) noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto result = CompareStringEx(LOCALE_NAME_INVARIANT,
|
auto result = CompareStringEx(LOCALE_NAME_INVARIANT,
|
||||||
LINGUISTIC_IGNORECASE,
|
NORM_IGNORECASE,
|
||||||
wa.c_str(), -1,
|
wa.c_str(), -1,
|
||||||
wb.c_str(), -1,
|
wb.c_str(), -1,
|
||||||
nullptr, nullptr, 0);
|
nullptr, nullptr, 0);
|
||||||
|
|||||||
@@ -22,6 +22,11 @@
|
|||||||
#include "util/StringAPI.hxx"
|
#include "util/StringAPI.hxx"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include "Win32.hxx"
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#ifdef HAVE_ICU_CASE_FOLD
|
#ifdef HAVE_ICU_CASE_FOLD
|
||||||
@@ -29,6 +34,17 @@
|
|||||||
IcuCompare::IcuCompare(const char *_needle) noexcept
|
IcuCompare::IcuCompare(const char *_needle) noexcept
|
||||||
:needle(IcuCaseFold(_needle)) {}
|
:needle(IcuCaseFold(_needle)) {}
|
||||||
|
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
|
||||||
|
IcuCompare::IcuCompare(const char *_needle) noexcept
|
||||||
|
:needle(nullptr)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
needle = MultiByteToWideChar(CP_UTF8, _needle);
|
||||||
|
} catch (...) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
IcuCompare::IcuCompare(const char *_needle) noexcept
|
IcuCompare::IcuCompare(const char *_needle) noexcept
|
||||||
@@ -41,6 +57,22 @@ IcuCompare::operator==(const char *haystack) const noexcept
|
|||||||
{
|
{
|
||||||
#ifdef HAVE_ICU_CASE_FOLD
|
#ifdef HAVE_ICU_CASE_FOLD
|
||||||
return StringIsEqual(IcuCaseFold(haystack).c_str(), needle.c_str());
|
return StringIsEqual(IcuCaseFold(haystack).c_str(), needle.c_str());
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
if (needle.IsNull())
|
||||||
|
/* the MultiByteToWideChar() call in the constructor
|
||||||
|
has failed, so let's always fail the comparison */
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto w_haystack = MultiByteToWideChar(CP_UTF8, haystack);
|
||||||
|
return CompareStringEx(LOCALE_NAME_INVARIANT,
|
||||||
|
NORM_IGNORECASE,
|
||||||
|
w_haystack.c_str(), -1,
|
||||||
|
needle.c_str(), -1,
|
||||||
|
nullptr, nullptr, 0) == CSTR_EQUAL;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
return strcasecmp(haystack, needle.c_str());
|
return strcasecmp(haystack, needle.c_str());
|
||||||
#endif
|
#endif
|
||||||
@@ -52,6 +84,24 @@ IcuCompare::IsIn(const char *haystack) const noexcept
|
|||||||
#ifdef HAVE_ICU_CASE_FOLD
|
#ifdef HAVE_ICU_CASE_FOLD
|
||||||
return StringFind(IcuCaseFold(haystack).c_str(),
|
return StringFind(IcuCaseFold(haystack).c_str(),
|
||||||
needle.c_str()) != nullptr;
|
needle.c_str()) != nullptr;
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
if (needle.IsNull())
|
||||||
|
/* the MultiByteToWideChar() call in the constructor
|
||||||
|
has failed, so let's always fail the comparison */
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto w_haystack = MultiByteToWideChar(CP_UTF8, haystack);
|
||||||
|
return FindNLSStringEx(LOCALE_NAME_INVARIANT,
|
||||||
|
FIND_FROMSTART|NORM_IGNORECASE,
|
||||||
|
w_haystack.c_str(), -1,
|
||||||
|
needle.c_str(), -1,
|
||||||
|
nullptr,
|
||||||
|
nullptr, nullptr, 0) >= 0;
|
||||||
|
} catch (...) {
|
||||||
|
/* MultiByteToWideChar() has failed */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
#elif defined(HAVE_STRCASESTR)
|
#elif defined(HAVE_STRCASESTR)
|
||||||
return strcasestr(haystack, needle.c_str()) != nullptr;
|
return strcasestr(haystack, needle.c_str()) != nullptr;
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -23,13 +23,23 @@
|
|||||||
#include "util/Compiler.h"
|
#include "util/Compiler.h"
|
||||||
#include "util/AllocatedString.hxx"
|
#include "util/AllocatedString.hxx"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <wchar.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class can compare one string ("needle") with lots of other
|
* This class can compare one string ("needle") with lots of other
|
||||||
* strings ("haystacks") efficiently, ignoring case. With some
|
* strings ("haystacks") efficiently, ignoring case. With some
|
||||||
* configurations, it can prepare a case-folded version of the needle.
|
* configurations, it can prepare a case-folded version of the needle.
|
||||||
*/
|
*/
|
||||||
class IcuCompare {
|
class IcuCompare {
|
||||||
|
#ifdef _WIN32
|
||||||
|
/* Windows API functions work with wchar_t strings, so let's
|
||||||
|
cache the MultiByteToWideChar() result for performance */
|
||||||
|
AllocatedString<wchar_t> needle;
|
||||||
|
#else
|
||||||
AllocatedString<> needle;
|
AllocatedString<> needle;
|
||||||
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
IcuCompare():needle(nullptr) {}
|
IcuCompare():needle(nullptr) {}
|
||||||
@@ -38,12 +48,12 @@ public:
|
|||||||
|
|
||||||
IcuCompare(const IcuCompare &src) noexcept
|
IcuCompare(const IcuCompare &src) noexcept
|
||||||
:needle(src
|
:needle(src
|
||||||
? AllocatedString<>::Duplicate(src.needle.c_str())
|
? src.needle.Clone()
|
||||||
: nullptr) {}
|
: nullptr) {}
|
||||||
|
|
||||||
IcuCompare &operator=(const IcuCompare &src) noexcept {
|
IcuCompare &operator=(const IcuCompare &src) noexcept {
|
||||||
needle = src
|
needle = src
|
||||||
? AllocatedString<>::Duplicate(src.needle.c_str())
|
? src.needle.Clone()
|
||||||
: nullptr;
|
: nullptr;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,7 +191,9 @@ static constexpr int
|
|||||||
events_to_libnfs(unsigned i) noexcept
|
events_to_libnfs(unsigned i) noexcept
|
||||||
{
|
{
|
||||||
return ((i & SocketMonitor::READ) ? POLLIN : 0) |
|
return ((i & SocketMonitor::READ) ? POLLIN : 0) |
|
||||||
((i & SocketMonitor::WRITE) ? POLLOUT : 0);
|
((i & SocketMonitor::WRITE) ? POLLOUT : 0) |
|
||||||
|
((i & SocketMonitor::HANGUP) ? POLLHUP : 0) |
|
||||||
|
((i & SocketMonitor::ERROR) ? POLLERR : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
NfsConnection::~NfsConnection() noexcept
|
NfsConnection::~NfsConnection() noexcept
|
||||||
@@ -450,8 +452,7 @@ NfsConnection::ScheduleSocket() noexcept
|
|||||||
SocketMonitor::Open(_fd);
|
SocketMonitor::Open(_fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
SocketMonitor::Schedule(libnfs_to_events(which_events)
|
SocketMonitor::Schedule(libnfs_to_events(which_events));
|
||||||
| SocketMonitor::HANGUP);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int
|
inline int
|
||||||
|
|||||||
@@ -180,7 +180,6 @@ NfsFileReader::OnNfsConnectionDisconnected(std::exception_ptr e) noexcept
|
|||||||
inline void
|
inline void
|
||||||
NfsFileReader::OpenCallback(nfsfh *_fh) noexcept
|
NfsFileReader::OpenCallback(nfsfh *_fh) noexcept
|
||||||
{
|
{
|
||||||
assert(state == State::OPEN);
|
|
||||||
assert(connection != nullptr);
|
assert(connection != nullptr);
|
||||||
assert(_fh != nullptr);
|
assert(_fh != nullptr);
|
||||||
|
|
||||||
@@ -197,27 +196,33 @@ NfsFileReader::OpenCallback(nfsfh *_fh) noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
NfsFileReader::StatCallback(const struct stat *st) noexcept
|
NfsFileReader::StatCallback(const struct stat *_st) noexcept
|
||||||
{
|
{
|
||||||
assert(state == State::STAT);
|
|
||||||
assert(connection != nullptr);
|
assert(connection != nullptr);
|
||||||
assert(fh != nullptr);
|
assert(fh != nullptr);
|
||||||
assert(st != nullptr);
|
assert(_st != nullptr);
|
||||||
|
|
||||||
|
#if defined(_WIN32) && !defined(_WIN64)
|
||||||
|
/* on 32-bit Windows, libnfs enables -D_FILE_OFFSET_BITS=64,
|
||||||
|
but MPD (Meson) doesn't - to work around this mismatch, we
|
||||||
|
cast explicitly to "struct stat64" */
|
||||||
|
const auto *st = (const struct stat64 *)_st;
|
||||||
|
#else
|
||||||
|
const auto *st = _st;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!S_ISREG(st->st_mode)) {
|
if (!S_ISREG(st->st_mode)) {
|
||||||
OnNfsFileError(std::make_exception_ptr(std::runtime_error("Not a regular file")));
|
OnNfsFileError(std::make_exception_ptr(std::runtime_error("Not a regular file")));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state = State::IDLE;
|
|
||||||
|
|
||||||
OnNfsFileOpen(st->st_size);
|
OnNfsFileOpen(st->st_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
NfsFileReader::OnNfsCallback(unsigned status, void *data) noexcept
|
NfsFileReader::OnNfsCallback(unsigned status, void *data) noexcept
|
||||||
{
|
{
|
||||||
switch (state) {
|
switch (std::exchange(state, State::IDLE)) {
|
||||||
case State::INITIAL:
|
case State::INITIAL:
|
||||||
case State::DEFER:
|
case State::DEFER:
|
||||||
case State::MOUNT:
|
case State::MOUNT:
|
||||||
@@ -234,7 +239,6 @@ NfsFileReader::OnNfsCallback(unsigned status, void *data) noexcept
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case State::READ:
|
case State::READ:
|
||||||
state = State::IDLE;
|
|
||||||
OnNfsFileRead(data, status);
|
OnNfsFileRead(data, status);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,28 @@
|
|||||||
#include "PcmBuffer.hxx"
|
#include "PcmBuffer.hxx"
|
||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* According to:
|
||||||
|
* - https://xiph.org/flac/format.html#frame_header
|
||||||
|
* - https://github.com/nu774/qaac/wiki/Multichannel--handling
|
||||||
|
* the source channel order (after decoding, e.g., flac, alac) is for
|
||||||
|
* - 1ch: mono
|
||||||
|
* - 2ch: left, right
|
||||||
|
* - 3ch: left, right, center
|
||||||
|
* - 4ch: front left, front right, back left, back right
|
||||||
|
* - 5ch: front left, front right, front center, back/surround left, back/surround right
|
||||||
|
* - 6ch (aka 5.1): front left, front right, front center, LFE, back/surround left, back/surround right
|
||||||
|
* - 7ch: front left, front right, front center, LFE, back center, side left, side right
|
||||||
|
* - 8ch: (aka 7.1): front left, front right, front center, LFE, back left, back right, side left, side right
|
||||||
|
*
|
||||||
|
* The ALSA default channel map is (see /usr/share/alsa/pcm/surround71.conf):
|
||||||
|
* - front left, front right, back left, back right, front center, LFE, side left, side right
|
||||||
|
*
|
||||||
|
* Hence, in case of the following source channel orders 3ch, 5ch, 6ch (aka
|
||||||
|
* 5.1), 7ch and 8ch the channel order has to be adapted
|
||||||
|
*/
|
||||||
|
|
||||||
template<typename V>
|
template<typename V>
|
||||||
struct TwoPointers {
|
struct TwoPointers {
|
||||||
V *dest;
|
V *dest;
|
||||||
@@ -44,17 +66,57 @@ struct TwoPointers {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TwoPointers<V> &ToAlsa50() noexcept {
|
||||||
|
*dest++ = src[0]; // front left
|
||||||
|
*dest++ = src[1]; // front right
|
||||||
|
*dest++ = src[3]; // surround left
|
||||||
|
*dest++ = src[4]; // surround right
|
||||||
|
*dest++ = src[2]; // front center
|
||||||
|
src += 5;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
TwoPointers<V> &ToAlsa51() noexcept {
|
TwoPointers<V> &ToAlsa51() noexcept {
|
||||||
return CopyTwo() // left+right
|
return CopyTwo() // left+right
|
||||||
.SwapTwoPairs(); // center, LFE, surround left+right
|
.SwapTwoPairs(); // center, LFE, surround left+right
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TwoPointers<V> &ToAlsa70() noexcept {
|
||||||
|
*dest++ = src[0]; // front left
|
||||||
|
*dest++ = src[1]; // front right
|
||||||
|
*dest++ = src[5]; // side left
|
||||||
|
*dest++ = src[6]; // side right
|
||||||
|
*dest++ = src[2]; // front center
|
||||||
|
*dest++ = src[3]; // LFE
|
||||||
|
*dest++ = src[4]; // back center
|
||||||
|
src += 7;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
TwoPointers<V> &ToAlsa71() noexcept {
|
TwoPointers<V> &ToAlsa71() noexcept {
|
||||||
return ToAlsa51()
|
return ToAlsa51()
|
||||||
.CopyTwo(); // side left+right
|
.CopyTwo(); // side left+right
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename V>
|
||||||
|
static void
|
||||||
|
ToAlsaChannelOrder50(V *dest, const V *src, size_t n) noexcept
|
||||||
|
{
|
||||||
|
TwoPointers<V> p{dest, src};
|
||||||
|
for (size_t i = 0; i != n; ++i)
|
||||||
|
p.ToAlsa50();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename V>
|
||||||
|
static inline ConstBuffer<V>
|
||||||
|
ToAlsaChannelOrder50(PcmBuffer &buffer, ConstBuffer<V> src) noexcept
|
||||||
|
{
|
||||||
|
auto dest = buffer.GetT<V>(src.size);
|
||||||
|
ToAlsaChannelOrder50(dest, src.data, src.size / 5);
|
||||||
|
return { dest, src.size };
|
||||||
|
}
|
||||||
|
|
||||||
template<typename V>
|
template<typename V>
|
||||||
static void
|
static void
|
||||||
ToAlsaChannelOrder51(V *dest, const V *src, size_t n) noexcept
|
ToAlsaChannelOrder51(V *dest, const V *src, size_t n) noexcept
|
||||||
@@ -73,6 +135,24 @@ ToAlsaChannelOrder51(PcmBuffer &buffer, ConstBuffer<V> src) noexcept
|
|||||||
return { dest, src.size };
|
return { dest, src.size };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename V>
|
||||||
|
static void
|
||||||
|
ToAlsaChannelOrder70(V *dest, const V *src, size_t n) noexcept
|
||||||
|
{
|
||||||
|
TwoPointers<V> p{dest, src};
|
||||||
|
for (size_t i = 0; i != n; ++i)
|
||||||
|
p.ToAlsa70();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename V>
|
||||||
|
static inline ConstBuffer<V>
|
||||||
|
ToAlsaChannelOrder70(PcmBuffer &buffer, ConstBuffer<V> src) noexcept
|
||||||
|
{
|
||||||
|
auto dest = buffer.GetT<V>(src.size);
|
||||||
|
ToAlsaChannelOrder70(dest, src.data, src.size / 7);
|
||||||
|
return { dest, src.size };
|
||||||
|
}
|
||||||
|
|
||||||
template<typename V>
|
template<typename V>
|
||||||
static void
|
static void
|
||||||
ToAlsaChannelOrder71(V *dest, const V *src, size_t n) noexcept
|
ToAlsaChannelOrder71(V *dest, const V *src, size_t n) noexcept
|
||||||
@@ -97,9 +177,15 @@ ToAlsaChannelOrderT(PcmBuffer &buffer, ConstBuffer<V> src,
|
|||||||
unsigned channels) noexcept
|
unsigned channels) noexcept
|
||||||
{
|
{
|
||||||
switch (channels) {
|
switch (channels) {
|
||||||
|
case 5: // 5.0
|
||||||
|
return ToAlsaChannelOrder50(buffer, src);
|
||||||
|
|
||||||
case 6: // 5.1
|
case 6: // 5.1
|
||||||
return ToAlsaChannelOrder51(buffer, src);
|
return ToAlsaChannelOrder51(buffer, src);
|
||||||
|
|
||||||
|
case 7: // 7.0
|
||||||
|
return ToAlsaChannelOrder70(buffer, src);
|
||||||
|
|
||||||
case 8: // 7.1
|
case 8: // 7.1
|
||||||
return ToAlsaChannelOrder71(buffer, src);
|
return ToAlsaChannelOrder71(buffer, src);
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ mixramp_interpolate(const char *ramp_list, float required_db) noexcept
|
|||||||
++ramp_list;
|
++ramp_list;
|
||||||
|
|
||||||
/* Check for exact match. */
|
/* Check for exact match. */
|
||||||
if (db == required_db) {
|
if (db >= required_db) {
|
||||||
return duration;
|
return duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -964,6 +964,12 @@ Player::SongBorder() noexcept
|
|||||||
if (border_pause) {
|
if (border_pause) {
|
||||||
paused = true;
|
paused = true;
|
||||||
pc.listener.OnBorderPause();
|
pc.listener.OnBorderPause();
|
||||||
|
|
||||||
|
/* drain all outputs to guarantee the current song is
|
||||||
|
really being played to the end; without this, the
|
||||||
|
Pause() call would drop all ring buffers */
|
||||||
|
pc.outputs.Drain();
|
||||||
|
|
||||||
pc.outputs.Pause();
|
pc.outputs.Pause();
|
||||||
idle_add(IDLE_PLAYER);
|
idle_add(IDLE_PLAYER);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -402,7 +402,7 @@ private:
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case State::HREF:
|
case State::HREF:
|
||||||
response.href.assign(s, len);
|
response.href.append(s, len);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case State::STATUS:
|
case State::STATUS:
|
||||||
@@ -482,7 +482,7 @@ class HttpListDirectoryOperation final : public PropfindOperation {
|
|||||||
public:
|
public:
|
||||||
HttpListDirectoryOperation(CurlGlobal &curl, const char *uri)
|
HttpListDirectoryOperation(CurlGlobal &curl, const char *uri)
|
||||||
:PropfindOperation(curl, uri, 1),
|
:PropfindOperation(curl, uri, 1),
|
||||||
base_path(UriPathOrSlash(uri)) {}
|
base_path(CurlUnescape(GetEasy(), UriPathOrSlash(uri))) {}
|
||||||
|
|
||||||
std::unique_ptr<StorageDirectoryReader> Perform() {
|
std::unique_ptr<StorageDirectoryReader> Perform() {
|
||||||
DeferStart();
|
DeferStart();
|
||||||
@@ -507,8 +507,7 @@ private:
|
|||||||
|
|
||||||
/* kludge: ignoring case in this comparison to avoid
|
/* kludge: ignoring case in this comparison to avoid
|
||||||
false negatives if the web server uses a different
|
false negatives if the web server uses a different
|
||||||
case in hex digits in escaped characters; TODO:
|
case */
|
||||||
implement properly */
|
|
||||||
path = StringAfterPrefixIgnoreCase(path, base_path.c_str());
|
path = StringAfterPrefixIgnoreCase(path, base_path.c_str());
|
||||||
if (path == nullptr || *path == 0)
|
if (path == nullptr || *path == 0)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -531,11 +530,12 @@ protected:
|
|||||||
if (r.status != 200)
|
if (r.status != 200)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const auto escaped_name = HrefToEscapedName(r.href.c_str());
|
std::string href = CurlUnescape(GetEasy(), r.href.c_str());
|
||||||
if (escaped_name.IsNull())
|
const auto name = HrefToEscapedName(href.c_str());
|
||||||
|
if (name.IsNull())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
entries.emplace_front(CurlUnescape(GetEasy(), escaped_name));
|
entries.emplace_front(std::string(name.data, name.size));
|
||||||
|
|
||||||
auto &info = entries.front().info;
|
auto &info = entries.front().info;
|
||||||
info = StorageFileInfo(r.collection
|
info = StorageFileInfo(r.collection
|
||||||
|
|||||||
@@ -45,6 +45,10 @@ ApplyTagFallback(TagType type, F &&f) noexcept
|
|||||||
"AlbumArtist"/"ArtistSort" was found */
|
"AlbumArtist"/"ArtistSort" was found */
|
||||||
return f(TAG_ARTIST);
|
return f(TAG_ARTIST);
|
||||||
|
|
||||||
|
if (type == TAG_ALBUM_SORT)
|
||||||
|
/* fall back to "Album" if no "AlbumSort" was found */
|
||||||
|
return f(TAG_ALBUM);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,11 +30,16 @@
|
|||||||
#ifndef MATH_HXX
|
#ifndef MATH_HXX
|
||||||
#define MATH_HXX
|
#define MATH_HXX
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* C99 math can be optionally omitted with gcc's libstdc++.
|
||||||
|
* Use boost if unavailable.
|
||||||
|
*/
|
||||||
#if (defined(__GLIBCPP__) || defined(__GLIBCXX__)) && !defined(_GLIBCXX_USE_C99_MATH)
|
#if (defined(__GLIBCPP__) || defined(__GLIBCXX__)) && !defined(_GLIBCXX_USE_C99_MATH)
|
||||||
#include <boost/math/special_functions/round.hpp>
|
#include <boost/math/special_functions/round.hpp>
|
||||||
using boost::math::lround;
|
using boost::math::lround;
|
||||||
#else
|
#else
|
||||||
#include <cmath>
|
|
||||||
using std::lround;
|
using std::lround;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -26,9 +26,7 @@ static unsigned
|
|||||||
FromAvahiWatchEvent(AvahiWatchEvent e)
|
FromAvahiWatchEvent(AvahiWatchEvent e)
|
||||||
{
|
{
|
||||||
return (e & AVAHI_WATCH_IN ? SocketMonitor::READ : 0) |
|
return (e & AVAHI_WATCH_IN ? SocketMonitor::READ : 0) |
|
||||||
(e & AVAHI_WATCH_OUT ? SocketMonitor::WRITE : 0) |
|
(e & AVAHI_WATCH_OUT ? SocketMonitor::WRITE : 0);
|
||||||
(e & AVAHI_WATCH_ERR ? SocketMonitor::ERROR : 0) |
|
|
||||||
(e & AVAHI_WATCH_HUP ? SocketMonitor::HANGUP : 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static AvahiWatchEvent
|
static AvahiWatchEvent
|
||||||
|
|||||||
Reference in New Issue
Block a user