Change mic capture API to return SoundData stream instead of individual chunks

This commit is contained in:
Nevyn Bengtsson 2020-12-16 16:07:29 +01:00 committed by Bjorn
parent 044a5a47d9
commit 5fcaad9bde
6 changed files with 91 additions and 117 deletions

View File

@ -12,12 +12,6 @@ StringEntry lovrAudioType[] = {
{ 0 }
};
StringEntry lovrTimeUnit[] = {
[UNIT_SECONDS] = ENTRY("seconds"),
[UNIT_SAMPLES] = ENTRY("samples"),
{ 0 }
};
static int l_lovrAudioReset(lua_State* L) {
lovrAudioReset();
return 0;
@ -86,37 +80,9 @@ static int l_lovrAudioSetListenerPose(lua_State *L) {
return 0;
}
static int l_lovrAudioGetCaptureDuration(lua_State *L) {
TimeUnit units = luax_checkenum(L, 1, TimeUnit, "seconds");
size_t sampleCount = lovrAudioGetCaptureSampleCount();
if (units == UNIT_SECONDS) {
lua_pushnumber(L, lovrAudioConvertToSeconds(sampleCount, AUDIO_CAPTURE));
} else {
lua_pushinteger(L, sampleCount);
}
return 1;
}
static int l_lovrAudioCapture(lua_State* L) {
int index = 1;
size_t samples = lua_type(L, index) == LUA_TNUMBER ? lua_tointeger(L, index++) : lovrAudioGetCaptureSampleCount();
if (samples == 0) {
return 0;
}
SoundData* soundData = luax_totype(L, index++, SoundData);
size_t offset = soundData ? luaL_optinteger(L, index, 0) : 0;
if (soundData) {
lovrRetain(soundData);
}
soundData = lovrAudioCapture(samples, soundData, offset);
static int l_lovrAudioGetCaptureStream(lua_State* L) {
SoundData* soundData = lovrAudioGetCaptureStream();
luax_pushtype(L, SoundData, soundData);
lovrRelease(SoundData, soundData);
return 1;
}
@ -165,8 +131,7 @@ static const luaL_Reg lovrAudio[] = {
{ "setVolume", l_lovrAudioSetVolume },
{ "newSource", l_lovrAudioNewSource },
{ "setListenerPose", l_lovrAudioSetListenerPose },
{ "capture", l_lovrAudioCapture },
{ "getCaptureDuration", l_lovrAudioGetCaptureDuration },
{ "getCaptureStream", l_lovrAudioGetCaptureStream },
{ "getDevices", l_lovrAudioGetDevices },
{ "useDevice", l_lovrUseDevice },
{ NULL, NULL }

View File

@ -1,6 +1,14 @@
#include "api.h"
#include "data/soundData.h"
#include "data/blob.h"
#include "core/ref.h"
#include <stdlib.h>
StringEntry lovrTimeUnit[] = {
[UNIT_SECONDS] = ENTRY("seconds"),
[UNIT_SAMPLES] = ENTRY("samples"),
{ 0 }
};
static int l_lovrSoundDataGetBlob(lua_State* L) {
SoundData* soundData = luax_checktype(L, 1, SoundData);
@ -9,6 +17,51 @@ static int l_lovrSoundDataGetBlob(lua_State* L) {
return 1;
}
static int l_lovrSoundDataGetDuration(lua_State* L) {
SoundData* soundData = luax_checktype(L, 1, SoundData);
TimeUnit units = luax_checkenum(L, 2, TimeUnit, "seconds");
uint32_t frames = lovrSoundDataGetDuration(soundData);
if (units == UNIT_SECONDS) {
lua_pushnumber(L, (double) frames / soundData->sampleRate);
} else {
lua_pushinteger(L, frames);
}
return 1;
}
static const char *format2string(SampleFormat f) { return f == SAMPLE_I16 ? "i16" : "f32"; }
// soundData:read(dest, {size}, {offset}) -> framesRead
// soundData:read({size}) -> framesRead
static int l_lovrSoundDataRead(lua_State* L) {
//struct SoundData* soundData, uint32_t offset, uint32_t count, void* data
SoundData* source = luax_checktype(L, 1, SoundData);
int index = 2;
SoundData* dest = luax_totype(L, index, SoundData);
size_t frameCount = lua_type(L, index) == LUA_TNUMBER ? lua_tointeger(L, index++) : lovrSoundDataGetDuration(source);
size_t offset = dest ? luaL_optinteger(L, index, 0) : 0;
bool shouldRelease = false;
if (dest == NULL) {
dest = lovrSoundDataCreateRaw(frameCount, source->channels, source->sampleRate, source->format, NULL);
shouldRelease = true;
} else {
lovrAssert(dest->channels == source->channels, "Source (%d) and destination (%d) channel counts must match", source->channels, dest->channels);
lovrAssert(dest->sampleRate == source->sampleRate, "Source (%d) and destination (%d) sample rates must match", source->sampleRate, dest->sampleRate);
lovrAssert(dest->format == source->format, "Source (%s) and destination (%s) formats must match", format2string(source->format), format2string(dest->format));
lovrAssert(offset + frameCount <= dest->frames, "Tried to write samples past the end of a SoundData buffer");
lovrAssert(dest->blob, "Can't read into a stream destination");
}
size_t outFrames = source->read(source, offset, frameCount, dest->blob->data);
dest->frames = outFrames;
dest->blob->size = outFrames * SampleFormatBytesPerFrame(dest->channels, dest->format);
luax_pushtype(L, SoundData, dest);
if (shouldRelease) lovrRelease(SoundData, dest);
return 1;
}
static int l_lovrSoundDataAppend(lua_State* L) {
SoundData* soundData = luax_checktype(L, 1, SoundData);
Blob* blob = luax_totype(L, 2, Blob);
@ -35,6 +88,8 @@ static int l_lovrSoundDataSetSample(lua_State* L) {
const luaL_Reg lovrSoundData[] = {
{ "getBlob", l_lovrSoundDataGetBlob },
{ "getDuration", l_lovrSoundDataGetDuration },
{ "read", l_lovrSoundDataRead },
{ "append", l_lovrSoundDataAppend },
{ "setSample", l_lovrSoundDataSetSample },
{ NULL, NULL }

View File

@ -42,7 +42,7 @@ static struct {
ma_device devices[AUDIO_TYPE_COUNT];
ma_mutex playbackLock;
Source* sources;
ma_pcm_rb captureRingbuffer;
SoundData *captureStream;
arr_t(ma_data_converter*) converters;
Spatializer* spatializer;
@ -115,23 +115,9 @@ static void onPlayback(ma_device* device, void* output, const void* _, uint32_t
}
static void onCapture(ma_device* device, void* output, const void* input, uint32_t frames) {
// note: ma_pcm_rb is lockless
void *store;
// note: uses ma_pcm_rb which is lockless
size_t bytesPerFrame = SampleFormatBytesPerFrame(CAPTURE_CHANNELS, OUTPUT_FORMAT);
while(frames > 0) {
uint32_t availableFrames = frames;
ma_result acquire_status = ma_pcm_rb_acquire_write(&state.captureRingbuffer, &availableFrames, &store);
if (acquire_status != MA_SUCCESS) {
return;
}
memcpy(store, input, availableFrames * bytesPerFrame);
ma_result commit_status = ma_pcm_rb_commit_write(&state.captureRingbuffer, availableFrames, store);
if (commit_status != MA_SUCCESS || availableFrames == 0) {
return;
}
frames -= availableFrames;
input += availableFrames * bytesPerFrame;
}
lovrSoundDataStreamAppendBuffer(state.captureStream, input, frames*bytesPerFrame);
}
static const ma_device_callback_proc callbacks[] = { onPlayback, onCapture };
@ -167,12 +153,6 @@ bool lovrAudioInit(AudioConfig config[2]) {
}
}
ma_result rbstatus = ma_pcm_rb_init(miniAudioFormatFromLovr[OUTPUT_FORMAT], CAPTURE_CHANNELS, state.config[AUDIO_CAPTURE].sampleRate * 1.0, NULL, NULL, &state.captureRingbuffer);
if (rbstatus != MA_SUCCESS) {
lovrAudioDestroy();
return false;
}
for (size_t i = 0; i < sizeof(spatializers) / sizeof(spatializers[0]); i++) {
if (spatializers[i]->init()) {
state.spatializer = spatializers[i];
@ -192,6 +172,7 @@ void lovrAudioDestroy() {
ma_device_uninit(&state.devices[AUDIO_CAPTURE]);
ma_mutex_uninit(&state.playbackLock);
ma_context_uninit(&state.context);
lovrRelease(SoundData, state.captureStream);
if (state.spatializer) state.spatializer->destroy();
for(int i = 0; i < state.converters.length; i++) {
ma_data_converter_uninit(state.converters.data[i]);
@ -221,6 +202,17 @@ bool lovrAudioInitDevice(AudioType type) {
lovrLog(LOG_WARN, "audio", "Failed to enable audio device %d: %d\n", type, err);
return false;
}
if (type == AUDIO_CAPTURE) {
lovrRelease(SoundData, state.captureStream);
state.captureStream = lovrSoundDataCreateStream(state.config[type].sampleRate * 1.0, CAPTURE_CHANNELS, state.config[type].sampleRate, OUTPUT_FORMAT);
if (!state.captureStream) {
lovrLog(LOG_WARN, "audio", "Failed to init audio device %d\n", type);
lovrAudioDestroy();
return false;
}
}
return true;
}
@ -406,55 +398,12 @@ SoundData* lovrSourceGetSoundData(Source* source) {
// Capture
uint32_t lovrAudioGetCaptureSampleCount() {
// note: must only be called from ONE thread!! ma_pcm_rb only promises
// thread safety with ONE reader and ONE writer thread.
return ma_pcm_rb_available_read(&state.captureRingbuffer);
struct SoundData* lovrAudioGetCaptureStream()
{
return state.captureStream;
}
static const char *format2string(SampleFormat f) { return f == SAMPLE_I16 ? "i16" : "f32"; }
struct SoundData* lovrAudioCapture(uint32_t frameCount, SoundData *soundData, uint32_t offset) {
uint32_t bufferedFrames = lovrAudioGetCaptureSampleCount();
if (frameCount == 0 || frameCount > bufferedFrames) {
frameCount = bufferedFrames;
}
if (frameCount == 0) {
return NULL;
}
if (soundData == NULL) {
soundData = lovrSoundDataCreateRaw(frameCount, CAPTURE_CHANNELS, state.config[AUDIO_CAPTURE].sampleRate, state.config[AUDIO_CAPTURE].format, NULL);
} else {
lovrAssert(soundData->channels == CAPTURE_CHANNELS, "Capture (%d) and SoundData (%d) channel counts must match", CAPTURE_CHANNELS, soundData->channels);
lovrAssert(soundData->sampleRate == state.config[AUDIO_CAPTURE].sampleRate, "Capture (%d) and SoundData (%d) sample rates must match", state.config[AUDIO_CAPTURE].sampleRate, soundData->sampleRate);
lovrAssert(soundData->format == state.config[AUDIO_CAPTURE].format, "Capture (%s) and SoundData (%s) formats must match", format2string(state.config[AUDIO_CAPTURE].format), format2string(soundData->format));
lovrAssert(offset + frameCount <= soundData->frames, "Tried to write samples past the end of a SoundData buffer");
}
uint32_t bytesPerFrame = SampleFormatBytesPerFrame(CAPTURE_CHANNELS, state.config[AUDIO_CAPTURE].format);
while(frameCount > 0) {
uint32_t availableFramesInRB = frameCount;
void *store;
ma_result acquire_status = ma_pcm_rb_acquire_read(&state.captureRingbuffer, &availableFramesInRB, &store);
if (acquire_status != MA_SUCCESS) {
lovrAssert(false, "Failed to acquire ring buffer for read: %d\n", acquire_status);
return NULL;
}
memcpy(soundData->blob->data + offset * bytesPerFrame, store, availableFramesInRB * bytesPerFrame);
ma_result commit_status = ma_pcm_rb_commit_read(&state.captureRingbuffer, availableFramesInRB, store);
if (commit_status != MA_SUCCESS) {
lovrAssert(false, "Failed to commit ring buffer for read: %d\n", acquire_status);
return NULL;
}
frameCount -= availableFramesInRB;
offset += availableFramesInRB;
}
return soundData;
}
// Devices
void lovrAudioGetDevices(AudioDevice **outDevices, size_t *outCount) {
if(state.deviceInfos)

View File

@ -21,11 +21,6 @@ typedef enum {
SOURCE_STREAM
} SourceType;
typedef enum {
UNIT_SECONDS,
UNIT_SAMPLES
} TimeUnit;
typedef void* AudioDeviceIdentifier;
typedef struct {
@ -70,8 +65,7 @@ uint32_t lovrSourceGetTime(Source* source);
void lovrSourceSetTime(Source* source, uint32_t sample);
struct SoundData* lovrSourceGetSoundData(Source* source);
uint32_t lovrAudioGetCaptureSampleCount();
struct SoundData* lovrAudioCapture(uint32_t sampleCount, struct SoundData *soundData, uint32_t offset);
struct SoundData* lovrAudioGetCaptureStream();
void lovrAudioGetDevices(AudioDevice **outDevices, size_t *outCount);
void lovrAudioUseDevice(AudioDeviceIdentifier identifier, int sampleRate, SampleFormat format);

View File

@ -158,18 +158,23 @@ SoundData* lovrSoundDataCreateFromFile(struct Blob* blob, bool decode) {
}
size_t lovrSoundDataStreamAppendBlob(SoundData *dest, struct Blob* blob) {
lovrSoundDataStreamAppendBuffer(dest, blob->data, blob->size);
}
size_t lovrSoundDataStreamAppendBuffer(SoundData *dest, const void *buf, size_t byteSize) {
lovrAssert(dest->ring, "Data can only be appended to a SoundData stream");
const uint8_t *charBuf = (const uint8_t*)buf;
void *store;
size_t blobOffset = 0;
size_t bytesPerFrame = SampleFormatBytesPerFrame(dest->channels, dest->format);
size_t frameCount = blob->size / bytesPerFrame;
size_t frameCount = byteSize / bytesPerFrame;
size_t framesAppended = 0;
while(frameCount > 0) {
uint32_t availableFrames = frameCount;
ma_result acquire_status = ma_pcm_rb_acquire_write(dest->ring, &availableFrames, &store);
lovrAssert(acquire_status == MA_SUCCESS, "Failed to acquire ring buffer");
memcpy(store, blob->data + blobOffset, availableFrames * bytesPerFrame);
memcpy(store, charBuf + blobOffset, availableFrames * bytesPerFrame);
ma_result commit_status = ma_pcm_rb_commit_write(dest->ring, availableFrames, store);
lovrAssert(commit_status == MA_SUCCESS, "Failed to commit to ring buffer");
if (availableFrames == 0) {

View File

@ -16,6 +16,11 @@ typedef enum {
SAMPLE_INVALID
} SampleFormat;
typedef enum {
UNIT_SECONDS,
UNIT_SAMPLES
} TimeUnit;
size_t SampleFormatBytesPerFrame(int channelCount, SampleFormat fmt);
typedef struct SoundData {
@ -35,6 +40,7 @@ SoundData* lovrSoundDataCreateStream(uint32_t bufferSizeInFrames, uint32_t chann
SoundData* lovrSoundDataCreateFromFile(struct Blob* blob, bool decode);
// returns the number of frames successfully appended (if it's less than the size of blob, the internal ring buffer is full)
size_t lovrSoundDataStreamAppendBuffer(SoundData *dest, const void *buf, size_t byteSize);
size_t lovrSoundDataStreamAppendBlob(SoundData *dest, struct Blob* blob);
size_t lovrSoundDataStreamAppendSound(SoundData *dest, SoundData *src);
void lovrSoundDataSetSample(SoundData* soundData, size_t index, float value);