diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-10-09 21:51:36 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-10-09 21:51:36 +0300 |
commit | a1101ec38cb29f701457215e35f75904326120cb (patch) | |
tree | e0b40731f65b9711d64353a78fd51ffa795b1b67 /src | |
parent | 4bf163ecfac27c3dd86dff96df6f4647f9afe021 (diff) |
Support Ogg Vorbis audio
Playback starts as soon as possible, so one can listen while streaming.
stb_vorbis.c needed a tiny tweak to not die on a file without (Ogg? Vorbis?) comments.
Diffstat (limited to 'src')
-rw-r--r-- | src/audio/player.c | 153 | ||||
-rw-r--r-- | src/audio/stb_vorbis.c | 4 | ||||
-rw-r--r-- | src/gmrequest.c | 3 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 13 |
4 files changed, 141 insertions, 32 deletions
diff --git a/src/audio/player.c b/src/audio/player.c index 0825dabd..226e0662 100644 --- a/src/audio/player.c +++ b/src/audio/player.c | |||
@@ -48,7 +48,7 @@ struct Impl_ContentSpec { | |||
48 | SDL_AudioSpec output; | 48 | SDL_AudioSpec output; |
49 | size_t totalInputSize; | 49 | size_t totalInputSize; |
50 | uint64_t totalSamples; | 50 | uint64_t totalSamples; |
51 | iRanges dataRange; | 51 | size_t inputStartPos; |
52 | }; | 52 | }; |
53 | 53 | ||
54 | iDeclareType(Decoder) | 54 | iDeclareType(Decoder) |
@@ -61,11 +61,13 @@ struct Impl_Decoder { | |||
61 | iInputBuf * input; | 61 | iInputBuf * input; |
62 | size_t inputPos; | 62 | size_t inputPos; |
63 | size_t totalInputSize; | 63 | size_t totalInputSize; |
64 | unsigned int outputFreq; | ||
64 | iSampleBuf output; | 65 | iSampleBuf output; |
65 | iMutex outputMutex; | 66 | iMutex outputMutex; |
67 | iArray pendingOutput; | ||
66 | uint64_t currentSample; | 68 | uint64_t currentSample; |
67 | uint64_t totalSamples; /* zero if unknown */ | 69 | uint64_t totalSamples; /* zero if unknown */ |
68 | iRanges wavData; | 70 | stb_vorbis * vorbis; |
69 | }; | 71 | }; |
70 | 72 | ||
71 | enum iDecoderStatus { | 73 | enum iDecoderStatus { |
@@ -78,8 +80,7 @@ static enum iDecoderStatus decodeWav_Decoder_(iDecoder *d, iRanges inputRange) { | |||
78 | const size_t inputSampleSize = numChannels * SDL_AUDIO_BITSIZE(d->inputFormat) / 8; | 80 | const size_t inputSampleSize = numChannels * SDL_AUDIO_BITSIZE(d->inputFormat) / 8; |
79 | const size_t vacancy = vacancy_SampleBuf(&d->output); | 81 | const size_t vacancy = vacancy_SampleBuf(&d->output); |
80 | const size_t inputBytePos = inputSampleSize * d->inputPos; | 82 | const size_t inputBytePos = inputSampleSize * d->inputPos; |
81 | const size_t avail = | 83 | const size_t avail = (inputRange.end - inputBytePos) / inputSampleSize; |
82 | iMin(inputRange.end - inputBytePos, d->wavData.end - inputBytePos) / inputSampleSize; | ||
83 | if (avail == 0) { | 84 | if (avail == 0) { |
84 | return needMoreInput_DecoderStatus; | 85 | return needMoreInput_DecoderStatus; |
85 | } | 86 | } |
@@ -88,7 +89,7 @@ static enum iDecoderStatus decodeWav_Decoder_(iDecoder *d, iRanges inputRange) { | |||
88 | return ok_DecoderStatus; | 89 | return ok_DecoderStatus; |
89 | } | 90 | } |
90 | void *samples = malloc(inputSampleSize * n); | 91 | void *samples = malloc(inputSampleSize * n); |
91 | /* Get a copy of the input for mixing. */ { | 92 | /* Get a copy of the input for further processing. */ { |
92 | lock_Mutex(&d->input->mtx); | 93 | lock_Mutex(&d->input->mtx); |
93 | iAssert(inputSampleSize * d->inputPos < size_Block(&d->input->data)); | 94 | iAssert(inputSampleSize * d->inputPos < size_Block(&d->input->data)); |
94 | memcpy(samples, | 95 | memcpy(samples, |
@@ -154,13 +155,81 @@ static enum iDecoderStatus decodeWav_Decoder_(iDecoder *d, iRanges inputRange) { | |||
154 | return ok_DecoderStatus; | 155 | return ok_DecoderStatus; |
155 | } | 156 | } |
156 | 157 | ||
158 | static enum iDecoderStatus decodeVorbis_Decoder_(iDecoder *d, iRanges inputRange) { | ||
159 | iUnused(inputRange); | ||
160 | const iBlock *input = &d->input->data; | ||
161 | if (!d->vorbis) { | ||
162 | lock_Mutex(&d->input->mtx); | ||
163 | int error; | ||
164 | int consumed; | ||
165 | d->vorbis = stb_vorbis_open_pushdata( | ||
166 | constData_Block(input), size_Block(input), &consumed, &error, NULL); | ||
167 | if (!d->vorbis) { | ||
168 | return needMoreInput_DecoderStatus; | ||
169 | } | ||
170 | d->inputPos += consumed; | ||
171 | unlock_Mutex(&d->input->mtx); | ||
172 | } | ||
173 | if (d->totalSamples == 0 && d->input->isComplete) { | ||
174 | /* Time to check the stream size. */ | ||
175 | lock_Mutex(&d->input->mtx); | ||
176 | d->totalInputSize = size_Block(input); | ||
177 | int error = 0; | ||
178 | stb_vorbis *vrb = stb_vorbis_open_memory(constData_Block(input), size_Block(input), | ||
179 | &error, NULL); | ||
180 | if (vrb) { | ||
181 | d->totalSamples = stb_vorbis_stream_length_in_samples(vrb); | ||
182 | stb_vorbis_close(vrb); | ||
183 | } | ||
184 | unlock_Mutex(&d->input->mtx); | ||
185 | } | ||
186 | enum iDecoderStatus status = ok_DecoderStatus; | ||
187 | while (size_Array(&d->pendingOutput) < d->output.count) { | ||
188 | /* Try to decode some input. */ | ||
189 | lock_Mutex(&d->input->mtx); | ||
190 | int count = 0; | ||
191 | float **samples = NULL; | ||
192 | int remaining = d->inputPos < size_Block(input) ? size_Block(input) - d->inputPos : 0; | ||
193 | int consumed = stb_vorbis_decode_frame_pushdata( | ||
194 | d->vorbis, constData_Block(input) + d->inputPos, remaining, NULL, &samples, &count); | ||
195 | d->inputPos += consumed; | ||
196 | iAssert(d->inputPos <= size_Block(input)); | ||
197 | unlock_Mutex(&d->input->mtx); | ||
198 | if (count == 0) { | ||
199 | if (consumed == 0) { | ||
200 | status = needMoreInput_DecoderStatus; | ||
201 | break; | ||
202 | } | ||
203 | else continue; | ||
204 | } | ||
205 | /* Apply gain. */ { | ||
206 | float sample[2]; | ||
207 | for (size_t i = 0; i < (size_t) count; ++i) { | ||
208 | for (size_t chan = 0; chan < d->output.numChannels; chan++) { | ||
209 | sample[chan] = samples[chan][i] * d->gain; | ||
210 | } | ||
211 | pushBack_Array(&d->pendingOutput, sample); | ||
212 | } | ||
213 | } | ||
214 | } | ||
215 | /* Write as much as we can. */ | ||
216 | lock_Mutex(&d->outputMutex); | ||
217 | size_t avail = vacancy_SampleBuf(&d->output); | ||
218 | size_t n = iMin(avail, size_Array(&d->pendingOutput)); | ||
219 | write_SampleBuf(&d->output, constData_Array(&d->pendingOutput), n); | ||
220 | removeN_Array(&d->pendingOutput, 0, n); | ||
221 | unlock_Mutex(&d->outputMutex); | ||
222 | d->currentSample += n; | ||
223 | return status; | ||
224 | } | ||
225 | |||
157 | static iThreadResult run_Decoder_(iThread *thread) { | 226 | static iThreadResult run_Decoder_(iThread *thread) { |
158 | iDecoder *d = userData_Thread(thread); | 227 | iDecoder *d = userData_Thread(thread); |
159 | /* Amount of data initially available. */ | ||
160 | lock_Mutex(&d->input->mtx); | ||
161 | size_t inputSize = size_InputBuf(d->input); | ||
162 | unlock_Mutex(&d->input->mtx); | ||
163 | while (d->type) { | 228 | while (d->type) { |
229 | /* Check amount of data available. */ | ||
230 | lock_Mutex(&d->input->mtx); | ||
231 | size_t inputSize = size_InputBuf(d->input); | ||
232 | unlock_Mutex(&d->input->mtx); | ||
164 | iRanges inputRange = { d->inputPos, inputSize }; | 233 | iRanges inputRange = { d->inputPos, inputSize }; |
165 | iAssert(inputRange.start <= inputRange.end); | 234 | iAssert(inputRange.start <= inputRange.end); |
166 | if (!d->type) break; | 235 | if (!d->type) break; |
@@ -171,6 +240,8 @@ static iThreadResult run_Decoder_(iThread *thread) { | |||
171 | case wav_DecoderType: | 240 | case wav_DecoderType: |
172 | status = decodeWav_Decoder_(d, inputRange); | 241 | status = decodeWav_Decoder_(d, inputRange); |
173 | break; | 242 | break; |
243 | case vorbis_DecoderType: | ||
244 | status = decodeVorbis_Decoder_(d, inputRange); | ||
174 | default: | 245 | default: |
175 | break; | 246 | break; |
176 | } | 247 | } |
@@ -180,7 +251,6 @@ static iThreadResult run_Decoder_(iThread *thread) { | |||
180 | if (size_InputBuf(d->input) == inputSize) { | 251 | if (size_InputBuf(d->input) == inputSize) { |
181 | wait_Condition(&d->input->changed, &d->input->mtx); | 252 | wait_Condition(&d->input->changed, &d->input->mtx); |
182 | } | 253 | } |
183 | inputSize = size_InputBuf(d->input); | ||
184 | unlock_Mutex(&d->input->mtx); | 254 | unlock_Mutex(&d->input->mtx); |
185 | } | 255 | } |
186 | else { | 256 | else { |
@@ -194,18 +264,21 @@ static iThreadResult run_Decoder_(iThread *thread) { | |||
194 | } | 264 | } |
195 | 265 | ||
196 | void init_Decoder(iDecoder *d, iInputBuf *input, const iContentSpec *spec) { | 266 | void init_Decoder(iDecoder *d, iInputBuf *input, const iContentSpec *spec) { |
197 | d->type = spec->type; | 267 | d->type = spec->type; |
198 | d->gain = 0.5f; | 268 | d->gain = 0.5f; |
199 | d->input = input; | 269 | d->input = input; |
200 | d->inputPos = spec->dataRange.start; | 270 | d->inputPos = spec->inputStartPos; |
201 | d->inputFormat = spec->inputFormat; | 271 | d->inputFormat = spec->inputFormat; |
202 | d->totalInputSize = spec->totalInputSize; | 272 | d->totalInputSize = spec->totalInputSize; |
273 | d->outputFreq = spec->output.freq; | ||
274 | init_Array(&d->pendingOutput, spec->output.channels * SDL_AUDIO_BITSIZE(spec->output.format) / 8); | ||
203 | init_SampleBuf(&d->output, | 275 | init_SampleBuf(&d->output, |
204 | spec->output.format, | 276 | spec->output.format, |
205 | spec->output.channels, | 277 | spec->output.channels, |
206 | spec->output.samples * 2); | 278 | spec->output.samples * 2); |
207 | d->currentSample = 0; | 279 | d->currentSample = 0; |
208 | d->totalSamples = spec->totalSamples; | 280 | d->totalSamples = spec->totalSamples; |
281 | d->vorbis = NULL; | ||
209 | init_Mutex(&d->outputMutex); | 282 | init_Mutex(&d->outputMutex); |
210 | d->thread = new_Thread(run_Decoder_); | 283 | d->thread = new_Thread(run_Decoder_); |
211 | setUserData_Thread(d->thread, d); | 284 | setUserData_Thread(d->thread, d); |
@@ -220,11 +293,7 @@ void deinit_Decoder(iDecoder *d) { | |||
220 | iRelease(d->thread); | 293 | iRelease(d->thread); |
221 | deinit_Mutex(&d->outputMutex); | 294 | deinit_Mutex(&d->outputMutex); |
222 | deinit_SampleBuf(&d->output); | 295 | deinit_SampleBuf(&d->output); |
223 | } | 296 | deinit_Array(&d->pendingOutput); |
224 | |||
225 | static void start_Decoder_(iDecoder *d) { | ||
226 | if (!d->thread && d->type != none_DecoderType) { | ||
227 | } | ||
228 | } | 297 | } |
229 | 298 | ||
230 | iDefineTypeConstructionArgs(Decoder, (iInputBuf *input, const iContentSpec *spec), | 299 | iDefineTypeConstructionArgs(Decoder, (iInputBuf *input, const iContentSpec *spec), |
@@ -256,7 +325,18 @@ static iContentSpec contentSpec_Player_(const iPlayer *d) { | |||
256 | const size_t dataSize = size_InputBuf(d->data); | 325 | const size_t dataSize = size_InputBuf(d->data); |
257 | iBuffer *buf = iClob(new_Buffer()); | 326 | iBuffer *buf = iClob(new_Buffer()); |
258 | open_Buffer(buf, &d->data->data); | 327 | open_Buffer(buf, &d->data->data); |
259 | content.type = wav_DecoderType; /* TODO: from MIME */ | 328 | if (!cmp_String(&d->mime, "audio/wave") || !cmp_String(&d->mime, "audio/wav") || |
329 | !cmp_String(&d->mime, "audio/x-wav") || !cmp_String(&d->mime, "audio/x-pn-wav")) { | ||
330 | content.type = wav_DecoderType; | ||
331 | } | ||
332 | else if (!cmp_String(&d->mime, "audio/vorbis") || !cmp_String(&d->mime, "audio/ogg") || | ||
333 | !cmp_String(&d->mime, "audio/x-vorbis+ogg")) { | ||
334 | content.type = vorbis_DecoderType; | ||
335 | } | ||
336 | else { | ||
337 | /* TODO: Could try decoders to see if one works? */ | ||
338 | content.type = none_DecoderType; | ||
339 | } | ||
260 | if (content.type == wav_DecoderType && dataSize >= 44) { | 340 | if (content.type == wav_DecoderType && dataSize >= 44) { |
261 | /* Read the RIFF/WAVE header. */ | 341 | /* Read the RIFF/WAVE header. */ |
262 | iStream *is = stream_Buffer(buf); | 342 | iStream *is = stream_Buffer(buf); |
@@ -326,8 +406,8 @@ static iContentSpec contentSpec_Player_(const iPlayer *d) { | |||
326 | } | 406 | } |
327 | } | 407 | } |
328 | else if (memcmp(magic, "data", 4) == 0) { | 408 | else if (memcmp(magic, "data", 4) == 0) { |
329 | content.dataRange = (iRanges){ pos_Stream(is), pos_Stream(is) + size }; | 409 | content.inputStartPos = pos_Stream(is); |
330 | content.totalSamples = (uint64_t) size_Range(&content.dataRange) / blockAlign; | 410 | content.totalSamples = (uint64_t) size / blockAlign; |
331 | break; | 411 | break; |
332 | } | 412 | } |
333 | else { | 413 | else { |
@@ -335,6 +415,26 @@ static iContentSpec contentSpec_Player_(const iPlayer *d) { | |||
335 | } | 415 | } |
336 | } | 416 | } |
337 | } | 417 | } |
418 | else if (content.type == vorbis_DecoderType) { | ||
419 | /* Try to decode what we have and see if it looks like Vorbis. */ | ||
420 | int consumed = 0; | ||
421 | int error = 0; | ||
422 | stb_vorbis *vrb = stb_vorbis_open_pushdata( | ||
423 | constData_Block(&d->data->data), size_Block(&d->data->data), &consumed, &error, NULL); | ||
424 | if (!vrb) { | ||
425 | return content; | ||
426 | } | ||
427 | const stb_vorbis_info info = stb_vorbis_get_info(vrb); | ||
428 | const int numChannels = info.channels; | ||
429 | if (numChannels != 1 && numChannels != 2) { | ||
430 | return content; | ||
431 | } | ||
432 | content.output.freq = info.sample_rate; | ||
433 | content.output.channels = numChannels; | ||
434 | content.output.format = AUDIO_F32; | ||
435 | content.inputFormat = AUDIO_F32; /* actually stb_vorbis provides floats */ | ||
436 | stb_vorbis_close(vrb); | ||
437 | } | ||
338 | iAssert(content.inputFormat == content.output.format || | 438 | iAssert(content.inputFormat == content.output.format || |
339 | (content.inputFormat == AUDIO_S24LSB && content.output.format == AUDIO_S16) || | 439 | (content.inputFormat == AUDIO_S24LSB && content.output.format == AUDIO_S16) || |
340 | (content.inputFormat == AUDIO_F64LSB && content.output.format == AUDIO_F32)); | 440 | (content.inputFormat == AUDIO_F64LSB && content.output.format == AUDIO_F32)); |
@@ -416,7 +516,10 @@ iBool start_Player(iPlayer *d) { | |||
416 | if (isStarted_Player(d)) { | 516 | if (isStarted_Player(d)) { |
417 | return iFalse; | 517 | return iFalse; |
418 | } | 518 | } |
419 | iContentSpec content = contentSpec_Player_(d); | 519 | iContentSpec content = contentSpec_Player_(d); |
520 | if (!content.output.freq) { | ||
521 | return iFalse; | ||
522 | } | ||
420 | content.output.callback = writeOutputSamples_Player_; | 523 | content.output.callback = writeOutputSamples_Player_; |
421 | content.output.userdata = d; | 524 | content.output.userdata = d; |
422 | d->device = SDL_OpenAudioDevice(NULL, SDL_FALSE /* playback */, &content.output, &d->spec, 0); | 525 | d->device = SDL_OpenAudioDevice(NULL, SDL_FALSE /* playback */, &content.output, &d->spec, 0); |
@@ -456,7 +559,7 @@ float duration_Player(const iPlayer *d) { | |||
456 | } | 559 | } |
457 | 560 | ||
458 | float streamProgress_Player(const iPlayer *d) { | 561 | float streamProgress_Player(const iPlayer *d) { |
459 | if (d->decoder->totalInputSize) { | 562 | if (d->decoder && d->decoder->totalInputSize) { |
460 | lock_Mutex(&d->data->mtx); | 563 | lock_Mutex(&d->data->mtx); |
461 | const double inputSize = size_InputBuf(d->data); | 564 | const double inputSize = size_InputBuf(d->data); |
462 | unlock_Mutex(&d->data->mtx); | 565 | unlock_Mutex(&d->data->mtx); |
diff --git a/src/audio/stb_vorbis.c b/src/audio/stb_vorbis.c index a8cbfa6c..98b8e649 100644 --- a/src/audio/stb_vorbis.c +++ b/src/audio/stb_vorbis.c | |||
@@ -3642,8 +3642,8 @@ static int start_decoder(vorb *f) | |||
3642 | f->vendor[len] = (char)'\0'; | 3642 | f->vendor[len] = (char)'\0'; |
3643 | //user comments | 3643 | //user comments |
3644 | f->comment_list_length = get32_packet(f); | 3644 | f->comment_list_length = get32_packet(f); |
3645 | f->comment_list = (char**)setup_malloc(f, sizeof(char*) * (f->comment_list_length)); | 3645 | f->comment_list = (char**)setup_malloc(f, sizeof(char*) * (f->comment_list_length + 1)); |
3646 | if (f->comment_list == NULL) return error(f, VORBIS_outofmem); | 3646 | if (f->comment_list == NULL) return error(f, VORBIS_outofmem); |
3647 | 3647 | ||
3648 | for(i=0; i < f->comment_list_length; ++i) { | 3648 | for(i=0; i < f->comment_list_length; ++i) { |
3649 | len = get32_packet(f); | 3649 | len = get32_packet(f); |
diff --git a/src/gmrequest.c b/src/gmrequest.c index 161c654c..dd2c2720 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c | |||
@@ -457,6 +457,9 @@ void submit_GmRequest(iGmRequest *d) { | |||
457 | else if (endsWithCase_String(path, ".wav")) { | 457 | else if (endsWithCase_String(path, ".wav")) { |
458 | setCStr_String(&d->resp.meta, "audio/wave"); | 458 | setCStr_String(&d->resp.meta, "audio/wave"); |
459 | } | 459 | } |
460 | else if (endsWithCase_String(path, ".ogg")) { | ||
461 | setCStr_String(&d->resp.meta, "audio/ogg"); | ||
462 | } | ||
460 | else { | 463 | else { |
461 | setCStr_String(&d->resp.meta, "application/octet-stream"); | 464 | setCStr_String(&d->resp.meta, "application/octet-stream"); |
462 | } | 465 | } |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index bbe5ccba..db7d8c8a 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -1680,11 +1680,14 @@ static void draw_PlayerUI(iPlayerUI *d, iPaint *p) { | |||
1680 | isPaused_Player(d->player) ? dim : bright, | 1680 | isPaused_Player(d->player) ? dim : bright, |
1681 | left_Alignment, | 1681 | left_Alignment, |
1682 | iRound(playTime)); | 1682 | iRound(playTime)); |
1683 | int rightWidth = drawSevenSegmentTime_( | 1683 | int rightWidth = 0; |
1684 | init_I2(right_Rect(d->scrubberRect) - 2 * gap_UI, yMid - hgt / 2), | 1684 | if (totalTime > 0) { |
1685 | dim, | 1685 | rightWidth = |
1686 | right_Alignment, | 1686 | drawSevenSegmentTime_(init_I2(right_Rect(d->scrubberRect) - 2 * gap_UI, yMid - hgt / 2), |
1687 | iRound(totalTime)); | 1687 | dim, |
1688 | right_Alignment, | ||
1689 | iRound(totalTime)); | ||
1690 | } | ||
1688 | /* Scrubber. */ | 1691 | /* Scrubber. */ |
1689 | const int s1 = left_Rect(d->scrubberRect) + leftWidth + 6 * gap_UI; | 1692 | const int s1 = left_Rect(d->scrubberRect) + leftWidth + 6 * gap_UI; |
1690 | const int s2 = right_Rect(d->scrubberRect) - rightWidth - 6 * gap_UI; | 1693 | const int s2 = right_Rect(d->scrubberRect) - rightWidth - 6 * gap_UI; |