diff --git a/mini_al.h b/mini_al.h index 0b8c3b90..9a0d7232 100644 --- a/mini_al.h +++ b/mini_al.h @@ -1,5 +1,5 @@ // Audio playback and capture library. Public domain. See "unlicense" statement at the end of this file. -// mini_al - v0.7 - 2018-02-25 +// mini_al - v0.x - 2018-xx-xx // // David Reid - davidreidsoftware@gmail.com @@ -17,6 +17,7 @@ // - DirectSound // - WinMM // - ALSA +// - PulseAudio // - OSS // - OpenSL|ES / Android // - OpenAL @@ -144,6 +145,9 @@ // #define MAL_NO_ALSA // Disables the ALSA backend. // +// #define MAL_NO_PULSEAUDIO +// Disables the PulseAudio backend. +// // #define MAL_NO_OSS // Disables the OSS backend. // @@ -233,6 +237,9 @@ extern "C" { #define MAL_SUPPORT_ALSA #endif #endif + #if !defined(MAL_APPLE) && !defined(MAL_ANDROID) && !defined(MAL_EMSCRIPTEN) + #define MAL_SUPPORT_PULSEAUDIO + #endif #if defined(MAL_APPLE) #define MAL_SUPPORT_COREAUDIO #endif @@ -265,6 +272,9 @@ extern "C" { #if !defined(MAL_NO_ALSA) && defined(MAL_SUPPORT_ALSA) #define MAL_ENABLE_ALSA #endif +#if !defined(MAL_NO_PULSEAUDIO) && defined(MAL_SUPPORT_PULSEAUDIO) + #define MAL_ENABLE_PULSEAUDIO +#endif #if !defined(MAL_NO_COREAUDIO) && defined(MAL_SUPPORT_COREAUDIO) #define MAL_ENABLE_COREAUDIO #endif @@ -502,6 +512,7 @@ typedef enum mal_backend_dsound, mal_backend_winmm, mal_backend_alsa, + mal_backend_pulseaudio, mal_backend_oss, mal_backend_opensl, mal_backend_openal, @@ -546,6 +557,9 @@ typedef union #ifdef MAL_SUPPORT_ALSA char alsa[256]; // ALSA uses a name string for identification. #endif +#ifdef MAL_SUPPORT_PULSEAUDIO + char pulseaudio[256]; // PulseAudio uses a name string for identification. +#endif #ifdef MAL_SUPPORT_COREAUDIO // TODO: Implement me. #endif @@ -780,6 +794,12 @@ struct mal_context mal_proc snd_pcm_info_get_name; } alsa; #endif +#ifdef MAL_SUPPORT_PULSEAUDIO + struct + { + mal_handle pulseSO; + } pulse; +#endif #ifdef MAL_SUPPORT_COREAUDIO struct { @@ -883,8 +903,8 @@ struct mal_context mal_proc alGetBufferiv; mal_bool32 isEnumerationSupported : 1; - mal_bool32 isFloat32Supported : 1; - mal_bool32 isMCFormatsSupported : 1; + mal_bool32 isFloat32Supported : 1; + mal_bool32 isMCFormatsSupported : 1; } openal; #endif #ifdef MAL_SUPPORT_SDL @@ -1036,6 +1056,15 @@ struct mal_device void* pIntermediaryBuffer; } alsa; #endif +#ifdef MAL_SUPPORT_PULSEAUDIO + struct + { + /*pa_simple**/ mal_ptr pPA; + mal_uint32 fragmentSizeInFrames; + mal_bool32 breakFromMainLoop : 1; + void* pIntermediaryBuffer; + } pulse; +#endif #ifdef MAL_SUPPORT_COREAUDIO struct { @@ -1114,6 +1143,7 @@ struct mal_device // - WASAPI // - DirectSound // - WinMM +// - PulseAudio // - ALSA // - OSS // - OpenSL|ES @@ -1755,6 +1785,14 @@ mal_result mal_decoder_seek_to_frame(mal_decoder* pDecoder, mal_uint64 frameInde #endif #endif #endif +#ifdef MAL_ENABLE_PULSEAUDIO + #define MAL_HAS_PULSEAUDIO + #ifdef __has_include + #if !__has_include() + #undef MAL_HAS_PULSEAUDIO + #endif + #endif +#endif #ifdef MAL_ENABLE_COREAUDIO #define MAL_HAS_COREAUDIO #endif @@ -5632,6 +5670,7 @@ static mal_result mal_device__main_loop__winmm(mal_device* pDevice) + /////////////////////////////////////////////////////////////////////////////// // // ALSA Backend @@ -6847,6 +6886,172 @@ static mal_result mal_device__main_loop__alsa(mal_device* pDevice) #endif // ALSA + +/////////////////////////////////////////////////////////////////////////////// +// +// PulseAudio Backend +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MAL_HAS_PULSEAUDIO +#include +#include + +static mal_result mal_result_from_pulse(int result) +{ + switch (result) { + case PA_OK: return MAL_SUCCESS; + case PA_ERR_ACCESS: return MAL_ACCESS_DENIED; + case PA_ERR_INVALID: return MAL_INVALID_ARGS; + case PA_ERR_NOENTITY: return MAL_NO_DEVICE; + default: return MAL_ERROR; + } +} + +static mal_result mal_context_init__pulse(mal_context* pContext) +{ + mal_assert(pContext != NULL); + + const char* libs[] = { + "libpulse.so", + "libpulse.so.0" + }; + + for (size_t i = 0; i < mal_countof(libs); ++i) { + pContext->pulse.pulseSO = mal_dlopen(libs[i]); + if (pContext->pulse.pulseSO != NULL) { + break; + } + } + + if (pContext->pulse.pulseSO != NULL) { + return MAL_NO_BACKEND; + } + + // TODO: Retrieve pointers to relevant APIs. + + return MAL_SUCCESS; +} + +static mal_result mal_context_uninit__pulse(mal_context* pContext) +{ + mal_assert(pContext != NULL); + mal_assert(pContext->backend == mal_backend_pulseaudio); + + mal_dlclose(pContext->pulse.pulseSO); + return MAL_SUCCESS; +} + +static mal_result mal_enumerate_devices__pulse(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) +{ + (void)pContext; + + //mal_uint32 infoSize = *pCount; + *pCount = 0; + + return MAL_SUCCESS; +} + +static void mal_device_uninit__pulse(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); +} + +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) +{ + (void)pContext; + + mal_assert(pDevice != NULL); + mal_zero_object(&pDevice->pulse); + + + + return MAL_SUCCESS; +} + +static mal_result mal_device__start_backend__pulse(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + +#if 0 + // 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) { + // Playback. + 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)); + if (bytesWritten == -1) { + return mal_post_error(pDevice, "[PulseAudio] Failed to send initial chunk of data to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); + } + } else { + // Capture. Do nothing. + } +#endif + + return MAL_SUCCESS; +} + +static mal_result mal_device__stop_backend__pulse(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + // The simple API doesn't seem to have any explicit start/stop APIs, so when stopping we just flush. + int error = PA_OK; + pa_simple_flush((pa_simple*)pDevice->pulse.pPA, &error); + + return mal_result_from_pulse(error); +} + +static mal_result mal_device__break_main_loop__pulse(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + pDevice->pulse.breakFromMainLoop = MAL_TRUE; + return MAL_SUCCESS; +} + +static mal_result mal_device__main_loop__pulse(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + pDevice->pulse.breakFromMainLoop = MAL_FALSE; + while (!pDevice->pulse.breakFromMainLoop) { + // Break from the main loop if the device isn't started anymore. Likely what's happened is the application + // has requested that the device be stopped. + if (!mal_device_is_started(pDevice)) { + break; + } + + if (pDevice->type == mal_device_type_playback) { + // Playback. + 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)); + //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 { + // Capture. + //int bytesRead = read(pDevice->oss.fd, pDevice->oss.pIntermediaryBuffer, pDevice->oss.fragmentSizeInFrames * mal_get_sample_size_in_bytes(pDevice->internalFormat)); + //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_device__send_frames_to_client(pDevice, framesRead, pDevice->pulse.pIntermediaryBuffer); + } + } + + return MAL_SUCCESS; +} +#endif + + + /////////////////////////////////////////////////////////////////////////////// // // OSS Backend @@ -9160,6 +9365,11 @@ static mal_result mal_device__start_backend(mal_device* pDevice) result = mal_device__start_backend__alsa(pDevice); } #endif +#ifdef MAL_HAS_PULSEAUDIO + if (pDevice->pContext->backend == mal_backend_pulseaudio) { + result = mal_device__start_backend__pulse(pDevice); + } +#endif #ifdef MAL_HAS_OSS if (pDevice->pContext->backend == mal_backend_oss) { result = mal_device__start_backend__oss(pDevice); @@ -9204,6 +9414,11 @@ static mal_result mal_device__stop_backend(mal_device* pDevice) result = mal_device__stop_backend__alsa(pDevice); } #endif +#ifdef MAL_HAS_PULSEAUDIO + if (pDevice->pContext->backend == mal_backend_pulseaudio) { + result = mal_device__stop_backend__pulse(pDevice); + } +#endif #ifdef MAL_HAS_OSS if (pDevice->pContext->backend == mal_backend_oss) { result = mal_device__stop_backend__oss(pDevice); @@ -9248,6 +9463,11 @@ static mal_result mal_device__break_main_loop(mal_device* pDevice) result = mal_device__break_main_loop__alsa(pDevice); } #endif +#ifdef MAL_HAS_PULSEAUDIO + if (pDevice->pContext->backend == mal_backend_pulseaudio) { + result = mal_device__break_main_loop__pulse(pDevice); + } +#endif #ifdef MAL_HAS_OSS if (pDevice->pContext->backend == mal_backend_oss) { result = mal_device__break_main_loop__oss(pDevice); @@ -9292,6 +9512,11 @@ static mal_result mal_device__main_loop(mal_device* pDevice) result = mal_device__main_loop__alsa(pDevice); } #endif +#ifdef MAL_HAS_PULSEAUDIO + if (pDevice->pContext->backend == mal_backend_pulseaudio) { + result = mal_device__main_loop__pulse(pDevice); + } +#endif #ifdef MAL_HAS_OSS if (pDevice->pContext->backend == mal_backend_oss) { result = mal_device__main_loop__oss(pDevice); @@ -9532,6 +9757,7 @@ mal_result mal_context_init(mal_backend backends[], mal_uint32 backendCount, con mal_backend_wasapi, mal_backend_dsound, mal_backend_winmm, + mal_backend_pulseaudio, mal_backend_alsa, mal_backend_oss, mal_backend_opensl, @@ -9542,7 +9768,7 @@ mal_result mal_context_init(mal_backend backends[], mal_uint32 backendCount, con if (backends == NULL) { backends = defaultBackends; - backendCount = sizeof(defaultBackends) / sizeof(defaultBackends[0]); + backendCount = mal_countof(defaultBackends); } mal_assert(backends != NULL); @@ -9576,6 +9802,12 @@ mal_result mal_context_init(mal_backend backends[], mal_uint32 backendCount, con result = mal_context_init__alsa(pContext); } break; #endif + #ifdef MAL_HAS_PULSEAUDIO + case mal_backend_pulseaudio: + { + result = mal_context_init__pulse(pContext); + } break; + #endif #ifdef MAL_HAS_OSS case mal_backend_oss: { @@ -9650,6 +9882,12 @@ mal_result mal_context_uninit(mal_context* pContext) return mal_context_uninit__alsa(pContext); } break; #endif + #ifdef MAL_HAS_PULSEAUDIO + case mal_backend_pulseaudio: + { + return mal_context_uninit__pulse(pContext); + } break; + #endif #ifdef MAL_HAS_OSS case mal_backend_oss: { @@ -9726,6 +9964,12 @@ mal_result mal_enumerate_devices(mal_context* pContext, mal_device_type type, ma return mal_enumerate_devices__alsa(pContext, type, pCount, pInfo); } break; #endif + #ifdef MAL_HAS_PULSEAUDIO + case mal_backend_pulseaudio: + { + return mal_enumerate_devices__pulse(pContext, type, pCount, pInfo); + } break; + #endif #ifdef MAL_HAS_OSS case mal_backend_oss: { @@ -9892,6 +10136,12 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi result = mal_device_init__alsa(pContext, type, pDeviceID, &config, pDevice); } break; #endif + #ifdef MAL_HAS_PULSEAUDIO + case mal_backend_pulseaudio: + { + result = mal_device_init__pulse(pContext, type, pDeviceID, &config, pDevice); + } break; + #endif #ifdef MAL_HAS_OSS case mal_backend_oss: { @@ -10069,6 +10319,11 @@ void mal_device_uninit(mal_device* pDevice) mal_device_uninit__alsa(pDevice); } #endif +#ifdef MAL_HAS_PULSEAUDIO + if (pDevice->pContext->backend == mal_backend_pulseaudio) { + mal_device_uninit__pulse(pDevice); + } +#endif #ifdef MAL_HAS_OSS if (pDevice->pContext->backend == mal_backend_oss) { mal_device_uninit__oss(pDevice); @@ -10275,6 +10530,8 @@ mal_context_config mal_context_config_init(mal_log_proc onLog) static void mal_get_default_device_config_channel_map(mal_uint32 channels, mal_uint8 channelMap[MAL_MAX_CHANNELS]) { + mal_zero_memory(channelMap, sizeof(mal_uint8)*MAL_MAX_CHANNELS); + switch (channels) { case 1: @@ -10348,7 +10605,6 @@ static void mal_get_default_device_config_channel_map(mal_uint32 channels, mal_u default: { // Just leave it all blank in this case. This will use the same mapping as the device's native mapping. - mal_zero_memory(channelMap, sizeof(channelMap)); } break; } } @@ -11794,6 +12050,7 @@ static mal_uint32 mal_decoder_internal_on_read_frames__wav(mal_dsp* pDSP, mal_ui case mal_format_s16: return (mal_uint32)drwav_read_s16(pWav, frameCount*pDecoder->internalChannels, (drwav_int16*)pSamplesOut) / pDecoder->internalChannels; case mal_format_s32: return (mal_uint32)drwav_read_s32(pWav, frameCount*pDecoder->internalChannels, (drwav_int32*)pSamplesOut) / pDecoder->internalChannels; case mal_format_f32: return (mal_uint32)drwav_read_f32(pWav, frameCount*pDecoder->internalChannels, (float*)pSamplesOut) / pDecoder->internalChannels; + default: break; } // Should never get here. If we do, it means the internal format was not set correctly at initialization time. @@ -11871,6 +12128,8 @@ mal_result mal_decoder_init_wav__internal(const mal_decoder_config* pConfig, mal static void mal_get_flac_channel_map(mal_uint32 channels, mal_uint8 channelMap[MAL_MAX_CHANNELS]) { + mal_zero_memory(channelMap, sizeof(mal_uint8)*MAL_MAX_CHANNELS); + switch (channels) { case 1: { @@ -11942,8 +12201,7 @@ static void mal_get_flac_channel_map(mal_uint32 channels, mal_uint8 channelMap[M default: { - // Should never get here because FLAC has a maximum of 8 channels. In any case, just set the channel map to all zeros. - mal_zero_memory(channelMap, sizeof(channelMap)); + // Should never get here because FLAC has a maximum of 8 channels. } break; } } @@ -13097,6 +13355,10 @@ void mal_pcm_f32_to_s32(int* pOut, const float* pIn, unsigned int count) // REVISION HISTORY // ================ // +// v0.x - 2018-xx-xx +// - Add support for PulseAudio. +// - Miscellaneous bug fixes. +// // v0.7 - 2018-02-25 // - API CHANGE: Change mal_src_read_frames() and mal_dsp_read_frames() to use 64-bit sample counts. // - Add decoder APIs for loading WAV, FLAC, Vorbis and MP3 files.