/* Copyright 2020 Jaakko Keränen Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "player.h" #include "defs.h" #include "buf.h" #include "lang.h" #include "../app.h" #include #define STB_VORBIS_HEADER_ONLY #include "stb_vorbis.c" #include #include #include #include #include #include #if defined (LAGRANGE_ENABLE_MPG123) # include #endif #if defined (LAGRANGE_ENABLE_OPUS) # include #endif #if defined (iPlatformAppleMobile) # include "platform/ios.h" #endif #if defined (iPlatformAndroidMobile) # include "platform/android.h" #endif /*----------------------------------------------------------------------------------------------*/ iDeclareType(AVFAudioPlayer) /* iOS */ iDeclareType(AndroidAudioPlayer) /* Android */ iDeclareType(ContentSpec) enum iDecoderType { none_DecoderType, wav_DecoderType, vorbis_DecoderType, mpeg_DecoderType, midi_DecoderType, opus_DecoderType, }; struct Impl_ContentSpec { enum iDecoderType type; SDL_AudioFormat inputFormat; SDL_AudioSpec output; size_t totalInputSize; uint64_t totalSamples; size_t inputStartPos; }; iDeclareType(Decoder) struct Impl_Decoder { enum iDecoderType type; float gain; iThread * thread; SDL_AudioFormat inputFormat; iInputBuf * input; size_t inputPos; size_t totalInputSize; unsigned int outputFreq; iSampleBuf output; iMutex outputMutex; iArray pendingOutput; uint64_t currentSample; uint64_t totalSamples; /* zero if unknown */ iBool isDone; /* set when all input consumed; output may still be draining */ iMutex tagMutex; iString tags[max_PlayerTag]; stb_vorbis * vorbis; #if defined (LAGRANGE_ENABLE_MPG123) mpg123_handle * mpeg; mpg123_id3v1 * id3v1; mpg123_id3v2 * id3v2; #endif #if defined (LAGRANGE_ENABLE_OPUS) OggOpusFile * opus; OpusFileCallbacks opusCallbacks; size_t opusLastInputSize; #endif }; enum iDecoderStatus { ok_DecoderStatus, needMoreInput_DecoderStatus, }; static enum iDecoderStatus decodeWav_Decoder_(iDecoder *d, iRanges inputRange) { const uint8_t numChannels = d->output.numChannels; const size_t inputSampleSize = numChannels * SDL_AUDIO_BITSIZE(d->inputFormat) / 8; const size_t vacancy = vacancy_SampleBuf(&d->output); const size_t inputBytePos = inputSampleSize * d->inputPos; const size_t avail = (inputRange.end - inputBytePos) / inputSampleSize; if (avail == 0) { return needMoreInput_DecoderStatus; } const size_t n = iMin(vacancy, avail); if (n == 0) { return ok_DecoderStatus; } void *samples = malloc(inputSampleSize * n); /* Get a copy of the input for further processing. */ { lock_Mutex(&d->input->mtx); iAssert(inputSampleSize * d->inputPos < size_Block(&d->input->data)); memcpy(samples, constData_Block(&d->input->data) + inputSampleSize * d->inputPos, inputSampleSize * n); d->inputPos += n; unlock_Mutex(&d->input->mtx); } /* Gain. */ { const float gain = d->gain; if (d->inputFormat == AUDIO_F64LSB) { iAssert(d->output.format == AUDIO_F32); double *inValue = samples; float * outValue = samples; for (size_t count = numChannels * n; count; count--) { *outValue++ = gain * *inValue++; } } else if (d->inputFormat == AUDIO_F32) { float *value = samples; for (size_t count = numChannels * n; count; count--, value++) { *value *= gain; } } else if (d->inputFormat == AUDIO_S24LSB) { iAssert(d->output.format == AUDIO_S16); const char *inValue = samples; int16_t * outValue = samples; for (size_t count = numChannels * n; count; count--, inValue += 3, outValue++) { memcpy(outValue, inValue, 2); *outValue *= gain; } } else { switch (SDL_AUDIO_BITSIZE(d->output.format)) { case 8: { uint8_t *value = samples; for (size_t count = numChannels * n; count; count--, value++) { *value = (int) (*value - 127) * gain + 127; } break; } case 16: { int16_t *value = samples; for (size_t count = numChannels * n; count; count--, value++) { *value *= gain; } break; } case 32: { int32_t *value = samples; for (size_t count = numChannels * n; count; count--, value++) { *value *= gain; } break; } } } } iGuardMutex(&d->outputMutex, write_SampleBuf(&d->output, samples, n)); d->currentSample += n; free(samples); return ok_DecoderStatus; } static void writePending_Decoder_(iDecoder *d) { /* Write as much as we can. */ lock_Mutex(&d->outputMutex); size_t avail = vacancy_SampleBuf(&d->output); size_t n = iMin(avail, size_Array(&d->pendingOutput)); write_SampleBuf(&d->output, constData_Array(&d->pendingOutput), n); removeN_Array(&d->pendingOutput, 0, n); unlock_Mutex(&d->outputMutex); d->currentSample += n; } static enum iDecoderStatus decodeVorbis_Decoder_(iDecoder *d) { const iBlock *input = &d->input->data; if (!d->vorbis) { lock_Mutex(&d->input->mtx); int error; int consumed; d->vorbis = stb_vorbis_open_pushdata( constData_Block(input), (int) size_Block(input), &consumed, &error, NULL); if (!d->vorbis) { return needMoreInput_DecoderStatus; } d->inputPos += consumed; unlock_Mutex(&d->input->mtx); /* Check the metadata. */ { const stb_vorbis_comment com = stb_vorbis_get_comment(d->vorbis); // printf("vendor: {%s}\n", comment.vendor); lock_Mutex(&d->tagMutex); for (int i = 0; i < com.comment_list_length; ++i) { const char *comStr = com.comment_list[i]; if (!iCmpStrN(comStr, "ARTIST=", 7)) { setCStr_String(&d->tags[artist_PlayerTag], comStr + 7); } else if (!iCmpStrN(comStr, "DATE=", 5)) { setCStr_String(&d->tags[date_PlayerTag], comStr + 5); } else if (!iCmpStrN(comStr, "TITLE=", 6)) { setCStr_String(&d->tags[title_PlayerTag], comStr + 6); } else if (!iCmpStrN(comStr, "GENRE=", 6)) { setCStr_String(&d->tags[genre_PlayerTag], comStr + 6); } } unlock_Mutex(&d->tagMutex); } } if (d->totalSamples == 0 && d->input->isComplete) { /* Time to check the stream size. */ lock_Mutex(&d->input->mtx); d->totalInputSize = size_Block(input); int error = 0; stb_vorbis *vrb = stb_vorbis_open_memory(constData_Block(input), (int) size_Block(input), &error, NULL); if (vrb) { d->totalSamples = stb_vorbis_stream_length_in_samples(vrb); stb_vorbis_close(vrb); } unlock_Mutex(&d->input->mtx); } enum iDecoderStatus status = ok_DecoderStatus; while (size_Array(&d->pendingOutput) < d->output.count) { /* Try to decode some input. */ lock_Mutex(&d->input->mtx); int count = 0; float **samples = NULL; int remaining = d->inputPos < size_Block(input) ? size_Block(input) - d->inputPos : 0; int consumed = stb_vorbis_decode_frame_pushdata( d->vorbis, constData_Block(input) + d->inputPos, remaining, NULL, &samples, &count); d->inputPos += consumed; iAssert(d->inputPos <= size_Block(input)); unlock_Mutex(&d->input->mtx); if (count == 0) { if (consumed == 0) { status = needMoreInput_DecoderStatus; break; } else continue; } /* Apply gain. */ { const float gain = d->gain; float sample[2]; for (size_t i = 0; i < (size_t) count; ++i) { for (size_t chan = 0; chan < d->output.numChannels; chan++) { sample[chan] = samples[chan][i] * gain; } pushBack_Array(&d->pendingOutput, sample); } } } writePending_Decoder_(d); return status; } #if defined (LAGRANGE_ENABLE_MPG123) static const char *mpegStr_(const mpg123_string *str) { return str ? str->p : ""; } #endif enum iDecoderStatus decodeMpeg_Decoder_(iDecoder *d) { enum iDecoderStatus status = ok_DecoderStatus; #if defined (LAGRANGE_ENABLE_MPG123) const iBlock *input = &d->input->data; if (!d->mpeg) { d->inputPos = 0; d->mpeg = mpg123_new(NULL, NULL); mpg123_format_none(d->mpeg); mpg123_format(d->mpeg, d->outputFreq, d->output.numChannels, MPG123_ENC_SIGNED_16); mpg123_open_feed(d->mpeg); } /* Feed more input. */ { lock_Mutex(&d->input->mtx); if (d->input->isComplete) { d->totalInputSize = size_Block(input); } if (d->inputPos < size_Block(input)) { mpg123_feed(d->mpeg, constData_Block(input) + d->inputPos, size_Block(input) - d->inputPos); if (d->inputPos == 0) { long r; int ch, enc; mpg123_getformat(d->mpeg, &r, &ch, &enc); iAssert(r == d->outputFreq); iAssert(ch == d->output.numChannels); iAssert(enc == MPG123_ENC_SIGNED_16); } d->inputPos = size_Block(input); } unlock_Mutex(&d->input->mtx); } while (size_Array(&d->pendingOutput) < d->output.count) { int16_t buffer[512]; size_t bytesRead = 0; const int rc = mpg123_read(d->mpeg, (uint8_t *) buffer, sizeof(buffer), &bytesRead); const float gain = d->gain; for (size_t i = 0; i < bytesRead / 2; i++) { buffer[i] *= gain; } pushBackN_Array(&d->pendingOutput, buffer, bytesRead / 2 / d->output.numChannels); if (rc == MPG123_NEED_MORE) { status = needMoreInput_DecoderStatus; break; } else if (rc == MPG123_DONE || bytesRead == 0) { break; } } if (!d->id3v1 &&!d->id3v2) { mpg123_id3(d->mpeg, &d->id3v1, &d->id3v2); /* TODO: These tags can change during decoding, so checking just once isn't quite right. Shouldn't check every time either, though... */ if (d->id3v2) { lock_Mutex(&d->tagMutex); setCStr_String(&d->tags[title_PlayerTag], mpegStr_(d->id3v2->title)); setCStr_String(&d->tags[artist_PlayerTag], mpegStr_(d->id3v2->artist)); setCStr_String(&d->tags[genre_PlayerTag], mpegStr_(d->id3v2->genre)); setCStr_String(&d->tags[date_PlayerTag], mpegStr_(d->id3v2->year)); unlock_Mutex(&d->tagMutex); } } /* Check if we know the total length already. This info should be available eventually. */ const off_t off = mpg123_length(d->mpeg); if (off > 0) { d->totalSamples = off; } writePending_Decoder_(d); #endif return status; } #if defined (LAGRANGE_ENABLE_OPUS) static int readOpus_(void *stream, unsigned char *ptr, int nbytes) { iDecoder *d = stream; const iBlock *input = &d->input->data; const size_t avail = size_Block(input) - d->inputPos; const size_t n = iMin(avail, nbytes); memcpy(ptr, constData_Block(input) + d->inputPos, n); d->inputPos += n; return n; } static int seekOpus_(void *stream, opus_int64 offset, int whence) { iDecoder *d = stream; const iBlock *input = &d->input->data; const size_t avail = size_Block(input); const size_t pos = d->inputPos; switch (whence) { case SEEK_SET: d->inputPos = offset; break; case SEEK_CUR: if (pos + offset > avail || PTRDIFF_MAX - offset < pos) { return -1; } d->inputPos += offset; break; case SEEK_END: if (avail <= pos + offset || PTRDIFF_MAX - pos < offset || -offset > pos) { return -1; } d->inputPos = size_Block(input) + offset; break; } return 0; } static opus_int64 tellOpus_(void *stream) { iDecoder *d = stream; return d->inputPos; } static const OpusFileCallbacks opusCallbacks_ = { readOpus_, seekOpus_, tellOpus_, NULL }; #endif static enum iDecoderStatus decodeOpus_Decoder_(iDecoder *d) { enum iDecoderStatus status = ok_DecoderStatus; #if defined (LAGRANGE_ENABLE_OPUS) const iBlock *input = &d->input->data; lock_Mutex(&d->input->mtx); if (!d->opus || d->opusLastInputSize != size_Block(input)) { ogg_int64_t lastRead = 0; if (d->opus) { lastRead = op_pcm_tell(d->opus); if (lastRead < 0) { unlock_Mutex(&d->input->mtx); return needMoreInput_DecoderStatus; } op_free(d->opus); } d->inputPos = 0; int error = 0; d->opusCallbacks = opusCallbacks_; d->opus = op_open_callbacks(d, &d->opusCallbacks, NULL, 0, &error); if (!d->opus) { unlock_Mutex(&d->input->mtx); return needMoreInput_DecoderStatus; } /* seek to the last position before we potentially re-opened the stream */ if (lastRead > 0) { int res = op_pcm_seek(d->opus, lastRead); if (res < 0) { unlock_Mutex(&d->input->mtx); return needMoreInput_DecoderStatus; } } } d->opusLastInputSize = size_Block(input); while (size_Array(&d->pendingOutput) < d->output.count) { float buffer[4096]; const int samplePerCh = op_read_float(d->opus, buffer, iElemCount(buffer), NULL); const float gain = d->gain; const int totalSamples = samplePerCh * d->output.numChannels; // assert (samplePerCh >= 0); for (size_t i = 0; i < totalSamples; i++) { buffer[i] *= gain; } pushBackN_Array(&d->pendingOutput, buffer, samplePerCh); if (totalSamples == 0) { status = needMoreInput_DecoderStatus; break; } } unlock_Mutex(&d->input->mtx); // Only check the length if we have the whole input if (d->input->isComplete) { const ogg_int64_t off = op_pcm_total(d->opus, -1); if (off > 0) { d->totalSamples = off; } } writePending_Decoder_(d); #endif return status; } static iThreadResult run_Decoder_(iThread *thread) { iDecoder *d = userData_Thread(thread); while (d->type) { /* Check amount of data available. */ lock_Mutex(&d->input->mtx); size_t inputSize = size_InputBuf(d->input); 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? */ enum iDecoderStatus status = ok_DecoderStatus; switch (d->type) { case wav_DecoderType: status = decodeWav_Decoder_(d, inputRange); break; case vorbis_DecoderType: status = decodeVorbis_Decoder_(d); break; case mpeg_DecoderType: status = decodeMpeg_Decoder_(d); break; case opus_DecoderType: status = decodeOpus_Decoder_(d); default: break; } if (status == needMoreInput_DecoderStatus) { lock_Mutex(&d->input->mtx); if (d->input->isComplete && size_InputBuf(d->input) == inputSize) { /* All input consumed; output buffer will drain via the audio callback. */ d->isDone = iTrue; unlock_Mutex(&d->input->mtx); break; } if (size_InputBuf(d->input) == inputSize) { wait_Condition(&d->input->changed, &d->input->mtx); } unlock_Mutex(&d->input->mtx); } else { iGuardMutex( &d->outputMutex, if (isFull_SampleBuf(&d->output)) { wait_Condition(&d->output.moreNeeded, &d->outputMutex); }); } } return 0; } void init_Decoder(iDecoder *d, iInputBuf *input, const iContentSpec *spec) { d->type = spec->type; d->gain = 1.0f; d->input = input; d->inputPos = spec->inputStartPos; d->inputFormat = spec->inputFormat; d->totalInputSize = spec->totalInputSize; d->outputFreq = spec->output.freq; d->currentSample = 0; d->totalSamples = spec->totalSamples; d->isDone = iFalse; init_Array(&d->pendingOutput, spec->output.channels * SDL_AUDIO_BITSIZE(spec->output.format) / 8); init_SampleBuf(&d->output, spec->output.format, spec->output.channels, spec->output.samples * 2); init_Mutex(&d->tagMutex); iForIndices(i, d->tags) { init_String(&d->tags[i]); } d->vorbis = NULL; #if defined (LAGRANGE_ENABLE_MPG123) d->mpeg = NULL; d->id3v1 = NULL; d->id3v2 = NULL; #endif #if defined (LAGRANGE_ENABLE_OPUS) d->opus = NULL; d->opusLastInputSize = 0; #endif init_Mutex(&d->outputMutex); 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->output.moreNeeded); signal_Condition(&d->input->changed); join_Thread(d->thread); iRelease(d->thread); deinit_Mutex(&d->outputMutex); deinit_SampleBuf(&d->output); deinit_Array(&d->pendingOutput); iForIndices(i, d->tags) { deinit_String(&d->tags[i]); } deinit_Mutex(&d->tagMutex); if (d->vorbis) { stb_vorbis_close(d->vorbis); } #if defined (LAGRANGE_ENABLE_MPG123) if (d->mpeg) { mpg123_close(d->mpeg); mpg123_delete(d->mpeg); } #endif #if defined (LAGRANGE_ENABLE_OPUS) if (d->opus) { op_free(d->opus); } #endif } iDefineTypeConstructionArgs(Decoder, (iInputBuf * input, const iContentSpec *spec), input, spec) /*----------------------------------------------------------------------------------------------*/ struct Impl_Player { SDL_AudioSpec spec; SDL_AudioDeviceID device; iString mime; float volume; int flags; iInputBuf *data; uint32_t lastInteraction; iDecoder *decoder; iAVFAudioPlayer *avfPlayer; /* iOS */ iAndroidAudioPlayer *androidPlayer; /* Android */ float lastTime; /* last known playback position, survives stop */ float lastDuration; /* total duration, survives stop */ iBool isFinished; /* set when SDL decoder output has fully drained */ }; static iPlayer * activePlayer_; static iAtomicInt numActivePlayers_; /* playing, not paused */ 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 iRangecc mediaType_(const iString *str) { iRangecc part = iNullRange; nextSplit_Rangecc(range_String(str), ";", &part); return part; } static iBool isVorbisMime_(const iString *mime) { const iRangecc mt = mediaType_(mime); return equal_Rangecc(mt, "audio/ogg") || equal_Rangecc(mt, "audio/vorbis") || equal_Rangecc(mt, "audio/x-vorbis+ogg"); } static iContentSpec detectContentSpec_Player_(const iPlayer *d) { iContentSpec content; iZap(content); const size_t dataSize = size_InputBuf(d->data); iBuffer *buf = NULL; const iRangecc mediaType = mediaType_(&d->mime); if (equal_Rangecc(mediaType, "audio/wave") || equal_Rangecc(mediaType, "audio/wav") || equal_Rangecc(mediaType, "audio/x-wav") || equal_Rangecc(mediaType, "audio/x-pn-wav")) { content.type = wav_DecoderType; } #if defined (LAGRANGE_ENABLE_OPUS) /* RFC MIME for Opus is audio/ogg; codecs=opus. Will collide with Vorbis. */ else if (equal_Rangecc(range_String(&d->mime), "audio/ogg; codecs=opus") || equal_Rangecc(range_String(&d->mime), "audio/ogg;codecs=opus") || equal_Rangecc(mediaType, "audio/opus")) { content.type = opus_DecoderType; } #endif else if (isVorbisMime_(&d->mime)) { content.type = vorbis_DecoderType; #if defined (LAGRANGE_ENABLE_OPUS) // Some servers will reply with audio/ogg for Opus, so we need to check the content. OpusHead head; int result = op_test(&head, constData_Block(&d->data->data), size_Block(&d->data->data)); if (result == 0) { content.type = opus_DecoderType; } #endif } #if defined (LAGRANGE_ENABLE_MPG123) else if (equal_Rangecc(mediaType, "audio/mpeg") || equal_Rangecc(mediaType, "audio/mp3")) { content.type = mpeg_DecoderType; } #endif else { /* TODO: Could try decoders to see if one works? */ content.type = none_DecoderType; } if (content.type != none_DecoderType) { buf = iClob(new_Buffer()); /* This holds a reference to the data block. The decoder runs in a background thread so it needs its own copy of the data. */ open_Buffer(buf, &d->data->data); } if (content.type == 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; } content.totalInputSize = readU32_Stream(is); /* file size */ readData_Buffer(buf, 4, magic); if (memcmp(magic, "WAVE", 4)) { /* Not WAV. */ return content; } /* Read all the chunks. */ int16_t blockAlign = 0; 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 && size != 18) { return content; } enum iWavFormat { pcm_WavFormat = 1, ieeeFloat_WavFormat = 3, }; const int16_t mode = read16_Stream(is); /* 1 = PCM, 3 = IEEE_FLOAT */ const int16_t numChannels = read16_Stream(is); const int32_t freq = read32_Stream(is); const uint32_t bytesPerSecond = readU32_Stream(is); blockAlign = read16_Stream(is); const int16_t bitsPerSample = read16_Stream(is); const uint16_t extSize = (size == 18 ? readU16_Stream(is) : 0); iUnused(bytesPerSecond); if (mode != pcm_WavFormat && mode != ieeeFloat_WavFormat) { /* PCM or float */ return content; } if (extSize != 0) { return content; } if (numChannels != 1 && numChannels != 2) { return content; } if (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 24 && bitsPerSample != 32 && bitsPerSample != 64) { return content; } if (bitsPerSample == 24 && blockAlign != 3 * numChannels) { return content; } content.output.freq = freq; content.output.channels = numChannels; if (mode == ieeeFloat_WavFormat) { content.inputFormat = (bitsPerSample == 32 ? AUDIO_F32 : AUDIO_F64LSB); content.output.format = AUDIO_F32; } else if (bitsPerSample == 24) { content.inputFormat = AUDIO_S24LSB; content.output.format = AUDIO_S16; } else { content.inputFormat = content.output.format = (bitsPerSample == 8 ? AUDIO_U8 : bitsPerSample == 16 ? AUDIO_S16 : AUDIO_S32); } } else if (memcmp(magic, "data", 4) == 0) { content.inputStartPos = pos_Stream(is); content.totalSamples = (uint64_t) size / blockAlign; break; } else { seek_Stream(is, pos_Stream(is) + size); } } } else if (content.type == vorbis_DecoderType) { /* Try to decode what we have and see if it looks like Vorbis. */ int consumed = 0; int error = 0; stb_vorbis *vrb = stb_vorbis_open_pushdata( constData_Block(&d->data->data), size_Block(&d->data->data), &consumed, &error, NULL); if (!vrb) { if (error != VORBIS_need_more_data) { content.type = none_DecoderType; } return content; } const stb_vorbis_info info = stb_vorbis_get_info(vrb); const int numChannels = info.channels; if (numChannels != 1 && numChannels != 2) { return content; } content.output.freq = info.sample_rate; content.output.channels = numChannels; content.output.format = AUDIO_F32; content.inputFormat = AUDIO_F32; /* actually stb_vorbis provides floats */ stb_vorbis_close(vrb); } else if (content.type == mpeg_DecoderType) { #if defined (LAGRANGE_ENABLE_MPG123) mpg123_handle *mh = mpg123_new(NULL, NULL); mpg123_open_feed(mh); mpg123_feed(mh, constData_Block(&d->data->data), size_Block(&d->data->data)); long rate = 0; int channels = 0; int encoding = 0; if (mpg123_getformat(mh, &rate, &channels, &encoding) == MPG123_OK) { content.output.freq = rate; content.output.channels = channels; content.inputFormat = AUDIO_S16; content.output.format = AUDIO_S16; } mpg123_close(mh); mpg123_delete(mh); #endif } else if (content.type == opus_DecoderType) { #if defined (LAGRANGE_ENABLE_OPUS) OpusHead head; int result = op_test(&head, constData_Block(&d->data->data), size_Block(&d->data->data)); if (result != 0) { return content; } content.output.freq = 48000; content.output.channels = head.channel_count; content.inputFormat = AUDIO_F32; content.output.format = AUDIO_F32; #endif } iAssert(content.inputFormat == content.output.format || (content.inputFormat == AUDIO_S24LSB && content.output.format == AUDIO_S16) || (content.inputFormat == AUDIO_F64LSB && content.output.format == AUDIO_F32)); content.output.samples = isAndroid_Platform() ? content.output.freq / 4 : 8192; 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->outputMutex); if (size_SampleBuf(&d->decoder->output) >= count) { read_SampleBuf(&d->decoder->output, count, stream); } else { memset(stream, d->spec.silence, len); if (d->decoder->isDone && size_SampleBuf(&d->decoder->output) == 0 && !d->isFinished) { d->isFinished = iTrue; /* signal main thread to call stop_Player */ postCommand_App("media.player.update"); } } signal_Condition(&d->decoder->output.moreNeeded); unlock_Mutex(&d->decoder->outputMutex); } int numActiveSDLAudio_Player(void) { return value_Atomic(&numActivePlayers_); } void init_Player(iPlayer *d) { iZap(d->spec); init_String(&d->mime); d->device = 0; d->decoder = NULL; d->avfPlayer = NULL; d->androidPlayer = NULL; d->data = new_InputBuf(); d->volume = 1.0f; d->flags = 0; d->lastTime = 0.0f; d->lastDuration = 0.0f; d->isFinished = iFalse; } void deinit_Player(iPlayer *d) { stop_Player(d); delete_InputBuf(d->data); deinit_String(&d->mime); iAssert(d->decoder == NULL); #if defined (iPlatformAppleMobile) iAssert(d->avfPlayer == NULL); #endif #if defined (iPlatformAndroidMobile) iAssert(d->androidPlayer == NULL); #endif if (activePlayer_ == d) { activePlayer_ = NULL; } } iBool isStarted_Player(const iPlayer *d) { #if defined (iPlatformAppleMobile) if (d->avfPlayer) { return isStarted_AVFAudioPlayer(d->avfPlayer); } #endif #if defined (iPlatformAndroidMobile) if (d->androidPlayer) { return isStarted_AndroidAudioPlayer(d->androidPlayer); } #endif return d->device != 0; } iBool isComplete_Player(const iPlayer *d) { iInputBuf *input = d->data; lock_Mutex(&input->mtx); const iBool isComplete = input->isComplete; unlock_Mutex(&input->mtx); return isComplete; } iBool isPaused_Player(const iPlayer *d) { #if defined (iPlatformAppleMobile) if (d->avfPlayer) { return isPaused_AVFAudioPlayer(d->avfPlayer); } #endif #if defined (iPlatformAndroidMobile) if (d->androidPlayer) { return isPaused_AndroidAudioPlayer(d->androidPlayer); } #endif if (!d->device) return iTrue; return SDL_GetAudioDeviceStatus(d->device) == SDL_AUDIO_PAUSED; } float volume_Player(const iPlayer *d) { return d->volume; } void updateSourceData_Player(iPlayer *d, const iString *mimeType, const iBlock *data, enum iPlayerUpdate update) { iInputBuf *input = d->data; lock_Mutex(&input->mtx); if (input->isShuttingDown) { unlock_Mutex(&input->mtx); return; } if (mimeType) { set_String(&d->mime, mimeType); } switch (update) { case replace_PlayerUpdate: set_Block(&input->data, data); input->isComplete = iFalse; #if defined (iPlatformAndroidMobile) if (!d->androidPlayer && !isVorbisMime_(&d->mime)) { /* Vorbis types are attempted via stb_vorbis/SDL first; androidPlayer is created lazily as a fallback if needed in start_Player. */ d->androidPlayer = new_AndroidAudioPlayer(); setupData_AndroidAudioPlayer(d->androidPlayer, &d->mime); } if (d->androidPlayer) { appendData_AndroidAudioPlayer(d->androidPlayer, constData_Block(data), size_Block(data)); } #endif break; case append_PlayerUpdate: { /* The assumption is that the new data and the old data share the same beginning portion; the new data has some additional data available at the end. */ const size_t oldSize = size_Block(&input->data); const size_t newSize = size_Block(data); if (input->isComplete) { #if !defined (iPlatformAndroidMobile) iAssert(newSize == oldSize); #endif break; } /* The old parts cannot have changed. */ /* NOTE: The decoder will hold a reference to the data block, which means appending here will cause a copy-on-write detach to occur. */ appendData_Block(&input->data, constBegin_Block(data) + oldSize, newSize - oldSize); #if defined (iPlatformAndroidMobile) if (d->androidPlayer && newSize > oldSize) { /* New chunk of data is passed to Java. */ appendData_AndroidAudioPlayer(d->androidPlayer, constBegin_Block(data) + oldSize, newSize - oldSize); } #endif break; } case complete_PlayerUpdate: if (!input->isComplete) { input->isComplete = iTrue; #if defined (iPlatformAppleMobile) iAssert(d->avfPlayer == NULL); d->avfPlayer = new_AVFAudioPlayer(); if (!setInput_AVFAudioPlayer(d->avfPlayer, &d->mime, &input->data)) { delete_AVFAudioPlayer(d->avfPlayer); d->avfPlayer = NULL; } #elif defined (iPlatformAndroidMobile) if (d->androidPlayer) { /* Streaming path: player was created in replace_PlayerUpdate. */ setComplete_AndroidAudioPlayer(d->androidPlayer); } else if (!isVorbisMime_(&d->mime)) { /* Fallback for non-Vorbis: data arrived without replace_PlayerUpdate (shouldn't happen in normal use). */ d->androidPlayer = new_AndroidAudioPlayer(); if (!setInput_AndroidAudioPlayer(d->androidPlayer, &d->mime, &input->data)) { delete_AndroidAudioPlayer(d->androidPlayer); d->androidPlayer = NULL; } } /* Vorbis: start_Player handles setup via stb_vorbis or androidPlayer fallback. */ #endif } break; } signal_Condition(&input->changed); unlock_Mutex(&input->mtx); } size_t sourceDataSize_Player(const iPlayer *d) { lock_Mutex(&d->data->mtx); const size_t size = size_Block(&d->data->data); unlock_Mutex(&d->data->mtx); return size; } static iBool setupSDLAudio_(void) { static iBool isAudioInited_ = iFalse; if (!isAudioInited_) { if (SDL_InitSubSystem(SDL_INIT_AUDIO)) { fprintf(stderr, "[SDL] audio init failed: %s\n", SDL_GetError()); return iFalse; } isAudioInited_ = iTrue; } return iTrue; } static void resumeDevice_Player_(iPlayer *d) { SDL_PauseAudioDevice(d->device, SDL_FALSE); #if defined (iPlatformAndroidMobile) javaCommand_Android("audio.sdl.start player:%p", d); notifySDLAudioStarted_Android(); /* we control event loop blocking */ #endif setNotIdle_Player(d); activePlayer_ = d; add_Atomic(&numActivePlayers_, 1); } static iBool pauseDevice_Player_(iPlayer *d) { if (d->device && SDL_GetAudioDeviceStatus(d->device) == SDL_AUDIO_PLAYING) { add_Atomic(&numActivePlayers_, -1); iAssert(value_Atomic(&numActivePlayers_) >= 0); SDL_PauseAudioDevice(d->device, SDL_TRUE); #if defined(iPlatformAndroidMobile) javaCommand_Android("audio.sdl.stop player:%p", d); #endif return iTrue; } return iFalse; } iBool start_Player(iPlayer *d) { if (isStarted_Player(d)) { return iFalse; } d->isFinished = iFalse; #if defined (iPlatformAppleMobile) if (!d->avfPlayer && isComplete_Player(d)) { /* Recreate from buffered data (e.g., restart after stop). */ d->avfPlayer = new_AVFAudioPlayer(); if (!setInput_AVFAudioPlayer(d->avfPlayer, &d->mime, &d->data->data)) { delete_AVFAudioPlayer(d->avfPlayer); d->avfPlayer = NULL; } } if (d->avfPlayer) { play_AVFAudioPlayer(d->avfPlayer); setNotIdle_Player(d); activePlayer_ = d; return iTrue; } #endif #if defined (iPlatformAndroidMobile) if (d->androidPlayer) { play_AndroidAudioPlayer(d->androidPlayer); setNotIdle_Player(d); activePlayer_ = d; return iTrue; } if (!isVorbisMime_(&d->mime)) { /* Recreate androidPlayer from buffered data (restart after stop). */ d->androidPlayer = new_AndroidAudioPlayer(); setupData_AndroidAudioPlayer(d->androidPlayer, &d->mime); lock_Mutex(&d->data->mtx); appendData_AndroidAudioPlayer(d->androidPlayer, constData_Block(&d->data->data), size_Block(&d->data->data)); unlock_Mutex(&d->data->mtx); if (isComplete_Player(d)) { setComplete_AndroidAudioPlayer(d->androidPlayer); } play_AndroidAudioPlayer(d->androidPlayer); setNotIdle_Player(d); activePlayer_ = d; return iTrue; } /* Vorbis: fall through to attempt stb_vorbis decoding via SDL. */ #endif iContentSpec content = detectContentSpec_Player_(d); #if defined (iPlatformAndroidMobile) if (!content.output.freq && isVorbisMime_(&d->mime)) { if (content.type == none_DecoderType) { /* stb_vorbis hard-failed (not a data-starvation issue). Fall back to Android MediaPlayer once we have enough data to be confident about the failure. */ const iBool isComplete = isComplete_Player(d); const size_t dataSize = sourceDataSize_Player(d); if (isComplete || dataSize >= 64 * 1024) { d->androidPlayer = new_AndroidAudioPlayer(); setupData_AndroidAudioPlayer(d->androidPlayer, &d->mime); lock_Mutex(&d->data->mtx); appendData_AndroidAudioPlayer(d->androidPlayer, constData_Block(&d->data->data), size_Block(&d->data->data)); unlock_Mutex(&d->data->mtx); if (isComplete) { setComplete_AndroidAudioPlayer(d->androidPlayer); } play_AndroidAudioPlayer(d->androidPlayer); setNotIdle_Player(d); activePlayer_ = d; return iTrue; } } return iFalse; /* Need more data for stb_vorbis to parse the stream headers. */ } #endif if (!content.output.freq) { return iFalse; } content.output.callback = writeOutputSamples_Player_; content.output.userdata = d; if (!setupSDLAudio_()) { return iFalse; } d->device = SDL_OpenAudioDevice(NULL, SDL_FALSE /* playback */, &content.output, &d->spec, 0); if (!d->device) { return iFalse; } d->decoder = new_Decoder(d->data, &content); d->decoder->gain = d->volume; resumeDevice_Player_(d); activePlayer_ = d; return iTrue; } void setPaused_Player(iPlayer *d, iBool isPaused) { #if defined (iPlatformAppleMobile) if (d->avfPlayer) { setPaused_AVFAudioPlayer(d->avfPlayer, isPaused); return; } #endif #if defined (iPlatformAndroidMobile) if (d->androidPlayer) { setPaused_AndroidAudioPlayer(d->androidPlayer, isPaused); return; } #endif if (isStarted_Player(d)) { if (isPaused && SDL_GetAudioDeviceStatus(d->device) == SDL_AUDIO_PLAYING) { pauseDevice_Player_(d); } else if (!isPaused && SDL_GetAudioDeviceStatus(d->device) == SDL_AUDIO_PAUSED) { resumeDevice_Player_(d); } } } void stop_Player(iPlayer *d) { /* Remember current play position and duration. */ d->lastTime = time_Player(d); d->lastDuration = duration_Player(d); d->isFinished = iFalse; #if defined (iPlatformAppleMobile) if (d->avfPlayer) { stop_AVFAudioPlayer(d->avfPlayer); delete_AVFAudioPlayer(d->avfPlayer); d->avfPlayer = NULL; if (activePlayer_ == d) { clearNowPlayingInfo_iOS(); } return; } #endif #if defined (iPlatformAndroidMobile) if (d->androidPlayer) { delete_AndroidAudioPlayer(d->androidPlayer); d->androidPlayer = NULL; return; } #endif if (isStarted_Player(d)) { pauseDevice_Player_(d); SDL_CloseAudioDevice(d->device); d->device = 0; delete_Decoder(d->decoder); d->decoder = NULL; } } void setVolume_Player(iPlayer *d, float volume) { d->volume = iClamp(volume, 0, 1); if (d->decoder) { d->decoder->gain = d->volume; } #if defined (iPlatformAppleMobile) if (d->avfPlayer) { setVolume_AVFAudioPlayer(d->avfPlayer, volume); } #endif #if defined (iPlatformAndroidMobile) if (d->androidPlayer) { setVolume_AndroidAudioPlayer(d->androidPlayer, volume); } #endif setNotIdle_Player(d); } void setFlags_Player(iPlayer *d, int flags, iBool set) { iChangeFlags(d->flags, flags, set); setNotIdle_Player(d); } void setNotIdle_Player(iPlayer *d) { d->lastInteraction = SDL_GetTicks(); } int flags_Player(const iPlayer *d) { return d->flags; } const iString *tag_Player(const iPlayer *d, enum iPlayerTag tag) { const iString *str = NULL; if (d->decoder) { lock_Mutex(&d->decoder->tagMutex); str = collect_String(copy_String(&d->decoder->tags[tag])); unlock_Mutex(&d->decoder->tagMutex); } return str ? str : collectNew_String(); } float time_Player(const iPlayer *d) { #if defined (iPlatformAppleMobile) if (d->avfPlayer) { return currentTime_AVFAudioPlayer(d->avfPlayer); } #endif #if defined (iPlatformAndroidMobile) if (d->androidPlayer) { return currentTime_AndroidAudioPlayer(d->androidPlayer); } #endif if (d->decoder) { return (float) ((double) d->decoder->currentSample / (double) d->spec.freq); } return d->lastTime; } float duration_Player(const iPlayer *d) { #if defined (iPlatformAppleMobile) if (d->avfPlayer) { return duration_AVFAudioPlayer(d->avfPlayer); } #endif #if defined (iPlatformAndroidMobile) if (d->androidPlayer) { return duration_AndroidAudioPlayer(d->androidPlayer); } #endif if (d->decoder) { return (float) ((double) d->decoder->totalSamples / (double) d->spec.freq); } return d->lastDuration; } iBool isFinished_Player(const iPlayer *d) { #if defined (iPlatformAppleMobile) if (d->avfPlayer) { return isFinished_AVFAudioPlayer(d->avfPlayer); } #endif #if defined (iPlatformAndroidMobile) if (d->androidPlayer) { return isFinished_AndroidAudioPlayer(d->androidPlayer); } #endif return d->isFinished; } float streamProgress_Player(const iPlayer *d) { if (d->decoder && d->decoder->totalInputSize) { lock_Mutex(&d->data->mtx); const double inputSize = size_InputBuf(d->data); unlock_Mutex(&d->data->mtx); return (float) iMin(1.0, (double) inputSize / (double) d->decoder->totalInputSize); } return 0; } uint32_t idleTimeMs_Player(const iPlayer *d) { return SDL_GetTicks() - d->lastInteraction; } iString *metadataLabel_Player(const iPlayer *d) { iString *meta = new_String(); if (d->decoder) { lock_Mutex(&d->decoder->tagMutex); const iString *tags = d->decoder->tags; if (!isEmpty_String(&tags[title_PlayerTag])) { appendFormat_String(meta, "${audio.meta.title}: %s\n", cstr_String(&tags[title_PlayerTag])); } if (!isEmpty_String(&tags[artist_PlayerTag])) { appendFormat_String(meta, "${audio.meta.artist}: %s\n", cstr_String(&tags[artist_PlayerTag])); } if (!isEmpty_String(&tags[genre_PlayerTag])) { appendFormat_String(meta, "${audio.meta.genre}: %s\n", cstr_String(&tags[genre_PlayerTag])); } if (!isEmpty_String(&tags[date_PlayerTag])) { appendFormat_String(meta, "${audio.meta.date}: %s\n", cstr_String(&tags[date_PlayerTag])); } unlock_Mutex(&d->decoder->tagMutex); } if (d->decoder) { appendFormat_String(meta, translateCStr_Lang("${n.bit} %s %d ${hz}"), /* translation adds %d */ SDL_AUDIO_BITSIZE(d->decoder->inputFormat), cstr_Lang(SDL_AUDIO_ISFLOAT(d->decoder->inputFormat) ? "numbertype.float" : "numbertype.integer"), d->spec.freq); } return meta; } iPlayer *active_Player(void) { return activePlayer_; }