diff --git a/miniaudio.h b/miniaudio.h index ccc809d0..4402fbb4 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -15567,7 +15567,7 @@ ma_result ma_device_stop__pulse(ma_device* pDevice) return MA_SUCCESS; } -ma_result ma_device_write__pulse(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount) +ma_result ma_device_write__pulse(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten) { ma_uint32 totalFramesWritten; @@ -15575,16 +15575,16 @@ ma_result ma_device_write__pulse(ma_device* pDevice, const void* pPCMFrames, ma_ ma_assert(pPCMFrames != NULL); ma_assert(frameCount > 0); - /* The stream needs to be uncorked first. */ - if (((ma_pa_stream_is_corked_proc)pDevice->pContext->pulse.pa_stream_is_corked)((ma_pa_stream*)pDevice->pulse.pStreamPlayback)) { - ma_result result = ma_device__cork_stream__pulse(pDevice, ma_device_type_playback, 0); - if (result != MA_SUCCESS) { - return result; - } + if (pFramesWritten != NULL) { + *pFramesWritten = 0; } totalFramesWritten = 0; while (totalFramesWritten < frameCount) { + if (ma_device__get_state(pDevice) != MA_STATE_STARTED) { + return MA_DEVICE_NOT_STARTED; + } + /* Place the data into the mapped buffer if we have one. */ if (pDevice->pulse.pMappedBufferPlayback != NULL && pDevice->pulse.mappedBufferFramesRemainingPlayback > 0) { ma_uint32 bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); @@ -15660,10 +15660,14 @@ ma_result ma_device_write__pulse(ma_device* pDevice, const void* pPCMFrames, ma_ } } + if (pFramesWritten != NULL) { + *pFramesWritten = totalFramesWritten; + } + return MA_SUCCESS; } -ma_result ma_device_read__pulse(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount) +ma_result ma_device_read__pulse(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesRead) { ma_uint32 totalFramesRead; @@ -15671,18 +15675,14 @@ ma_result ma_device_read__pulse(ma_device* pDevice, void* pPCMFrames, ma_uint32 ma_assert(pPCMFrames != NULL); ma_assert(frameCount > 0); - /* The stream needs to be uncorked first. */ - if (((ma_pa_stream_is_corked_proc)pDevice->pContext->pulse.pa_stream_is_corked)((ma_pa_stream*)pDevice->pulse.pStreamCapture)) { - ma_result result = ma_device__cork_stream__pulse(pDevice, ma_device_type_capture, 0); - if (result != MA_SUCCESS) { - return result; - } + if (pFramesRead != NULL) { + *pFramesRead = 0; } totalFramesRead = 0; while (totalFramesRead < frameCount) { if (ma_device__get_state(pDevice) != MA_STATE_STARTED) { - break; + return MA_DEVICE_NOT_STARTED; } /* If a buffer is mapped we need to write to that first. Once it's consumed we reset the event and unmap it. */ @@ -15787,9 +15787,183 @@ ma_result ma_device_read__pulse(ma_device* pDevice, void* pPCMFrames, ma_uint32 } } + if (pFramesRead != NULL) { + *pFramesRead = totalFramesRead; + } + return MA_SUCCESS; } +ma_result ma_device_main_loop__pulse(ma_device* pDevice) +{ + ma_result result = MA_SUCCESS; + ma_bool32 exitLoop = MA_FALSE; + + ma_assert(pDevice != NULL); + + /* The stream needs to be uncorked first. We do this at the top for both capture and playback for PulseAudio. */ + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + result = ma_device__cork_stream__pulse(pDevice, ma_device_type_capture, 0); + if (result != MA_SUCCESS) { + return result; + } + } + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + result = ma_device__cork_stream__pulse(pDevice, ma_device_type_playback, 0); + if (result != MA_SUCCESS) { + return result; + } + } + + + while (ma_device__get_state(pDevice) == MA_STATE_STARTED && !exitLoop) { + switch (pDevice->type) + { + case ma_device_type_duplex: + { + /* The process is: device_read -> convert -> callback -> convert -> device_write */ + ma_uint8 capturedDeviceData[8192]; + ma_uint8 playbackDeviceData[8192]; + ma_uint32 capturedDeviceDataCapInFrames = sizeof(capturedDeviceData) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + ma_uint32 playbackDeviceDataCapInFrames = sizeof(playbackDeviceData) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + + ma_uint32 totalFramesProcessed = 0; + ma_uint32 periodSizeInFrames = ma_min(pDevice->capture.internalBufferSizeInFrames/pDevice->capture.internalPeriods, pDevice->playback.internalBufferSizeInFrames/pDevice->playback.internalPeriods); + + while (totalFramesProcessed < periodSizeInFrames) { + ma_device_callback_proc onData; + ma_uint32 framesRemaining = periodSizeInFrames - totalFramesProcessed; + ma_uint32 framesProcessed; + ma_uint32 framesToProcess = framesRemaining; + if (framesToProcess > capturedDeviceDataCapInFrames) { + framesToProcess = capturedDeviceDataCapInFrames; + } + + result = ma_device_read__pulse(pDevice, capturedDeviceData, framesToProcess, &framesProcessed); + if (result != MA_SUCCESS) { + exitLoop = MA_TRUE; + break; + } + + onData = pDevice->onData; + if (onData != NULL) { + pDevice->capture._dspFrameCount = framesToProcess; + pDevice->capture._dspFrames = capturedDeviceData; + + for (;;) { + ma_uint8 capturedData[8192]; + ma_uint8 playbackData[8192]; + ma_uint32 capturedDataCapInFrames = sizeof(capturedData) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); + ma_uint32 playbackDataCapInFrames = sizeof(playbackData) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); + + ma_uint32 capturedFramesToTryProcessing = ma_min(capturedDataCapInFrames, playbackDataCapInFrames); + ma_uint32 capturedFramesToProcess = (ma_uint32)ma_pcm_converter_read(&pDevice->capture.converter, capturedData, capturedFramesToTryProcessing); + if (capturedFramesToProcess == 0) { + break; /* Don't fire the data callback with zero frames. */ + } + + onData(pDevice, playbackData, capturedData, capturedFramesToProcess); + + /* At this point the playbackData buffer should be holding data that needs to be written to the device. */ + pDevice->playback._dspFrameCount = capturedFramesToProcess; + pDevice->playback._dspFrames = playbackData; + for (;;) { + ma_uint32 playbackDeviceFramesCount = (ma_uint32)ma_pcm_converter_read(&pDevice->playback.converter, playbackDeviceData, playbackDeviceDataCapInFrames); + if (playbackDeviceFramesCount == 0) { + break; + } + + result = ma_device_write__pulse(pDevice, playbackDeviceData, playbackDeviceFramesCount, NULL); + if (result != MA_SUCCESS) { + exitLoop = MA_TRUE; + break; + } + + if (playbackDeviceFramesCount < playbackDeviceDataCapInFrames) { + break; + } + } + + if (capturedFramesToProcess < capturedFramesToTryProcessing) { + break; + } + + /* In case an error happened from ma_device_write2__alsa()... */ + if (result != MA_SUCCESS) { + exitLoop = MA_TRUE; + break; + } + } + } + + totalFramesProcessed += framesProcessed; + } + } break; + + case ma_device_type_capture: + { + ma_uint8 intermediaryBuffer[8192]; + ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + ma_uint32 periodSizeInFrames = pDevice->capture.internalBufferSizeInFrames / pDevice->capture.internalPeriods; + ma_uint32 framesReadThisPeriod = 0; + while (framesReadThisPeriod < periodSizeInFrames) { + ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesReadThisPeriod; + ma_uint32 framesProcessed; + ma_uint32 framesToReadThisIteration = framesRemainingInPeriod; + if (framesToReadThisIteration > intermediaryBufferSizeInFrames) { + framesToReadThisIteration = intermediaryBufferSizeInFrames; + } + + result = ma_device_read__pulse(pDevice, intermediaryBuffer, framesToReadThisIteration, &framesProcessed); + if (result != MA_SUCCESS) { + exitLoop = MA_TRUE; + break; + } + + ma_device__send_frames_to_client(pDevice, framesProcessed, intermediaryBuffer); + + framesReadThisPeriod += framesProcessed; + } + } break; + + case ma_device_type_playback: + { + ma_uint8 intermediaryBuffer[8192]; + ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + ma_uint32 periodSizeInFrames = pDevice->playback.internalBufferSizeInFrames / pDevice->playback.internalPeriods; + ma_uint32 framesWrittenThisPeriod = 0; + while (framesWrittenThisPeriod < periodSizeInFrames) { + ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesWrittenThisPeriod; + ma_uint32 framesProcessed; + ma_uint32 framesToWriteThisIteration = framesRemainingInPeriod; + if (framesToWriteThisIteration > intermediaryBufferSizeInFrames) { + framesToWriteThisIteration = intermediaryBufferSizeInFrames; + } + + ma_device__read_frames_from_client(pDevice, framesToWriteThisIteration, intermediaryBuffer); + + result = ma_device_write__pulse(pDevice, intermediaryBuffer, framesToWriteThisIteration, &framesProcessed); + if (result != MA_SUCCESS) { + exitLoop = MA_TRUE; + break; + } + + framesWrittenThisPeriod += framesProcessed; + } + } break; + + /* To silence a warning. Will never hit this. */ + case ma_device_type_loopback: + default: break; + } + } + + /* Here is where the device needs to be stopped. */ + ma_device_stop__pulse(pDevice); + + return result; +} + ma_result ma_context_uninit__pulse(ma_context* pContext) { @@ -15973,9 +16147,10 @@ ma_result ma_context_init__pulse(const ma_context_config* pConfig, ma_context* p pContext->onDeviceInit = ma_device_init__pulse; pContext->onDeviceUninit = ma_device_uninit__pulse; pContext->onDeviceStart = NULL; - pContext->onDeviceStop = ma_device_stop__pulse; - pContext->onDeviceWrite = ma_device_write__pulse; - pContext->onDeviceRead = ma_device_read__pulse; + pContext->onDeviceStop = NULL; + pContext->onDeviceWrite = NULL; + pContext->onDeviceRead = NULL; + pContext->onDeviceMainLoop = ma_device_main_loop__pulse; if (pConfig->pulse.pApplicationName) { pContext->pulse.pApplicationName = ma_copy_string(pConfig->pulse.pApplicationName); @@ -33568,7 +33743,7 @@ v0.9.7 - 2019-xx-xx - If you need to capture from a specific output device, set the capture device ID to that of a playback device. - Fix a crash when an error is posted in ma_device_init(). - Fix a compilation error when compiling for ARM architectures. - - Minor refactoring to the ALSA backend. + - Minor refactoring to the ALSA and PulseAudio backends. v0.9.6 - 2019-08-04 - Add support for loading decoders using a wchar_t string for file paths. diff --git a/tests/ma_duplex.c b/tests/ma_duplex.c index 05ec6abb..9be967fe 100644 --- a/tests/ma_duplex.c +++ b/tests/ma_duplex.c @@ -63,7 +63,7 @@ int main(int argc, char** argv) #endif - ma_backend backend = ma_backend_alsa; + ma_backend backend = ma_backend_pulseaudio; ma_context_config contextConfig = ma_context_config_init(); contextConfig.logCallback = log_callback; @@ -100,11 +100,11 @@ int main(int argc, char** argv) deviceConfig.capture.pDeviceID = NULL; deviceConfig.capture.format = ma_format_s16; deviceConfig.capture.channels = 2; - deviceConfig.capture.shareMode = ma_share_mode_exclusive; + deviceConfig.capture.shareMode = ma_share_mode_shared; deviceConfig.playback.pDeviceID = NULL; deviceConfig.playback.format = ma_format_s16; deviceConfig.playback.channels = 2; - deviceConfig.playback.shareMode = ma_share_mode_exclusive; + deviceConfig.playback.shareMode = ma_share_mode_shared; deviceConfig.sampleRate = 50000; //deviceConfig.bufferSizeInMilliseconds = 60; deviceConfig.bufferSizeInFrames = 4096;