input/plugins/AlsaInputPlugin: extend the alsa uri parsing to permit specification of the desired pcm audio format in the uri
This commit is contained in:
parent
945ea51bd4
commit
c834eb4590
@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
#include "AlsaInputPlugin.hxx"
|
#include "AlsaInputPlugin.hxx"
|
||||||
#include "lib/alsa/NonBlock.hxx"
|
#include "lib/alsa/NonBlock.hxx"
|
||||||
|
#include "lib/alsa/Format.hxx"
|
||||||
#include "../InputPlugin.hxx"
|
#include "../InputPlugin.hxx"
|
||||||
#include "../AsyncInputStream.hxx"
|
#include "../AsyncInputStream.hxx"
|
||||||
#include "event/Call.hxx"
|
#include "event/Call.hxx"
|
||||||
@ -34,6 +35,9 @@
|
|||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/StringCompare.hxx"
|
||||||
#include "util/ASCII.hxx"
|
#include "util/ASCII.hxx"
|
||||||
|
#include "util/DivideString.hxx"
|
||||||
|
#include "AudioParser.hxx"
|
||||||
|
#include "AudioFormat.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
#include "event/MultiSocketMonitor.hxx"
|
#include "event/MultiSocketMonitor.hxx"
|
||||||
#include "event/DeferEvent.hxx"
|
#include "event/DeferEvent.hxx"
|
||||||
@ -45,15 +49,13 @@
|
|||||||
|
|
||||||
static constexpr Domain alsa_input_domain("alsa");
|
static constexpr Domain alsa_input_domain("alsa");
|
||||||
|
|
||||||
static constexpr const char *default_device = "hw:0,0";
|
static constexpr auto ALSA_URI_PREFIX = "alsa://";
|
||||||
|
|
||||||
// the following defaults are because the PcmDecoderPlugin forces CD format
|
static constexpr auto BUILTIN_DEFAULT_DEVICE = "hw:0,0";
|
||||||
static constexpr snd_pcm_format_t default_format = SND_PCM_FORMAT_S16;
|
static constexpr auto BUILTIN_DEFAULT_FORMAT = "44100:16:2";
|
||||||
static constexpr int default_channels = 2; // stereo
|
|
||||||
static constexpr unsigned int default_rate = 44100; // cd quality
|
|
||||||
|
|
||||||
static constexpr size_t ALSA_MAX_BUFFERED = default_rate * default_channels * 2;
|
static constexpr auto DEFAULT_BUFFER_TIME = std::chrono::milliseconds(1000);
|
||||||
static constexpr size_t ALSA_RESUME_AT = ALSA_MAX_BUFFERED / 2;
|
static constexpr auto DEFAULT_RESUME_TIME = DEFAULT_BUFFER_TIME / 2;
|
||||||
|
|
||||||
class AlsaInputStream final
|
class AlsaInputStream final
|
||||||
: public AsyncInputStream,
|
: public AsyncInputStream,
|
||||||
@ -64,7 +66,7 @@ class AlsaInputStream final
|
|||||||
*/
|
*/
|
||||||
const std::string device;
|
const std::string device;
|
||||||
|
|
||||||
snd_pcm_t *const capture_handle;
|
snd_pcm_t *capture_handle;
|
||||||
const size_t frame_size;
|
const size_t frame_size;
|
||||||
|
|
||||||
AlsaNonBlockPcm non_block;
|
AlsaNonBlockPcm non_block;
|
||||||
@ -72,32 +74,12 @@ class AlsaInputStream final
|
|||||||
DeferEvent defer_invalidate_sockets;
|
DeferEvent defer_invalidate_sockets;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
class SourceSpec;
|
||||||
|
|
||||||
AlsaInputStream(EventLoop &_loop,
|
AlsaInputStream(EventLoop &_loop,
|
||||||
const char *_uri, Mutex &_mutex,
|
Mutex &_mutex,
|
||||||
const char *_device,
|
const SourceSpec &spec);
|
||||||
snd_pcm_t *_handle, int _frame_size)
|
|
||||||
:AsyncInputStream(_loop, _uri, _mutex,
|
|
||||||
ALSA_MAX_BUFFERED, ALSA_RESUME_AT),
|
|
||||||
MultiSocketMonitor(_loop),
|
|
||||||
device(_device),
|
|
||||||
capture_handle(_handle),
|
|
||||||
frame_size(_frame_size),
|
|
||||||
defer_invalidate_sockets(_loop,
|
|
||||||
BIND_THIS_METHOD(InvalidateSockets))
|
|
||||||
{
|
|
||||||
assert(_uri != nullptr);
|
|
||||||
assert(_handle != nullptr);
|
|
||||||
|
|
||||||
/* this mime type forces use of the PcmDecoderPlugin.
|
|
||||||
Needs to be generalised when/if that decoder is
|
|
||||||
updated to support other audio formats */
|
|
||||||
SetMimeType("audio/x-mpd-cdda-pcm");
|
|
||||||
InputStream::SetReady();
|
|
||||||
|
|
||||||
snd_pcm_start(capture_handle);
|
|
||||||
|
|
||||||
defer_invalidate_sockets.Schedule();
|
|
||||||
}
|
|
||||||
|
|
||||||
~AlsaInputStream() {
|
~AlsaInputStream() {
|
||||||
BlockingCall(MultiSocketMonitor::GetEventLoop(), [this](){
|
BlockingCall(MultiSocketMonitor::GetEventLoop(), [this](){
|
||||||
@ -125,8 +107,8 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static snd_pcm_t *OpenDevice(const char *device, int rate,
|
void OpenDevice(const SourceSpec &spec);
|
||||||
snd_pcm_format_t format, int channels);
|
void ConfigureCapture(AudioFormat audio_format);
|
||||||
|
|
||||||
void Pause() {
|
void Pause() {
|
||||||
AsyncInputStream::Pause();
|
AsyncInputStream::Pause();
|
||||||
@ -135,39 +117,97 @@ private:
|
|||||||
|
|
||||||
int Recover(int err);
|
int Recover(int err);
|
||||||
|
|
||||||
void SafeInvalidateSockets() {
|
|
||||||
defer_invalidate_sockets.Schedule();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* virtual methods from class MultiSocketMonitor */
|
/* virtual methods from class MultiSocketMonitor */
|
||||||
std::chrono::steady_clock::duration PrepareSockets() noexcept override;
|
std::chrono::steady_clock::duration PrepareSockets() noexcept override;
|
||||||
void DispatchSockets() noexcept override;
|
void DispatchSockets() noexcept override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class AlsaInputStream::SourceSpec {
|
||||||
|
const char *uri;
|
||||||
|
const char *device_name;
|
||||||
|
const char *format_string;
|
||||||
|
AudioFormat audio_format;
|
||||||
|
DivideString components;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SourceSpec(const char *_uri)
|
||||||
|
: uri(_uri)
|
||||||
|
, components(uri, '?')
|
||||||
|
{
|
||||||
|
if (components.IsDefined()) {
|
||||||
|
device_name = StringAfterPrefixCaseASCII(components.GetFirst(),
|
||||||
|
ALSA_URI_PREFIX);
|
||||||
|
format_string = StringAfterPrefixCaseASCII(components.GetSecond(),
|
||||||
|
"format=");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
device_name = StringAfterPrefixCaseASCII(uri, ALSA_URI_PREFIX);
|
||||||
|
format_string = BUILTIN_DEFAULT_FORMAT;
|
||||||
|
}
|
||||||
|
if (IsValidScheme()) {
|
||||||
|
if (*device_name == 0)
|
||||||
|
device_name = BUILTIN_DEFAULT_DEVICE;
|
||||||
|
if (format_string != nullptr)
|
||||||
|
audio_format = ParseAudioFormat(format_string, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool IsValidScheme() const noexcept {
|
||||||
|
return device_name != nullptr;
|
||||||
|
}
|
||||||
|
bool IsValid() const noexcept {
|
||||||
|
return (device_name != nullptr) && (format_string != nullptr);
|
||||||
|
}
|
||||||
|
const char *GetURI() const noexcept {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
const char *GetDeviceName() const noexcept {
|
||||||
|
return device_name;
|
||||||
|
}
|
||||||
|
const char *GetFormatString() const noexcept {
|
||||||
|
return format_string;
|
||||||
|
}
|
||||||
|
AudioFormat GetAudioFormat() const noexcept {
|
||||||
|
return audio_format;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AlsaInputStream::AlsaInputStream(EventLoop &_loop,
|
||||||
|
Mutex &_mutex,
|
||||||
|
const SourceSpec &spec)
|
||||||
|
:AsyncInputStream(_loop, spec.GetURI(), _mutex,
|
||||||
|
spec.GetAudioFormat().TimeToSize(DEFAULT_BUFFER_TIME),
|
||||||
|
spec.GetAudioFormat().TimeToSize(DEFAULT_RESUME_TIME)),
|
||||||
|
MultiSocketMonitor(_loop),
|
||||||
|
device(spec.GetDeviceName()),
|
||||||
|
frame_size(spec.GetAudioFormat().GetFrameSize()),
|
||||||
|
defer_invalidate_sockets(_loop,
|
||||||
|
BIND_THIS_METHOD(InvalidateSockets))
|
||||||
|
{
|
||||||
|
OpenDevice(spec);
|
||||||
|
|
||||||
|
std::string mimestr = "audio/x-mpd-alsa-pcm;format=";
|
||||||
|
mimestr += spec.GetFormatString();
|
||||||
|
SetMimeType(mimestr.c_str());
|
||||||
|
|
||||||
|
InputStream::SetReady();
|
||||||
|
|
||||||
|
snd_pcm_start(capture_handle);
|
||||||
|
|
||||||
|
defer_invalidate_sockets.Schedule();
|
||||||
|
}
|
||||||
|
|
||||||
inline InputStreamPtr
|
inline InputStreamPtr
|
||||||
AlsaInputStream::Create(EventLoop &event_loop, const char *uri,
|
AlsaInputStream::Create(EventLoop &event_loop, const char *uri,
|
||||||
Mutex &mutex)
|
Mutex &mutex)
|
||||||
{
|
{
|
||||||
const char *device = StringAfterPrefixCaseASCII(uri, "alsa://");
|
assert(uri != nullptr);
|
||||||
if (device == nullptr)
|
|
||||||
|
AlsaInputStream::SourceSpec spec(uri);
|
||||||
|
if (!spec.IsValidScheme())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
if (*device == 0)
|
return std::make_unique<AlsaInputStream>(event_loop, mutex, spec);
|
||||||
device = default_device;
|
|
||||||
|
|
||||||
/* placeholders - eventually user-requested audio format will
|
|
||||||
be passed via the URI. For now we just force the
|
|
||||||
defaults */
|
|
||||||
int rate = default_rate;
|
|
||||||
snd_pcm_format_t format = default_format;
|
|
||||||
int channels = default_channels;
|
|
||||||
|
|
||||||
snd_pcm_t *handle = OpenDevice(device, rate, format, channels);
|
|
||||||
|
|
||||||
int frame_size = snd_pcm_format_width(format) / 8 * channels;
|
|
||||||
return std::make_unique<AlsaInputStream>(event_loop,
|
|
||||||
uri, mutex,
|
|
||||||
device, handle, frame_size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::chrono::steady_clock::duration
|
std::chrono::steady_clock::duration
|
||||||
@ -268,13 +308,11 @@ AlsaInputStream::Recover(int err)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
void
|
||||||
ConfigureCapture(snd_pcm_t *capture_handle,
|
AlsaInputStream::ConfigureCapture(AudioFormat audio_format)
|
||||||
int rate, snd_pcm_format_t format, int channels)
|
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
@ -285,19 +323,23 @@ ConfigureCapture(snd_pcm_t *capture_handle,
|
|||||||
throw FormatRuntimeError("Cannot initialize hardware parameter structure (%s)",
|
throw FormatRuntimeError("Cannot initialize hardware parameter structure (%s)",
|
||||||
snd_strerror(err));
|
snd_strerror(err));
|
||||||
|
|
||||||
if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
|
if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params,
|
||||||
|
SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
|
||||||
throw FormatRuntimeError("Cannot set access type (%s)",
|
throw FormatRuntimeError("Cannot set access type (%s)",
|
||||||
snd_strerror(err));
|
snd_strerror(err));
|
||||||
|
|
||||||
if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, format)) < 0)
|
if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params,
|
||||||
|
ToAlsaPcmFormat(audio_format.format))) < 0)
|
||||||
throw FormatRuntimeError("Cannot set sample format (%s)",
|
throw FormatRuntimeError("Cannot set sample format (%s)",
|
||||||
snd_strerror(err));
|
snd_strerror(err));
|
||||||
|
|
||||||
if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, channels)) < 0)
|
if ((err = snd_pcm_hw_params_set_channels(capture_handle,
|
||||||
|
hw_params, audio_format.channels)) < 0)
|
||||||
throw FormatRuntimeError("Cannot set channels (%s)",
|
throw FormatRuntimeError("Cannot set channels (%s)",
|
||||||
snd_strerror(err));
|
snd_strerror(err));
|
||||||
|
|
||||||
if ((err = snd_pcm_hw_params_set_rate(capture_handle, hw_params, rate, 0)) < 0)
|
if ((err = snd_pcm_hw_params_set_rate(capture_handle,
|
||||||
|
hw_params, audio_format.sample_rate, 0)) < 0)
|
||||||
throw FormatRuntimeError("Cannot set sample rate (%s)",
|
throw FormatRuntimeError("Cannot set sample rate (%s)",
|
||||||
snd_strerror(err));
|
snd_strerror(err));
|
||||||
|
|
||||||
@ -332,8 +374,8 @@ ConfigureCapture(snd_pcm_t *capture_handle,
|
|||||||
if (snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size) == 0) {
|
if (snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size) == 0) {
|
||||||
snd_pcm_uframes_t period_size = buffer_size / 4;
|
snd_pcm_uframes_t period_size = buffer_size / 4;
|
||||||
int direction = -1;
|
int direction = -1;
|
||||||
if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params,
|
if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle,
|
||||||
&period_size, &direction)) < 0)
|
hw_params, &period_size, &direction)) < 0)
|
||||||
throw FormatRuntimeError("Cannot set period size (%s)",
|
throw FormatRuntimeError("Cannot set period size (%s)",
|
||||||
snd_strerror(err));
|
snd_strerror(err));
|
||||||
}
|
}
|
||||||
@ -368,28 +410,25 @@ ConfigureCapture(snd_pcm_t *capture_handle,
|
|||||||
snd_strerror(err));
|
snd_strerror(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline snd_pcm_t *
|
inline void
|
||||||
AlsaInputStream::OpenDevice(const char *device,
|
AlsaInputStream::OpenDevice(const SourceSpec &spec)
|
||||||
int rate, snd_pcm_format_t format, int channels)
|
|
||||||
{
|
{
|
||||||
snd_pcm_t *capture_handle;
|
|
||||||
int err;
|
int err;
|
||||||
if ((err = snd_pcm_open(&capture_handle, device,
|
|
||||||
|
if ((err = snd_pcm_open(&capture_handle, spec.GetDeviceName(),
|
||||||
SND_PCM_STREAM_CAPTURE,
|
SND_PCM_STREAM_CAPTURE,
|
||||||
SND_PCM_NONBLOCK)) < 0)
|
SND_PCM_NONBLOCK)) < 0)
|
||||||
throw FormatRuntimeError("Failed to open device: %s (%s)",
|
throw FormatRuntimeError("Failed to open device: %s (%s)",
|
||||||
device, snd_strerror(err));
|
spec.GetDeviceName(), snd_strerror(err));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ConfigureCapture(capture_handle, rate, format, channels);
|
ConfigureCapture(spec.GetAudioFormat());
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
snd_pcm_close(capture_handle);
|
snd_pcm_close(capture_handle);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
snd_pcm_prepare(capture_handle);
|
snd_pcm_prepare(capture_handle);
|
||||||
|
|
||||||
return capture_handle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*######################### Plugin Functions ##############################*/
|
/*######################### Plugin Functions ##############################*/
|
||||||
@ -410,7 +449,7 @@ alsa_input_open(const char *uri, Mutex &mutex)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static constexpr const char *alsa_prefixes[] = {
|
static constexpr const char *alsa_prefixes[] = {
|
||||||
"alsa://",
|
ALSA_URI_PREFIX,
|
||||||
nullptr
|
nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user