Commit 7600817e authored by David Reid's avatar David Reid

Get basic playback working with PulseAudio.

parent 3afb29af
...@@ -798,6 +798,7 @@ struct mal_context ...@@ -798,6 +798,7 @@ struct mal_context
struct struct
{ {
mal_handle pulseSO; mal_handle pulseSO;
mal_handle pulsesimpleSO;
} pulse; } pulse;
#endif #endif
#ifdef MAL_SUPPORT_COREAUDIO #ifdef MAL_SUPPORT_COREAUDIO
...@@ -1143,8 +1144,8 @@ struct mal_device ...@@ -1143,8 +1144,8 @@ struct mal_device
// - WASAPI // - WASAPI
// - DirectSound // - DirectSound
// - WinMM // - WinMM
// - PulseAudio
// - ALSA // - ALSA
// - PulseAudio
// - OSS // - OSS
// - OpenSL|ES // - OpenSL|ES
// - OpenAL // - OpenAL
...@@ -6907,23 +6908,143 @@ static mal_result mal_result_from_pulse(int result) ...@@ -6907,23 +6908,143 @@ static mal_result mal_result_from_pulse(int result)
} }
} }
static pa_sample_format_t mal_format_to_pulse(mal_format format)
{
switch (format)
{
case mal_format_u8: return PA_SAMPLE_U8;
case mal_format_s16: return PA_SAMPLE_S16LE;
//case mal_format_s16be: return PA_SAMPLE_S16BE;
case mal_format_s24: return PA_SAMPLE_S24LE;
//case mal_format_s24be: return PA_SAMPLE_S24BE;
//case mal_format_s24_32: return PA_SAMPLE_S24_32LE;
//case mal_format_s24_32be: return PA_SAMPLE_S24_32BE;
case mal_format_s32: return PA_SAMPLE_S32LE;
//case mal_format_s32be: return PA_SAMPLE_S32BE;
case mal_format_f32: return PA_SAMPLE_FLOAT32LE;
//case mal_format_f32be: return PA_SAMPLE_FLOAT32BE;
default: return PA_SAMPLE_INVALID;
}
}
static mal_format mal_format_from_pulse(pa_sample_format_t format)
{
switch (format)
{
case PA_SAMPLE_U8: return mal_format_u8;
case PA_SAMPLE_S16LE: return mal_format_s16;
//case PA_SAMPLE_S16BE: return mal_format_s16be;
case PA_SAMPLE_S24LE: return mal_format_s24;
//case PA_SAMPLE_S24BE: return mal_format_s24be;
//case PA_SAMPLE_S24_32LE: return mal_format_s24_32;
//case PA_SAMPLE_S24_32BE: return mal_format_s24_32be;
case PA_SAMPLE_S32LE: return mal_format_s32;
//case PA_SAMPLE_S32BE: return mal_format_s32be;
case PA_SAMPLE_FLOAT32LE: return mal_format_f32;
//case PA_SAMPLE_FLOAT32BE: return mal_format_f32be;
default: return mal_format_unknown;
}
}
static mal_channel mal_channel_position_from_pulse(enum pa_channel_position position)
{
switch (position)
{
case PA_CHANNEL_POSITION_INVALID: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_MONO: return MAL_CHANNEL_MONO;
case PA_CHANNEL_POSITION_FRONT_LEFT: return MAL_CHANNEL_FRONT_LEFT;
case PA_CHANNEL_POSITION_FRONT_RIGHT: return MAL_CHANNEL_FRONT_RIGHT;
case PA_CHANNEL_POSITION_FRONT_CENTER: return MAL_CHANNEL_FRONT_CENTER;
case PA_CHANNEL_POSITION_REAR_CENTER: return MAL_CHANNEL_BACK_CENTER;
case PA_CHANNEL_POSITION_REAR_LEFT: return MAL_CHANNEL_BACK_LEFT;
case PA_CHANNEL_POSITION_REAR_RIGHT: return MAL_CHANNEL_BACK_RIGHT;
case PA_CHANNEL_POSITION_LFE: return MAL_CHANNEL_LFE;
case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: return MAL_CHANNEL_FRONT_LEFT_CENTER;
case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: return MAL_CHANNEL_FRONT_RIGHT_CENTER;
case PA_CHANNEL_POSITION_SIDE_LEFT: return MAL_CHANNEL_SIDE_LEFT;
case PA_CHANNEL_POSITION_SIDE_RIGHT: return MAL_CHANNEL_SIDE_RIGHT;
case PA_CHANNEL_POSITION_AUX0: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX1: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX2: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX3: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX4: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX5: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX6: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX7: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX8: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX9: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX10: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX11: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX12: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX13: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX14: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX15: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX16: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX17: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX18: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX19: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX20: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX21: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX22: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX23: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX24: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX25: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX26: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX27: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX28: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX29: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX30: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_AUX31: return MAL_CHANNEL_NONE;
case PA_CHANNEL_POSITION_TOP_CENTER: return MAL_CHANNEL_TOP_CENTER;
case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return MAL_CHANNEL_TOP_FRONT_LEFT;
case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return MAL_CHANNEL_TOP_FRONT_RIGHT;
case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: return MAL_CHANNEL_TOP_FRONT_CENTER;
case PA_CHANNEL_POSITION_TOP_REAR_LEFT: return MAL_CHANNEL_TOP_BACK_LEFT;
case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return MAL_CHANNEL_TOP_BACK_RIGHT;
case PA_CHANNEL_POSITION_TOP_REAR_CENTER: return MAL_CHANNEL_TOP_BACK_CENTER;
default: return (mal_uint8)position;
}
}
static mal_result mal_context_init__pulse(mal_context* pContext) static mal_result mal_context_init__pulse(mal_context* pContext)
{ {
mal_assert(pContext != NULL); mal_assert(pContext != NULL);
const char* libs[] = { // libpulse.so
const char* libpulseNames[] = {
"libpulse.so", "libpulse.so",
"libpulse.so.0" "libpulse.so.0"
}; };
for (size_t i = 0; i < mal_countof(libs); ++i) { for (size_t i = 0; i < mal_countof(libpulseNames); ++i) {
pContext->pulse.pulseSO = mal_dlopen(libs[i]); pContext->pulse.pulseSO = mal_dlopen(libpulseNames[i]);
if (pContext->pulse.pulseSO != NULL) { if (pContext->pulse.pulseSO != NULL) {
break; break;
} }
} }
if (pContext->pulse.pulseSO != NULL) { if (pContext->pulse.pulseSO == NULL) {
return MAL_NO_BACKEND;
}
// libpulse-simple.so
const char* libpulsesimpleNames[] = {
"libpulse-simple.so",
"libpulse-simple.so.0"
};
for (size_t i = 0; i < mal_countof(libpulsesimpleNames); ++i) {
pContext->pulse.pulsesimpleSO = mal_dlopen(libpulsesimpleNames[i]);
if (pContext->pulse.pulsesimpleSO != NULL) {
break;
}
}
if (pContext->pulse.pulsesimpleSO == NULL) {
printf("Failed to libpulse-simple.\n");
mal_dlclose(pContext->pulse.pulseSO);
return MAL_NO_BACKEND; return MAL_NO_BACKEND;
} }
...@@ -6954,6 +7075,8 @@ static mal_result mal_enumerate_devices__pulse(mal_context* pContext, mal_device ...@@ -6954,6 +7075,8 @@ static mal_result mal_enumerate_devices__pulse(mal_context* pContext, mal_device
static void mal_device_uninit__pulse(mal_device* pDevice) static void mal_device_uninit__pulse(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
pa_simple_free((pa_simple*)pDevice->pulse.pPA);
mal_free(pDevice->pulse.pIntermediaryBuffer);
} }
static mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) static mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice)
...@@ -6963,7 +7086,53 @@ static mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type ...@@ -6963,7 +7086,53 @@ static mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
mal_zero_object(&pDevice->pulse); mal_zero_object(&pDevice->pulse);
enum pa_stream_direction dir;
if (type == mal_device_type_playback) {
dir = PA_STREAM_PLAYBACK;
} else {
dir = PA_STREAM_RECORD;
}
pa_sample_spec ss;
ss.format = mal_format_to_pulse(pConfig->format);
if (ss.format == PA_SAMPLE_INVALID) {
ss.format = PA_SAMPLE_S16LE;
}
ss.channels = pConfig->channels;
ss.rate = pConfig->sampleRate;
pa_channel_map cmap;
pa_channel_map_init_extend(&cmap, ss.channels, PA_CHANNEL_MAP_DEFAULT);
pa_buffer_attr attr;
attr.maxlength = pConfig->bufferSizeInFrames * mal_get_sample_size_in_bytes(mal_format_from_pulse(ss.format))*ss.channels;
attr.tlength = attr.maxlength / pConfig->periods;
attr.prebuf = (mal_uint32)-1;
attr.minreq = attr.tlength;
attr.fragsize = attr.tlength;
int error;
pa_simple* pPA = pa_simple_new(NULL, "mini_al", dir, NULL, "mini_al", &ss, &cmap, &attr, &error);
if (pPA == NULL) {
return mal_post_error(pDevice, "[PulseAudio] Failed to initialize simple API.", mal_result_from_pulse(error));
}
pDevice->pulse.pPA = pPA;
pDevice->internalFormat = mal_format_from_pulse(ss.format);
pDevice->internalChannels = ss.channels;
pDevice->internalSampleRate = ss.rate;
for (mal_uint32 iChannel = 0; iChannel < pDevice->internalChannels; ++iChannel) {
pDevice->internalChannelMap[iChannel] = mal_channel_position_from_pulse(cmap.map[iChannel]);
}
pDevice->pulse.fragmentSizeInFrames = attr.tlength / (pDevice->internalChannels*mal_get_sample_size_in_bytes(pDevice->internalFormat));
pDevice->pulse.pIntermediaryBuffer = mal_malloc(attr.tlength);
if (pDevice->pulse.pIntermediaryBuffer == NULL) {
pa_simple_free((pa_simple*)pDevice->pulse.pPA);
return mal_post_error(pDevice, "[PulseAudio] Failed to allocate memory for intermediary buffer.", MAL_OUT_OF_MEMORY);
}
return MAL_SUCCESS; return MAL_SUCCESS;
} }
...@@ -6972,23 +7141,19 @@ static mal_result mal_device__start_backend__pulse(mal_device* pDevice) ...@@ -6972,23 +7141,19 @@ static mal_result mal_device__start_backend__pulse(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
#if 0 // A playback device is started by simply writing data to it. For capture we do nothing.
// The device is started by the next calls to read() and write(). For playback it's simple - just read
// data from the client, then write it to the device with write() which will in turn start the device.
// For capture it's a bit less intuitive - we do nothing (it'll be started automatically by the first
// call to read().
if (pDevice->type == mal_device_type_playback) { if (pDevice->type == mal_device_type_playback) {
// Playback. // Playback.
mal_device__read_frames_from_client(pDevice, pDevice->pulse.fragmentSizeInFrames, pDevice->pulse.pIntermediaryBuffer); mal_device__read_frames_from_client(pDevice, pDevice->pulse.fragmentSizeInFrames, pDevice->pulse.pIntermediaryBuffer);
//int bytesWritten = write(pDevice->oss.fd, pDevice->oss.pIntermediaryBuffer, pDevice->oss.fragmentSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat)); int error;
if (bytesWritten == -1) { int bytesWritten = pa_simple_write((pa_simple*)pDevice->pulse.pPA, pDevice->pulse.pIntermediaryBuffer, pDevice->pulse.fragmentSizeInFrames * pDevice->internalChannels*mal_get_sample_size_in_bytes(pDevice->internalFormat), &error);
return mal_post_error(pDevice, "[PulseAudio] Failed to send initial chunk of data to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); if (bytesWritten < 0) {
return mal_post_error(pDevice, "[PulseAudio] Failed to send initial chunk of data to the device.", mal_result_from_pulse(error));
} }
} else { } else {
// Capture. Do nothing. // Capture. Do nothing.
} }
#endif
return MAL_SUCCESS; return MAL_SUCCESS;
} }
...@@ -7028,18 +7193,18 @@ static mal_result mal_device__main_loop__pulse(mal_device* pDevice) ...@@ -7028,18 +7193,18 @@ static mal_result mal_device__main_loop__pulse(mal_device* pDevice)
// Playback. // Playback.
mal_device__read_frames_from_client(pDevice, pDevice->pulse.fragmentSizeInFrames, pDevice->pulse.pIntermediaryBuffer); mal_device__read_frames_from_client(pDevice, pDevice->pulse.fragmentSizeInFrames, pDevice->pulse.pIntermediaryBuffer);
//int bytesWritten = write(pDevice->oss.fd, pDevice->oss.pIntermediaryBuffer, pDevice->oss.fragmentSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat)); int error;
//if (bytesWritten < 0) { int bytesWritten = pa_simple_write((pa_simple*)pDevice->pulse.pPA, pDevice->pulse.pIntermediaryBuffer, pDevice->pulse.fragmentSizeInFrames * pDevice->internalChannels*mal_get_sample_size_in_bytes(pDevice->internalFormat), &error);
// return mal_post_error(pDevice, "[PulseAudio] Failed to send data from the client to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); if (bytesWritten < 0) {
//} return mal_post_error(pDevice, "[PulseAudio] Failed to send data from the client to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE);
}
} else { } else {
// Capture. // Capture.
//int bytesRead = read(pDevice->oss.fd, pDevice->oss.pIntermediaryBuffer, pDevice->oss.fragmentSizeInFrames * mal_get_sample_size_in_bytes(pDevice->internalFormat)); int error;
//if (bytesRead < 0) { int bytesRead = pa_simple_read((pa_simple*)pDevice->pulse.pPA, pDevice->pulse.pIntermediaryBuffer, pDevice->pulse.fragmentSizeInFrames * pDevice->internalChannels*mal_get_sample_size_in_bytes(pDevice->internalFormat), &error);
// return mal_post_error(pDevice, "[PulseAudio] Failed to read data from the device to be sent to the client.", MAL_FAILED_TO_READ_DATA_FROM_DEVICE); if (bytesRead< 0) {
//} return mal_post_error(pDevice, "[PulseAudio] Failed to read data from the device to be sent to the client.", MAL_FAILED_TO_READ_DATA_FROM_DEVICE);
}
int bytesRead = 0;
mal_uint32 framesRead = (mal_uint32)bytesRead / pDevice->internalChannels / mal_get_sample_size_in_bytes(pDevice->internalFormat); mal_uint32 framesRead = (mal_uint32)bytesRead / pDevice->internalChannels / mal_get_sample_size_in_bytes(pDevice->internalFormat);
mal_device__send_frames_to_client(pDevice, framesRead, pDevice->pulse.pIntermediaryBuffer); mal_device__send_frames_to_client(pDevice, framesRead, pDevice->pulse.pIntermediaryBuffer);
...@@ -9757,8 +9922,8 @@ mal_result mal_context_init(mal_backend backends[], mal_uint32 backendCount, con ...@@ -9757,8 +9922,8 @@ mal_result mal_context_init(mal_backend backends[], mal_uint32 backendCount, con
mal_backend_wasapi, mal_backend_wasapi,
mal_backend_dsound, mal_backend_dsound,
mal_backend_winmm, mal_backend_winmm,
mal_backend_pulseaudio,
mal_backend_alsa, mal_backend_alsa,
mal_backend_pulseaudio,
mal_backend_oss, mal_backend_oss,
mal_backend_opensl, mal_backend_opensl,
mal_backend_openal, mal_backend_openal,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment