mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-22 00:06:59 +02:00
Initial working implementation of full-duplex on WASAPI.
This commit is contained in:
+44
-78
@@ -1,106 +1,72 @@
|
||||
// This example simply captures data from your default microphone until you press Enter, after
|
||||
// which it plays back the captured audio.
|
||||
// This example simply captures data from your default microphone until you press Enter. The output is saved to the file specified on the command line.
|
||||
|
||||
#define MINI_AL_IMPLEMENTATION
|
||||
#include "../mini_al.h"
|
||||
|
||||
#define DR_WAV_IMPLEMENTATION
|
||||
#include "../extras/dr_wav.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
mal_uint32 capturedSampleCount = 0;
|
||||
mal_int16* pCapturedSamples = NULL;
|
||||
mal_uint32 playbackSample = 0;
|
||||
|
||||
void on_recv_frames(mal_device* pDevice, void* pOutput, const void* pInput, mal_uint32 frameCount)
|
||||
void data_callback(mal_device* pDevice, void* pOutput, const void* pInput, mal_uint32 frameCount)
|
||||
{
|
||||
mal_uint32 sampleCount = frameCount * pDevice->channels;
|
||||
|
||||
mal_uint32 newCapturedSampleCount = capturedSampleCount + sampleCount;
|
||||
mal_int16* pNewCapturedSamples = (mal_int16*)realloc(pCapturedSamples, newCapturedSampleCount * sizeof(mal_int16));
|
||||
if (pNewCapturedSamples == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(pNewCapturedSamples + capturedSampleCount, pInput, sampleCount * sizeof(mal_int16));
|
||||
|
||||
pCapturedSamples = pNewCapturedSamples;
|
||||
capturedSampleCount = newCapturedSampleCount;
|
||||
|
||||
(void)pOutput;
|
||||
|
||||
drwav* pWav = (drwav*)pDevice->pUserData;
|
||||
mal_assert(pWav != NULL);
|
||||
|
||||
drwav_write_pcm_frames(pWav, frameCount, pInput);
|
||||
}
|
||||
|
||||
void on_send_frames(mal_device* pDevice, void* pOutput, const void* pInput, mal_uint32 frameCount)
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
mal_uint32 samplesToRead = frameCount * pDevice->channels;
|
||||
if (samplesToRead > capturedSampleCount-playbackSample) {
|
||||
samplesToRead = capturedSampleCount-playbackSample;
|
||||
}
|
||||
|
||||
if (samplesToRead == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(pOutput, pCapturedSamples + playbackSample, samplesToRead * sizeof(mal_int16));
|
||||
playbackSample += samplesToRead;
|
||||
|
||||
(void)pInput;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
mal_device_config config;
|
||||
|
||||
mal_context context;
|
||||
if (mal_context_init(NULL, 0, NULL, &context) != MAL_SUCCESS) {
|
||||
printf("Failed to initialize context.");
|
||||
if (argc < 2) {
|
||||
printf("No input file.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("Recording...\n");
|
||||
config = mal_device_config_init(mal_format_s16, 2, 48000, on_recv_frames, NULL);
|
||||
mal_device captureDevice;
|
||||
if (mal_device_init(&context, mal_device_type_capture, NULL, &config, &captureDevice) != MAL_SUCCESS) {
|
||||
mal_context_uninit(&context);
|
||||
mal_result result;
|
||||
|
||||
drwav_data_format wavFormat;
|
||||
wavFormat.container = drwav_container_riff;
|
||||
wavFormat.format = DR_WAVE_FORMAT_IEEE_FLOAT;
|
||||
wavFormat.channels = 2;
|
||||
wavFormat.sampleRate = 44100;
|
||||
wavFormat.bitsPerSample = 32;
|
||||
|
||||
drwav wav;
|
||||
if (drwav_init_file_write(&wav, argv[1], &wavFormat) == DRWAV_FALSE) {
|
||||
printf("Failed to initialize output file.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
mal_device_config config = mal_device_config_init(mal_device_type_capture);
|
||||
config.capture.format = mal_format_f32;
|
||||
config.capture.channels = wavFormat.channels;
|
||||
config.sampleRate = wavFormat.sampleRate;
|
||||
config.dataCallback = data_callback;
|
||||
config.pUserData = &wav;
|
||||
|
||||
mal_device device;
|
||||
result = mal_device_init(NULL, &config, &device);
|
||||
if (result != MAL_SUCCESS) {
|
||||
printf("Failed to initialize capture device.\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (mal_device_start(&captureDevice) != MAL_SUCCESS) {
|
||||
mal_device_uninit(&captureDevice);
|
||||
mal_context_uninit(&context);
|
||||
printf("Failed to start capture device.\n");
|
||||
result = mal_device_start(&device);
|
||||
if (result != MAL_SUCCESS) {
|
||||
mal_device_uninit(&device);
|
||||
printf("Failed to start device.\n");
|
||||
return -3;
|
||||
}
|
||||
|
||||
printf("Press Enter to stop recording...\n");
|
||||
getchar();
|
||||
|
||||
mal_device_uninit(&captureDevice);
|
||||
mal_device_uninit(&device);
|
||||
drwav_uninit(&wav);
|
||||
|
||||
|
||||
|
||||
printf("Playing...\n");
|
||||
config = mal_device_config_init(mal_format_s16, 2, 48000, on_send_frames, NULL);
|
||||
mal_device playbackDevice;
|
||||
if (mal_device_init(&context, mal_device_type_playback, NULL, &config, &playbackDevice) != MAL_SUCCESS) {
|
||||
mal_context_uninit(&context);
|
||||
printf("Failed to initialize playback device.\n");
|
||||
return -4;
|
||||
}
|
||||
|
||||
if (mal_device_start(&playbackDevice) != MAL_SUCCESS) {
|
||||
mal_device_uninit(&playbackDevice);
|
||||
mal_context_uninit(&context);
|
||||
printf("Failed to start playback device.\n");
|
||||
return -5;
|
||||
}
|
||||
|
||||
printf("Press Enter to quit...\n");
|
||||
getchar();
|
||||
|
||||
mal_device_uninit(&playbackDevice);
|
||||
|
||||
mal_context_uninit(&context);
|
||||
return 0;
|
||||
}
|
||||
@@ -2052,6 +2052,8 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device
|
||||
mal_uint32 internalBufferSizeInFrames;
|
||||
mal_uint32 internalPeriods;
|
||||
mal_pcm_converter converter;
|
||||
mal_uint32 _dspFrameCount; // Internal use only. Used as the data source when reading from the device.
|
||||
const mal_uint8* _dspFrames; // ^^^ AS ABOVE ^^^
|
||||
} playback;
|
||||
struct
|
||||
{
|
||||
@@ -2072,6 +2074,8 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device
|
||||
mal_uint32 internalBufferSizeInFrames;
|
||||
mal_uint32 internalPeriods;
|
||||
mal_pcm_converter converter;
|
||||
mal_uint32 _dspFrameCount; // Internal use only. Used as the data source when reading from the device.
|
||||
const mal_uint8* _dspFrames; // ^^^ AS ABOVE ^^^
|
||||
} capture;
|
||||
|
||||
union
|
||||
@@ -4623,6 +4627,53 @@ mal_uint32 mal_device__on_read_from_device(mal_pcm_converter* pDSP, mal_uint32 f
|
||||
return framesToRead;
|
||||
}
|
||||
|
||||
/* The PCM converter callback for reading from a buffer. */
|
||||
mal_uint32 mal_device__pcm_converter__on_read_from_buffer_capture(mal_pcm_converter* pConverter, mal_uint32 frameCount, void* pFramesOut, void* pUserData)
|
||||
{
|
||||
mal_device* pDevice = (mal_device*)pUserData;
|
||||
mal_assert(pDevice != NULL);
|
||||
|
||||
if (pDevice->capture._dspFrameCount == 0) {
|
||||
return 0; // Nothing left.
|
||||
}
|
||||
|
||||
mal_uint32 framesToRead = frameCount;
|
||||
if (framesToRead > pDevice->capture._dspFrameCount) {
|
||||
framesToRead = pDevice->capture._dspFrameCount;
|
||||
}
|
||||
|
||||
mal_uint32 bytesToRead = framesToRead * mal_get_bytes_per_frame(pConverter->formatConverterIn.config.formatIn, pConverter->channelRouter.config.channelsIn);
|
||||
mal_copy_memory(pFramesOut, pDevice->capture._dspFrames, bytesToRead);
|
||||
pDevice->capture._dspFrameCount -= framesToRead;
|
||||
pDevice->capture._dspFrames += bytesToRead;
|
||||
|
||||
return framesToRead;
|
||||
}
|
||||
|
||||
mal_uint32 mal_device__pcm_converter__on_read_from_buffer_playback(mal_pcm_converter* pConverter, mal_uint32 frameCount, void* pFramesOut, void* pUserData)
|
||||
{
|
||||
mal_device* pDevice = (mal_device*)pUserData;
|
||||
mal_assert(pDevice != NULL);
|
||||
|
||||
if (pDevice->playback._dspFrameCount == 0) {
|
||||
return 0; // Nothing left.
|
||||
}
|
||||
|
||||
mal_uint32 framesToRead = frameCount;
|
||||
if (framesToRead > pDevice->playback._dspFrameCount) {
|
||||
framesToRead = pDevice->playback._dspFrameCount;
|
||||
}
|
||||
|
||||
mal_uint32 bytesToRead = framesToRead * mal_get_bytes_per_frame(pConverter->formatConverterIn.config.formatIn, pConverter->channelRouter.config.channelsIn);
|
||||
mal_copy_memory(pFramesOut, pDevice->playback._dspFrames, bytesToRead);
|
||||
pDevice->playback._dspFrameCount -= framesToRead;
|
||||
pDevice->playback._dspFrames += bytesToRead;
|
||||
|
||||
return framesToRead;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// A helper function for reading sample data from the client. Returns the number of samples read from the client. Remaining samples
|
||||
// are filled with silence.
|
||||
static MAL_INLINE mal_uint32 mal_device__read_frames_from_client(mal_device* pDevice, mal_uint32 frameCount, void* pSamples)
|
||||
@@ -7156,10 +7207,10 @@ mal_result mal_device_init__wasapi(mal_context* pContext, const mal_device_confi
|
||||
|
||||
if (pConfig->deviceType == mal_device_type_capture || pConfig->deviceType == mal_device_type_duplex) {
|
||||
mal_device_init_internal_data__wasapi data;
|
||||
data.formatIn = pConfig->format;
|
||||
data.channelsIn = pConfig->channels;
|
||||
data.formatIn = pConfig->capture.format;
|
||||
data.channelsIn = pConfig->capture.channels;
|
||||
data.sampleRateIn = pConfig->sampleRate;
|
||||
mal_copy_memory(data.channelMapIn, pConfig->channelMap, sizeof(pConfig->channelMap));
|
||||
mal_copy_memory(data.channelMapIn, pConfig->capture.channelMap, sizeof(pConfig->capture.channelMap));
|
||||
data.usingDefaultFormat = pDevice->usingDefaultFormat;
|
||||
data.usingDefaultChannels = pDevice->usingDefaultChannels;
|
||||
data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate;
|
||||
@@ -7207,8 +7258,8 @@ mal_result mal_device_init__wasapi(mal_context* pContext, const mal_device_confi
|
||||
|
||||
if (pConfig->deviceType == mal_device_type_playback || pConfig->deviceType == mal_device_type_duplex) {
|
||||
mal_device_init_internal_data__wasapi data;
|
||||
data.formatIn = pConfig->format;
|
||||
data.channelsIn = pConfig->channels;
|
||||
data.formatIn = pConfig->playback.format;
|
||||
data.channelsIn = pConfig->playback.channels;
|
||||
data.sampleRateIn = pConfig->sampleRate;
|
||||
mal_copy_memory(data.channelMapIn, pConfig->channelMap, sizeof(pConfig->channelMap));
|
||||
data.usingDefaultFormat = pDevice->usingDefaultFormat;
|
||||
@@ -7216,16 +7267,9 @@ mal_result mal_device_init__wasapi(mal_context* pContext, const mal_device_confi
|
||||
data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate;
|
||||
data.usingDefaultChannelMap = pDevice->usingDefaultChannelMap;
|
||||
data.shareMode = pConfig->playback.shareMode;
|
||||
|
||||
// In duplex mode we want the playback device to have the same buffer size and period count as the capture device.
|
||||
if (pConfig->deviceType == mal_device_type_duplex) {
|
||||
data.bufferSizeInFramesIn = pDevice->capture.internalBufferSizeInFrames;
|
||||
data.periodsIn = pDevice->capture.internalPeriods;
|
||||
} else {
|
||||
data.bufferSizeInFramesIn = pConfig->bufferSizeInFrames;
|
||||
data.bufferSizeInMillisecondsIn = pConfig->bufferSizeInMilliseconds;
|
||||
data.periodsIn = pConfig->periods;
|
||||
}
|
||||
|
||||
result = mal_device_init_internal__wasapi(pDevice->pContext, mal_device_type_playback, pConfig->playback.pDeviceID, &data);
|
||||
if (result != MAL_SUCCESS) {
|
||||
@@ -7446,7 +7490,7 @@ mal_result mal_device__get_available_frames__wasapi(mal_device* pDevice, mal_IAu
|
||||
*pFrameCount = paddingFramesCount;
|
||||
} else {
|
||||
if ((mal_ptr)pAudioClient == pDevice->wasapi.pAudioClientPlayback) {
|
||||
*pFrameCount = pDevice->bufferSizeInFrames - paddingFramesCount;
|
||||
*pFrameCount = (pDevice->playback.internalBufferSizeInFrames/pDevice->playback.internalPeriods) - paddingFramesCount;
|
||||
} else {
|
||||
*pFrameCount = paddingFramesCount;
|
||||
}
|
||||
@@ -8616,11 +8660,6 @@ mal_result mal_device_init__dsound(mal_context* pContext, const mal_device_confi
|
||||
mal_assert(pDevice != NULL);
|
||||
mal_zero_object(&pDevice->dsound);
|
||||
|
||||
/* Full-duplex is not yet implemented. */
|
||||
if (pConfig->deviceType == mal_device_type_duplex) {
|
||||
return MAL_INVALID_ARGS;
|
||||
}
|
||||
|
||||
/* DirectSound should use a latency of about 20ms per period for low latency mode. */
|
||||
if (pDevice->usingDefaultBufferSize) {
|
||||
if (pConfig->performanceProfile == mal_performance_profile_low_latency) {
|
||||
@@ -9717,11 +9756,6 @@ mal_result mal_device_init__winmm(mal_context* pContext, const mal_device_config
|
||||
mal_assert(pDevice != NULL);
|
||||
mal_zero_object(&pDevice->winmm);
|
||||
|
||||
/* Full-duplex is not yet implemented. */
|
||||
if (pConfig->deviceType == mal_device_type_duplex) {
|
||||
return MAL_INVALID_ARGS;
|
||||
}
|
||||
|
||||
/* No exlusive mode with WinMM. */
|
||||
if (((pConfig->deviceType == mal_device_type_playback || pConfig->deviceType == mal_device_type_duplex) && pConfig->playback.shareMode == mal_share_mode_exclusive) ||
|
||||
((pConfig->deviceType == mal_device_type_capture || pConfig->deviceType == mal_device_type_duplex) && pConfig->capture.shareMode == mal_share_mode_exclusive)) {
|
||||
@@ -20657,6 +20691,50 @@ void mal_device__post_init_setup(mal_device* pDevice, mal_device_type deviceType
|
||||
dspConfig.onRead = mal_device__on_read_from_device;
|
||||
mal_pcm_converter_init(&dspConfig, &pDevice->dsp);
|
||||
}
|
||||
|
||||
|
||||
/* PCM converters. */
|
||||
if (deviceType == mal_device_type_capture || deviceType == mal_device_type_duplex) {
|
||||
/* Converting from internal device format to public format. */
|
||||
mal_pcm_converter_config converterConfig = mal_pcm_converter_config_init_new();
|
||||
converterConfig.neverConsumeEndOfInput = MAL_TRUE;
|
||||
converterConfig.pUserData = pDevice;
|
||||
converterConfig.formatIn = pDevice->capture.internalFormat;
|
||||
converterConfig.channelsIn = pDevice->capture.internalChannels;
|
||||
converterConfig.sampleRateIn = pDevice->capture.internalSampleRate;
|
||||
mal_channel_map_copy(converterConfig.channelMapIn, pDevice->capture.internalChannelMap, pDevice->capture.internalChannels);
|
||||
converterConfig.formatOut = pDevice->capture.format;
|
||||
converterConfig.channelsOut = pDevice->capture.channels;
|
||||
converterConfig.sampleRateOut = pDevice->sampleRate;
|
||||
mal_channel_map_copy(converterConfig.channelMapOut, pDevice->capture.channelMap, pDevice->capture.channels);
|
||||
if (deviceType == mal_device_type_capture) {
|
||||
converterConfig.onRead = mal_device__on_read_from_device;
|
||||
} else {
|
||||
converterConfig.onRead = mal_device__pcm_converter__on_read_from_buffer_capture;
|
||||
}
|
||||
mal_pcm_converter_init(&converterConfig, &pDevice->capture.converter);
|
||||
}
|
||||
|
||||
if (deviceType == mal_device_type_playback || deviceType == mal_device_type_duplex) {
|
||||
/* Converting from public format to device format. */
|
||||
mal_pcm_converter_config converterConfig = mal_pcm_converter_config_init_new();
|
||||
converterConfig.neverConsumeEndOfInput = MAL_TRUE;
|
||||
converterConfig.pUserData = pDevice;
|
||||
converterConfig.formatIn = pDevice->playback.format;
|
||||
converterConfig.channelsIn = pDevice->playback.channels;
|
||||
converterConfig.sampleRateIn = pDevice->sampleRate;
|
||||
mal_channel_map_copy(converterConfig.channelMapIn, pDevice->playback.channelMap, pDevice->playback.channels);
|
||||
converterConfig.formatOut = pDevice->playback.internalFormat;
|
||||
converterConfig.channelsOut = pDevice->playback.internalChannels;
|
||||
converterConfig.sampleRateOut = pDevice->playback.internalSampleRate;
|
||||
mal_channel_map_copy(converterConfig.channelMapOut, pDevice->playback.internalChannelMap, pDevice->playback.internalChannels);
|
||||
if (deviceType == mal_device_type_playback) {
|
||||
converterConfig.onRead = mal_device__on_read_from_client;
|
||||
} else {
|
||||
converterConfig.onRead = mal_device__pcm_converter__on_read_from_buffer_playback;
|
||||
}
|
||||
mal_pcm_converter_init(&converterConfig, &pDevice->playback.converter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20700,7 +20778,13 @@ mal_thread_result MAL_THREADCALL mal_worker_thread(void* pData)
|
||||
(pDevice->type == mal_device_type_duplex && pDevice->pContext->onDeviceWrite != NULL && pDevice->pContext->onDeviceRead != NULL)
|
||||
);
|
||||
|
||||
mal_uint32 periodSizeInFrames = pDevice->bufferSizeInFrames / pDevice->periods;
|
||||
mal_uint32 periodSizeInFrames;
|
||||
if (pDevice->type == mal_device_type_capture || pDevice->type == mal_device_type_duplex) {
|
||||
periodSizeInFrames = pDevice->capture.internalBufferSizeInFrames / pDevice->capture.internalPeriods;
|
||||
} else {
|
||||
periodSizeInFrames = pDevice->playback.internalBufferSizeInFrames / pDevice->playback.internalPeriods;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
With the blocking API, the device is started automatically in read()/write(). All we need to do is enter the loop and just keep reading
|
||||
@@ -20718,6 +20802,77 @@ mal_thread_result MAL_THREADCALL mal_worker_thread(void* pData)
|
||||
while (mal_device__get_state(pDevice) == MAL_STATE_STARTED) {
|
||||
mal_result result = MAL_SUCCESS;
|
||||
mal_uint32 totalFramesProcessed = 0;
|
||||
|
||||
if (pDevice->type == mal_device_type_duplex) {
|
||||
/* The process is device_read -> convert -> callback -> convert -> device_write. */
|
||||
mal_uint8 captureDeviceData[4096];
|
||||
mal_uint32 captureDeviceDataCapInFrames = sizeof(captureDeviceData) / mal_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels);
|
||||
|
||||
while (totalFramesProcessed < periodSizeInFrames) {
|
||||
mal_uint32 framesRemaining = periodSizeInFrames - totalFramesProcessed;
|
||||
mal_uint32 framesToProcess = framesRemaining;
|
||||
if (framesToProcess > captureDeviceDataCapInFrames) {
|
||||
framesToProcess = captureDeviceDataCapInFrames;
|
||||
}
|
||||
|
||||
result = pDevice->pContext->onDeviceRead(pDevice, captureDeviceData, framesToProcess);
|
||||
if (result != MAL_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
|
||||
mal_device_callback_proc onData = pDevice->onData;
|
||||
if (onData != NULL) {
|
||||
pDevice->capture._dspFrameCount = framesToProcess;
|
||||
pDevice->capture._dspFrames = captureDeviceData;
|
||||
|
||||
/* We need to process every input frame. */
|
||||
for (;;) {
|
||||
mal_uint8 capturedData[4096]; // In capture.format/channels format
|
||||
mal_uint8 playbackData[4096]; // In playback.format/channels format
|
||||
|
||||
mal_uint32 capturedDataCapInFrames = sizeof(capturedData) / mal_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels);
|
||||
mal_uint32 playbackDataCapInFrames = sizeof(playbackData) / mal_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels);
|
||||
|
||||
mal_uint32 capturedFramesToTryProcessing = mal_min(capturedDataCapInFrames, playbackDataCapInFrames);
|
||||
mal_uint32 capturedFramesToProcess = (mal_uint32)mal_pcm_converter_read(&pDevice->capture.converter, capturedFramesToTryProcessing, capturedData, pDevice->capture.converter.pUserData);
|
||||
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 (;;) {
|
||||
mal_uint8 playbackDeviceData[4096];
|
||||
|
||||
mal_uint32 playbackDeviceDataCapInFrames = sizeof(playbackDeviceData) / mal_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels);
|
||||
mal_uint32 playbackDeviceFramesCount = (mal_uint32)mal_pcm_converter_read(&pDevice->playback.converter, playbackDeviceDataCapInFrames, playbackDeviceData, pDevice->playback.converter.pUserData);
|
||||
if (playbackDeviceFramesCount == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
result = pDevice->pContext->onDeviceWrite(pDevice, playbackDeviceData, playbackDeviceFramesCount);
|
||||
if (result != MAL_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (capturedFramesToProcess < capturedFramesToTryProcessing) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* In case an error happened from onDeviceWrite()... */
|
||||
if (result != MAL_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
totalFramesProcessed += framesToProcess;
|
||||
}
|
||||
} else {
|
||||
mal_uint8 buffer[4096];
|
||||
mal_uint32 bufferSizeInFrames = sizeof(buffer) / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
|
||||
|
||||
@@ -20738,6 +20893,7 @@ mal_thread_result MAL_THREADCALL mal_worker_thread(void* pData)
|
||||
|
||||
totalFramesProcessed += framesToProcess;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get out of the loop if read()/write() returned an error. It probably means the device has been stopped. */
|
||||
if (result != MAL_SUCCESS) {
|
||||
|
||||
+34
-8
@@ -3,6 +3,9 @@
|
||||
#define MINI_AL_IMPLEMENTATION
|
||||
#include "../mini_al.h"
|
||||
|
||||
#define DR_WAV_IMPLEMENTATION
|
||||
#include "../extras/dr_wav.h"
|
||||
|
||||
void log_callback(mal_context* pContext, mal_device* pDevice, mal_uint32 logLevel, const char* message)
|
||||
{
|
||||
(void)pContext;
|
||||
@@ -21,14 +24,31 @@ void data_callback(mal_device* pDevice, void* pOutput, const void* pInput, mal_u
|
||||
{
|
||||
/* In this test the format and channel count are the same for both input and output which means we can just memcpy(). */
|
||||
mal_copy_memory(pOutput, pInput, frameCount * mal_get_bytes_per_frame(pDevice->format, pDevice->channels));
|
||||
|
||||
/* Also write to a wav file for debugging. */
|
||||
drwav* pWav = (drwav*)pDevice->pUserData;
|
||||
mal_assert(pWav != NULL);
|
||||
|
||||
drwav_write_pcm_frames(pWav, frameCount, pInput);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
mal_result result;
|
||||
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
drwav_data_format wavFormat;
|
||||
wavFormat.container = drwav_container_riff;
|
||||
wavFormat.format = DR_WAVE_FORMAT_IEEE_FLOAT;
|
||||
wavFormat.channels = 2;
|
||||
wavFormat.sampleRate = 44100;
|
||||
wavFormat.bitsPerSample = 32;
|
||||
|
||||
drwav wav;
|
||||
if (drwav_init_file_write(&wav, "output.wav", &wavFormat) == DRWAV_FALSE) {
|
||||
printf("Failed to initialize output file.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
mal_backend backend = mal_backend_wasapi;
|
||||
|
||||
@@ -43,16 +63,18 @@ int main(int argc, char** argv)
|
||||
}
|
||||
|
||||
mal_device_config deviceConfig = mal_device_config_init(mal_device_type_duplex);
|
||||
deviceConfig.pPlaybackDeviceID = NULL;
|
||||
deviceConfig.format = mal_format_f32;
|
||||
deviceConfig.channels = 2;
|
||||
deviceConfig.capture.pDeviceID = NULL;
|
||||
deviceConfig.capture.format = mal_format_f32;
|
||||
deviceConfig.capture.channels = 2;
|
||||
deviceConfig.playback.pDeviceID = NULL;
|
||||
deviceConfig.playback.format = mal_format_f32;
|
||||
deviceConfig.playback.channels = 2;
|
||||
deviceConfig.sampleRate = 44100;
|
||||
deviceConfig.bufferSizeInMilliseconds = 50;
|
||||
deviceConfig.bufferSizeInMilliseconds = 1000;
|
||||
deviceConfig.periods = 3;
|
||||
deviceConfig.shareMode = mal_share_mode_shared;
|
||||
deviceConfig.dataCallback = data_callback;
|
||||
deviceConfig.stopCallback = stop_callback;
|
||||
deviceConfig.pUserData = NULL;
|
||||
deviceConfig.pUserData = &wav;
|
||||
|
||||
mal_device device;
|
||||
result = mal_device_init(&context, &deviceConfig, &device);
|
||||
@@ -66,5 +88,9 @@ int main(int argc, char** argv)
|
||||
getchar();
|
||||
|
||||
mal_device_uninit(&device);
|
||||
drwav_uninit(&wav);
|
||||
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
return 0;
|
||||
}
|
||||
+13
-12
@@ -272,11 +272,11 @@
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\examples\simple_capture.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">false</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">false</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\examples\simple_enumeration.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
@@ -319,7 +319,7 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="mal_duplex.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</ExcludedFromBuild>
|
||||
@@ -359,12 +359,12 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="mal_test_0.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">false</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">false</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="mal_test_0.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
@@ -386,6 +386,7 @@
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\mini_al.h" />
|
||||
<ClInclude Include="..\research\mal_resampler.h" />
|
||||
<ClInclude Include="..\research\mal_ring_buffer.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
|
||||
@@ -68,5 +68,8 @@
|
||||
<ClInclude Include="..\research\mal_resampler.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\research\mal_ring_buffer.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user