From efb40105d657da935d3854e6ea7a513c6210224b Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sun, 4 Oct 2020 17:20:41 +0300 Subject: Working on audio playback Audio players are displayed the same way as images. When playing, a decoder runs in a background thread producing samples suitable for output. --- src/audio/player.c | 337 +++++++++++++++++++++++++++++++++++++++++++++++------ src/audio/player.h | 5 +- 2 files changed, 306 insertions(+), 36 deletions(-) (limited to 'src/audio') diff --git a/src/audio/player.c b/src/audio/player.c index aea2a998..1dd3c38a 100644 --- a/src/audio/player.c +++ b/src/audio/player.c @@ -22,12 +22,122 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "player.h" +#include #include #include +iDeclareType(InputBuf) + +struct Impl_InputBuf { + iMutex mtx; + iCondition changed; + iBlock data; + iBool isComplete; +}; + +void init_InputBuf(iInputBuf *d) { + init_Mutex(&d->mtx); + init_Condition(&d->changed); + init_Block(&d->data, 0); + d->isComplete = iTrue; +} + +void deinit_InputBuf(iInputBuf *d) { + deinit_Block(&d->data); + deinit_Condition(&d->changed); + deinit_Mutex(&d->mtx); +} + +size_t size_InputBuf(const iInputBuf *d) { + return size_Block(&d->data); +} + +iDefineTypeConstruction(InputBuf) + +/*----------------------------------------------------------------------------------------------*/ + +iDeclareType(SampleBuf) + +struct Impl_SampleBuf { + void * data; + size_t sampleSize; + size_t count; + size_t head, tail; +}; + +void init_SampleBuf(iSampleBuf *d, size_t sampleSize, size_t count) { + d->sampleSize = sampleSize; + d->count = count + 1; /* considered empty if head==tail */ + d->data = malloc(d->sampleSize * d->count); + d->head = 0; + d->tail = 0; +} + +void deinit_SampleBuf(iSampleBuf *d) { + free(d->data); +} + +size_t size_SampleBuf(const iSampleBuf *d) { + return d->head - d->tail; +} + +size_t vacancy_SampleBuf(const iSampleBuf *d) { + return d->count - size_SampleBuf(d) - 1; +} + +iBool isFull_SampleBuf(const iSampleBuf *d) { + return vacancy_SampleBuf(d) == 0; +} + +iLocalDef void *ptr_SampleBuf_(iSampleBuf *d, size_t pos) { + return ((char *) d->data) + (d->sampleSize * pos); +} + +void write_SampleBuf(iSampleBuf *d, const void *samples, const size_t n) { + iAssert(n <= vacancy_SampleBuf(d)); + const size_t headPos = d->head % d->count; + const size_t avail = d->count - headPos; + if (n > avail) { + const char *in = samples; + memcpy(ptr_SampleBuf_(d, headPos), in, d->sampleSize * avail); + in += d->sampleSize * avail; + memcpy(ptr_SampleBuf_(d, 0), in, d->sampleSize * (n - avail)); + } + else { + memcpy(ptr_SampleBuf_(d, headPos), samples, d->sampleSize * n); + } + d->head += n; +} + +void read_SampleBuf(iSampleBuf *d, const size_t n, void *samples_out) { + iAssert(n <= size_SampleBuf(d)); + const size_t tailPos = d->tail % d->count; + const size_t avail = d->count - tailPos; + if (n > avail) { + char *out = samples_out; + memcpy(out, ptr_SampleBuf_(d, tailPos), d->sampleSize * avail); + out += d->sampleSize * avail; + memcpy(out, ptr_SampleBuf_(d, 0), d->sampleSize * (n - avail)); + } + else { + memcpy(samples_out, ptr_SampleBuf_(d, tailPos), d->sampleSize * n); + } + d->tail += n; +} + +/*----------------------------------------------------------------------------------------------*/ + +iDeclareType(ContentSpec) + +struct Impl_ContentSpec { + SDL_AudioSpec spec; + iRanges wavData; +}; + iDeclareType(Decoder) enum iDecoderType { + none_DecoderType, wav_DecoderType, mpeg_DecoderType, vorbis_DecoderType, @@ -36,29 +146,189 @@ enum iDecoderType { struct Impl_Decoder { enum iDecoderType type; - size_t inPos; -// iBlock samples; + iThread * thread; + iInputBuf * input; + size_t inputPos; + iSampleBuf output; + iMutex mtx; /* for output */ + iRanges wavData; }; +static void parseWav_Decoder_(iDecoder *d, iRanges inputRange) { + lock_Mutex(&d->mtx); + const size_t vacancy = vacancy_SampleBuf(&d->output); + const size_t avail = iMin(inputRange.end - d->inputPos, d->wavData.end - d->inputPos) / + d->output.sampleSize; + const size_t n = iMin(vacancy, avail); + iGuardMutex(&d->input->mtx, /* lock so input array isn't reallocated during the copy */ + write_SampleBuf(&d->output, constData_Block(&d->input->data) + d->inputPos, n)); + d->inputPos += n; + unlock_Mutex(&d->mtx); +} + +static iThreadResult run_Decoder_(iThread *thread) { + iDecoder *d = userData_Thread(thread); + while (d->type) { + size_t inputSize = 0; + /* Grab more input. */ { + lock_Mutex(&d->input->mtx); + wait_Condition(&d->input->changed, &d->input->mtx); + inputSize = size_Block(&d->input->data); + unlock_Mutex(&d->input->mtx); + } + iRanges inputRange = { d->inputPos, inputSize }; + iAssert(inputRange.start <= inputRange.end); + if (!d->type) break; + /* Have data to work on and a place to save output? */ + if (!isEmpty_Range(&inputRange) && !isFull_SampleBuf(&d->output)) { + switch (d->type) { + case wav_DecoderType: + parseWav_Decoder_(d, inputRange); + break; + default: + break; + } + } + } + return 0; +} + +void init_Decoder(iDecoder *d, iInputBuf *input, const iContentSpec *spec) { + d->type = wav_DecoderType; + d->input = input; + d->inputPos = spec->wavData.start; + init_SampleBuf(&d->output, SDL_AUDIO_BITSIZE(spec->spec.format) / 8 * + spec->spec.channels, spec->spec.samples * 2); + init_Mutex(&d->mtx); + d->thread = new_Thread(run_Decoder_); + setUserData_Thread(d->thread, d); + start_Thread(d->thread); +} + +void deinit_Decoder(iDecoder *d) { + d->type = none_DecoderType; + signal_Condition(&d->input->changed); + join_Thread(d->thread); + iRelease(d->thread); + deinit_Mutex(&d->mtx); + deinit_SampleBuf(&d->output); +} + +iDefineTypeConstructionArgs(Decoder, (iInputBuf *input, const iContentSpec *spec), + input, spec) + +/*----------------------------------------------------------------------------------------------*/ + struct Impl_Player { SDL_AudioSpec spec; SDL_AudioDeviceID device; - iMutex mtx; - iBlock data; - iBool isDataComplete; + iInputBuf *data; + iDecoder *decoder; }; +iDefineTypeConstruction(Player) + +static size_t sampleSize_Player_(const iPlayer *d) { + return d->spec.channels * SDL_AUDIO_BITSIZE(d->spec.format) / 8; +} + +static int silence_Player_(const iPlayer *d) { + return d->spec.silence; +} + +static iContentSpec contentSpec_Player_(const iPlayer *d) { + iContentSpec content; + iZap(content); + const size_t dataSize = size_InputBuf(d->data); + iBuffer *buf = iClob(new_Buffer()); + open_Buffer(buf, &d->data->data); + enum iDecoderType decType = wav_DecoderType; /* TODO: from MIME */ + if (decType == wav_DecoderType && dataSize >= 44) { + /* Read the RIFF/WAVE header. */ + iStream *is = stream_Buffer(buf); + char magic[4]; + readData_Buffer(buf, 4, magic); + if (memcmp(magic, "RIFF", 4)) { + /* Not WAV. */ + return content; + } + readU32_Stream(is); /* file size */ + readData_Buffer(buf, 4, magic); + if (memcmp(magic, "WAVE", 4)) { + /* Not WAV. */ + return content; + } + /* Read all the chunks. */ + while (!atEnd_Buffer(buf)) { + readData_Buffer(buf, 4, magic); + const size_t size = read32_Stream(is); + if (memcmp(magic, "fmt ", 4) == 0) { + if (size != 16) { + return content; + } + const int16_t mode = read16_Stream(is); /* 1 = PCM */ + const int16_t numChannels = read16_Stream(is); + const int32_t freq = read32_Stream(is); + const uint32_t bytesPerSecond = readU32_Stream(is); + const int16_t blockAlign = read16_Stream(is); + const int16_t bitsPerSample = read16_Stream(is); + iUnused(bytesPerSecond); + iUnused(blockAlign); /* TODO: Should use this one when reading samples? */ + if (mode != 1) { /* PCM */ + return content; + } + if (numChannels != 1 && numChannels != 2) { + return content; + } + if (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 32) { + return content; + } + content.spec.freq = freq; + content.spec.channels = numChannels; + content.spec.format = + (bitsPerSample == 8 ? AUDIO_S8 : bitsPerSample == 16 ? AUDIO_S16 : AUDIO_S32); + } + else if (memcmp(magic, "data", 4) == 0) { + size_t len = read32_Stream(is); /* data size */ + content.wavData = (iRanges){ pos_Stream(is), pos_Stream(is) + len }; + break; + } + else { + seek_Stream(is, pos_Stream(is) + size); + } + } + } + content.spec.samples = 2048; + return content; +} + +static void writeOutputSamples_Player_(void *plr, Uint8 *stream, int len) { + iPlayer *d = plr; + iAssert(d->decoder); + const size_t sampleSize = sampleSize_Player_(d); + const size_t count = len / sampleSize; + lock_Mutex(&d->decoder->mtx); + if (size_SampleBuf(&d->decoder->output) >= count) { + read_SampleBuf(&d->decoder->output, count, stream); + } + else { + memset(stream, 0, len); /* TODO: If unsigned samples, don't use zero! */ + } + unlock_Mutex(&d->decoder->mtx); + /* Wake up decoder; there is more room for output. */ + signal_Condition(&d->data->changed); +} + void init_Player(iPlayer *d) { iZap(d->spec); - d->device = 0; - init_Mutex(&d->mtx); - init_Block(&d->data, 0); - d->isDataComplete = iFalse; + d->device = 0; + d->decoder = NULL; + d->data = new_InputBuf(); } void deinit_Player(iPlayer *d) { stop_Player(d); - deinit_Block(&d->data); + delete_InputBuf(d->data); } iBool isStarted_Player(const iPlayer *d) { @@ -70,55 +340,54 @@ void setFormatHint_Player(iPlayer *d, const char *hint) { } void updateSourceData_Player(iPlayer *d, const iBlock *data, enum iPlayerUpdate update) { - lock_Mutex(&d->mtx); + iInputBuf *inp = d->data; + lock_Mutex(&inp->mtx); switch (update) { case replace_PlayerUpdate: - set_Block(&d->data, data); - d->isDataComplete = iFalse; + set_Block(&inp->data, data); + inp->isComplete = iFalse; break; case append_PlayerUpdate: - append_Block(&d->data, data); - d->isDataComplete = iFalse; + append_Block(&inp->data, data); + inp->isComplete = iFalse; break; case complete_PlayerUpdate: - d->isDataComplete = iTrue; + inp->isComplete = iTrue; break; } - unlock_Mutex(&d->mtx); -} - -static void writeOutputSamples_Player_(void *plr, Uint8 *stream, int len) { - iPlayer *d = plr; - memset(stream, 0, len); - /* TODO: Copy samples from the decoder's ring buffer. */ + signal_Condition(&inp->changed); + unlock_Mutex(&inp->mtx); } iBool start_Player(iPlayer *d) { if (isStarted_Player(d)) { return iFalse; } - SDL_AudioSpec conf; - iZap(conf); - conf.freq = 44100; /* TODO: from content */ - conf.format = AUDIO_S16; - conf.channels = 2; /* TODO: from content */ - conf.samples = 2048; - conf.callback = writeOutputSamples_Player_; - conf.userdata = d; - d->device = SDL_OpenAudioDevice(NULL, SDL_FALSE /* playback */, &conf, &d->spec, 0); + iContentSpec content = contentSpec_Player_(d); + content.spec.callback = writeOutputSamples_Player_; + content.spec.userdata = d; + d->device = SDL_OpenAudioDevice(NULL, SDL_FALSE /* playback */, &content.spec, &d->spec, 0); if (!d->device) { return iFalse; } - /* TODO: Start the stream/decoder thread. */ - /* TODO: Audio device is unpaused when there are samples ready to play. */ + d->decoder = new_Decoder(d->data, &content); + SDL_PauseAudioDevice(d->device, SDL_FALSE); return iTrue; } +void setPaused_Player(iPlayer *d, iBool isPaused) { + if (isStarted_Player(d)) { + SDL_PauseAudioDevice(d->device, isPaused ? SDL_TRUE : SDL_FALSE); + } +} + void stop_Player(iPlayer *d) { if (isStarted_Player(d)) { /* TODO: Stop the stream/decoder. */ SDL_PauseAudioDevice(d->device, SDL_TRUE); SDL_CloseAudioDevice(d->device); d->device = 0; + delete_Decoder(d->decoder); + d->decoder = NULL; } } diff --git a/src/audio/player.h b/src/audio/player.h index 121659f2..2138c556 100644 --- a/src/audio/player.h +++ b/src/audio/player.h @@ -36,7 +36,8 @@ enum iPlayerUpdate { void setFormatHint_Player (iPlayer *, const char *hint); void updateSourceData_Player (iPlayer *, const iBlock *data, enum iPlayerUpdate update); -iBool start_Player (iPlayer *); -void stop_Player (iPlayer *); +iBool start_Player (iPlayer *); +void setPaused_Player (iPlayer *, iBool isPaused); +void stop_Player (iPlayer *); iBool isStarted_Player (const iPlayer *); -- cgit v1.2.3