diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-10-04 17:20:41 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-10-04 17:20:41 +0300 |
commit | efb40105d657da935d3854e6ea7a513c6210224b (patch) | |
tree | 0327d88d68c0a402f0f90307756bb8536c54dc8b /src/audio/player.c | |
parent | 25346114f96a29e8af6125e0cac3d5f8a2ffd551 (diff) |
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.
Diffstat (limited to 'src/audio/player.c')
-rw-r--r-- | src/audio/player.c | 337 |
1 files changed, 303 insertions, 34 deletions
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. */ | |||
22 | 22 | ||
23 | #include "player.h" | 23 | #include "player.h" |
24 | 24 | ||
25 | #include <the_Foundation/buffer.h> | ||
25 | #include <the_Foundation/thread.h> | 26 | #include <the_Foundation/thread.h> |
26 | #include <SDL_audio.h> | 27 | #include <SDL_audio.h> |
27 | 28 | ||
29 | iDeclareType(InputBuf) | ||
30 | |||
31 | struct Impl_InputBuf { | ||
32 | iMutex mtx; | ||
33 | iCondition changed; | ||
34 | iBlock data; | ||
35 | iBool isComplete; | ||
36 | }; | ||
37 | |||
38 | void init_InputBuf(iInputBuf *d) { | ||
39 | init_Mutex(&d->mtx); | ||
40 | init_Condition(&d->changed); | ||
41 | init_Block(&d->data, 0); | ||
42 | d->isComplete = iTrue; | ||
43 | } | ||
44 | |||
45 | void deinit_InputBuf(iInputBuf *d) { | ||
46 | deinit_Block(&d->data); | ||
47 | deinit_Condition(&d->changed); | ||
48 | deinit_Mutex(&d->mtx); | ||
49 | } | ||
50 | |||
51 | size_t size_InputBuf(const iInputBuf *d) { | ||
52 | return size_Block(&d->data); | ||
53 | } | ||
54 | |||
55 | iDefineTypeConstruction(InputBuf) | ||
56 | |||
57 | /*----------------------------------------------------------------------------------------------*/ | ||
58 | |||
59 | iDeclareType(SampleBuf) | ||
60 | |||
61 | struct Impl_SampleBuf { | ||
62 | void * data; | ||
63 | size_t sampleSize; | ||
64 | size_t count; | ||
65 | size_t head, tail; | ||
66 | }; | ||
67 | |||
68 | void init_SampleBuf(iSampleBuf *d, size_t sampleSize, size_t count) { | ||
69 | d->sampleSize = sampleSize; | ||
70 | d->count = count + 1; /* considered empty if head==tail */ | ||
71 | d->data = malloc(d->sampleSize * d->count); | ||
72 | d->head = 0; | ||
73 | d->tail = 0; | ||
74 | } | ||
75 | |||
76 | void deinit_SampleBuf(iSampleBuf *d) { | ||
77 | free(d->data); | ||
78 | } | ||
79 | |||
80 | size_t size_SampleBuf(const iSampleBuf *d) { | ||
81 | return d->head - d->tail; | ||
82 | } | ||
83 | |||
84 | size_t vacancy_SampleBuf(const iSampleBuf *d) { | ||
85 | return d->count - size_SampleBuf(d) - 1; | ||
86 | } | ||
87 | |||
88 | iBool isFull_SampleBuf(const iSampleBuf *d) { | ||
89 | return vacancy_SampleBuf(d) == 0; | ||
90 | } | ||
91 | |||
92 | iLocalDef void *ptr_SampleBuf_(iSampleBuf *d, size_t pos) { | ||
93 | return ((char *) d->data) + (d->sampleSize * pos); | ||
94 | } | ||
95 | |||
96 | void write_SampleBuf(iSampleBuf *d, const void *samples, const size_t n) { | ||
97 | iAssert(n <= vacancy_SampleBuf(d)); | ||
98 | const size_t headPos = d->head % d->count; | ||
99 | const size_t avail = d->count - headPos; | ||
100 | if (n > avail) { | ||
101 | const char *in = samples; | ||
102 | memcpy(ptr_SampleBuf_(d, headPos), in, d->sampleSize * avail); | ||
103 | in += d->sampleSize * avail; | ||
104 | memcpy(ptr_SampleBuf_(d, 0), in, d->sampleSize * (n - avail)); | ||
105 | } | ||
106 | else { | ||
107 | memcpy(ptr_SampleBuf_(d, headPos), samples, d->sampleSize * n); | ||
108 | } | ||
109 | d->head += n; | ||
110 | } | ||
111 | |||
112 | void read_SampleBuf(iSampleBuf *d, const size_t n, void *samples_out) { | ||
113 | iAssert(n <= size_SampleBuf(d)); | ||
114 | const size_t tailPos = d->tail % d->count; | ||
115 | const size_t avail = d->count - tailPos; | ||
116 | if (n > avail) { | ||
117 | char *out = samples_out; | ||
118 | memcpy(out, ptr_SampleBuf_(d, tailPos), d->sampleSize * avail); | ||
119 | out += d->sampleSize * avail; | ||
120 | memcpy(out, ptr_SampleBuf_(d, 0), d->sampleSize * (n - avail)); | ||
121 | } | ||
122 | else { | ||
123 | memcpy(samples_out, ptr_SampleBuf_(d, tailPos), d->sampleSize * n); | ||
124 | } | ||
125 | d->tail += n; | ||
126 | } | ||
127 | |||
128 | /*----------------------------------------------------------------------------------------------*/ | ||
129 | |||
130 | iDeclareType(ContentSpec) | ||
131 | |||
132 | struct Impl_ContentSpec { | ||
133 | SDL_AudioSpec spec; | ||
134 | iRanges wavData; | ||
135 | }; | ||
136 | |||
28 | iDeclareType(Decoder) | 137 | iDeclareType(Decoder) |
29 | 138 | ||
30 | enum iDecoderType { | 139 | enum iDecoderType { |
140 | none_DecoderType, | ||
31 | wav_DecoderType, | 141 | wav_DecoderType, |
32 | mpeg_DecoderType, | 142 | mpeg_DecoderType, |
33 | vorbis_DecoderType, | 143 | vorbis_DecoderType, |
@@ -36,29 +146,189 @@ enum iDecoderType { | |||
36 | 146 | ||
37 | struct Impl_Decoder { | 147 | struct Impl_Decoder { |
38 | enum iDecoderType type; | 148 | enum iDecoderType type; |
39 | size_t inPos; | 149 | iThread * thread; |
40 | // iBlock samples; | 150 | iInputBuf * input; |
151 | size_t inputPos; | ||
152 | iSampleBuf output; | ||
153 | iMutex mtx; /* for output */ | ||
154 | iRanges wavData; | ||
41 | }; | 155 | }; |
42 | 156 | ||
157 | static void parseWav_Decoder_(iDecoder *d, iRanges inputRange) { | ||
158 | lock_Mutex(&d->mtx); | ||
159 | const size_t vacancy = vacancy_SampleBuf(&d->output); | ||
160 | const size_t avail = iMin(inputRange.end - d->inputPos, d->wavData.end - d->inputPos) / | ||
161 | d->output.sampleSize; | ||
162 | const size_t n = iMin(vacancy, avail); | ||
163 | iGuardMutex(&d->input->mtx, /* lock so input array isn't reallocated during the copy */ | ||
164 | write_SampleBuf(&d->output, constData_Block(&d->input->data) + d->inputPos, n)); | ||
165 | d->inputPos += n; | ||
166 | unlock_Mutex(&d->mtx); | ||
167 | } | ||
168 | |||
169 | static iThreadResult run_Decoder_(iThread *thread) { | ||
170 | iDecoder *d = userData_Thread(thread); | ||
171 | while (d->type) { | ||
172 | size_t inputSize = 0; | ||
173 | /* Grab more input. */ { | ||
174 | lock_Mutex(&d->input->mtx); | ||
175 | wait_Condition(&d->input->changed, &d->input->mtx); | ||
176 | inputSize = size_Block(&d->input->data); | ||
177 | unlock_Mutex(&d->input->mtx); | ||
178 | } | ||
179 | iRanges inputRange = { d->inputPos, inputSize }; | ||
180 | iAssert(inputRange.start <= inputRange.end); | ||
181 | if (!d->type) break; | ||
182 | /* Have data to work on and a place to save output? */ | ||
183 | if (!isEmpty_Range(&inputRange) && !isFull_SampleBuf(&d->output)) { | ||
184 | switch (d->type) { | ||
185 | case wav_DecoderType: | ||
186 | parseWav_Decoder_(d, inputRange); | ||
187 | break; | ||
188 | default: | ||
189 | break; | ||
190 | } | ||
191 | } | ||
192 | } | ||
193 | return 0; | ||
194 | } | ||
195 | |||
196 | void init_Decoder(iDecoder *d, iInputBuf *input, const iContentSpec *spec) { | ||
197 | d->type = wav_DecoderType; | ||
198 | d->input = input; | ||
199 | d->inputPos = spec->wavData.start; | ||
200 | init_SampleBuf(&d->output, SDL_AUDIO_BITSIZE(spec->spec.format) / 8 * | ||
201 | spec->spec.channels, spec->spec.samples * 2); | ||
202 | init_Mutex(&d->mtx); | ||
203 | d->thread = new_Thread(run_Decoder_); | ||
204 | setUserData_Thread(d->thread, d); | ||
205 | start_Thread(d->thread); | ||
206 | } | ||
207 | |||
208 | void deinit_Decoder(iDecoder *d) { | ||
209 | d->type = none_DecoderType; | ||
210 | signal_Condition(&d->input->changed); | ||
211 | join_Thread(d->thread); | ||
212 | iRelease(d->thread); | ||
213 | deinit_Mutex(&d->mtx); | ||
214 | deinit_SampleBuf(&d->output); | ||
215 | } | ||
216 | |||
217 | iDefineTypeConstructionArgs(Decoder, (iInputBuf *input, const iContentSpec *spec), | ||
218 | input, spec) | ||
219 | |||
220 | /*----------------------------------------------------------------------------------------------*/ | ||
221 | |||
43 | struct Impl_Player { | 222 | struct Impl_Player { |
44 | SDL_AudioSpec spec; | 223 | SDL_AudioSpec spec; |
45 | SDL_AudioDeviceID device; | 224 | SDL_AudioDeviceID device; |
46 | iMutex mtx; | 225 | iInputBuf *data; |
47 | iBlock data; | 226 | iDecoder *decoder; |
48 | iBool isDataComplete; | ||
49 | }; | 227 | }; |
50 | 228 | ||
229 | iDefineTypeConstruction(Player) | ||
230 | |||
231 | static size_t sampleSize_Player_(const iPlayer *d) { | ||
232 | return d->spec.channels * SDL_AUDIO_BITSIZE(d->spec.format) / 8; | ||
233 | } | ||
234 | |||
235 | static int silence_Player_(const iPlayer *d) { | ||
236 | return d->spec.silence; | ||
237 | } | ||
238 | |||
239 | static iContentSpec contentSpec_Player_(const iPlayer *d) { | ||
240 | iContentSpec content; | ||
241 | iZap(content); | ||
242 | const size_t dataSize = size_InputBuf(d->data); | ||
243 | iBuffer *buf = iClob(new_Buffer()); | ||
244 | open_Buffer(buf, &d->data->data); | ||
245 | enum iDecoderType decType = wav_DecoderType; /* TODO: from MIME */ | ||
246 | if (decType == wav_DecoderType && dataSize >= 44) { | ||
247 | /* Read the RIFF/WAVE header. */ | ||
248 | iStream *is = stream_Buffer(buf); | ||
249 | char magic[4]; | ||
250 | readData_Buffer(buf, 4, magic); | ||
251 | if (memcmp(magic, "RIFF", 4)) { | ||
252 | /* Not WAV. */ | ||
253 | return content; | ||
254 | } | ||
255 | readU32_Stream(is); /* file size */ | ||
256 | readData_Buffer(buf, 4, magic); | ||
257 | if (memcmp(magic, "WAVE", 4)) { | ||
258 | /* Not WAV. */ | ||
259 | return content; | ||
260 | } | ||
261 | /* Read all the chunks. */ | ||
262 | while (!atEnd_Buffer(buf)) { | ||
263 | readData_Buffer(buf, 4, magic); | ||
264 | const size_t size = read32_Stream(is); | ||
265 | if (memcmp(magic, "fmt ", 4) == 0) { | ||
266 | if (size != 16) { | ||
267 | return content; | ||
268 | } | ||
269 | const int16_t mode = read16_Stream(is); /* 1 = PCM */ | ||
270 | const int16_t numChannels = read16_Stream(is); | ||
271 | const int32_t freq = read32_Stream(is); | ||
272 | const uint32_t bytesPerSecond = readU32_Stream(is); | ||
273 | const int16_t blockAlign = read16_Stream(is); | ||
274 | const int16_t bitsPerSample = read16_Stream(is); | ||
275 | iUnused(bytesPerSecond); | ||
276 | iUnused(blockAlign); /* TODO: Should use this one when reading samples? */ | ||
277 | if (mode != 1) { /* PCM */ | ||
278 | return content; | ||
279 | } | ||
280 | if (numChannels != 1 && numChannels != 2) { | ||
281 | return content; | ||
282 | } | ||
283 | if (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 32) { | ||
284 | return content; | ||
285 | } | ||
286 | content.spec.freq = freq; | ||
287 | content.spec.channels = numChannels; | ||
288 | content.spec.format = | ||
289 | (bitsPerSample == 8 ? AUDIO_S8 : bitsPerSample == 16 ? AUDIO_S16 : AUDIO_S32); | ||
290 | } | ||
291 | else if (memcmp(magic, "data", 4) == 0) { | ||
292 | size_t len = read32_Stream(is); /* data size */ | ||
293 | content.wavData = (iRanges){ pos_Stream(is), pos_Stream(is) + len }; | ||
294 | break; | ||
295 | } | ||
296 | else { | ||
297 | seek_Stream(is, pos_Stream(is) + size); | ||
298 | } | ||
299 | } | ||
300 | } | ||
301 | content.spec.samples = 2048; | ||
302 | return content; | ||
303 | } | ||
304 | |||
305 | static void writeOutputSamples_Player_(void *plr, Uint8 *stream, int len) { | ||
306 | iPlayer *d = plr; | ||
307 | iAssert(d->decoder); | ||
308 | const size_t sampleSize = sampleSize_Player_(d); | ||
309 | const size_t count = len / sampleSize; | ||
310 | lock_Mutex(&d->decoder->mtx); | ||
311 | if (size_SampleBuf(&d->decoder->output) >= count) { | ||
312 | read_SampleBuf(&d->decoder->output, count, stream); | ||
313 | } | ||
314 | else { | ||
315 | memset(stream, 0, len); /* TODO: If unsigned samples, don't use zero! */ | ||
316 | } | ||
317 | unlock_Mutex(&d->decoder->mtx); | ||
318 | /* Wake up decoder; there is more room for output. */ | ||
319 | signal_Condition(&d->data->changed); | ||
320 | } | ||
321 | |||
51 | void init_Player(iPlayer *d) { | 322 | void init_Player(iPlayer *d) { |
52 | iZap(d->spec); | 323 | iZap(d->spec); |
53 | d->device = 0; | 324 | d->device = 0; |
54 | init_Mutex(&d->mtx); | 325 | d->decoder = NULL; |
55 | init_Block(&d->data, 0); | 326 | d->data = new_InputBuf(); |
56 | d->isDataComplete = iFalse; | ||
57 | } | 327 | } |
58 | 328 | ||
59 | void deinit_Player(iPlayer *d) { | 329 | void deinit_Player(iPlayer *d) { |
60 | stop_Player(d); | 330 | stop_Player(d); |
61 | deinit_Block(&d->data); | 331 | delete_InputBuf(d->data); |
62 | } | 332 | } |
63 | 333 | ||
64 | iBool isStarted_Player(const iPlayer *d) { | 334 | iBool isStarted_Player(const iPlayer *d) { |
@@ -70,55 +340,54 @@ void setFormatHint_Player(iPlayer *d, const char *hint) { | |||
70 | } | 340 | } |
71 | 341 | ||
72 | void updateSourceData_Player(iPlayer *d, const iBlock *data, enum iPlayerUpdate update) { | 342 | void updateSourceData_Player(iPlayer *d, const iBlock *data, enum iPlayerUpdate update) { |
73 | lock_Mutex(&d->mtx); | 343 | iInputBuf *inp = d->data; |
344 | lock_Mutex(&inp->mtx); | ||
74 | switch (update) { | 345 | switch (update) { |
75 | case replace_PlayerUpdate: | 346 | case replace_PlayerUpdate: |
76 | set_Block(&d->data, data); | 347 | set_Block(&inp->data, data); |
77 | d->isDataComplete = iFalse; | 348 | inp->isComplete = iFalse; |
78 | break; | 349 | break; |
79 | case append_PlayerUpdate: | 350 | case append_PlayerUpdate: |
80 | append_Block(&d->data, data); | 351 | append_Block(&inp->data, data); |
81 | d->isDataComplete = iFalse; | 352 | inp->isComplete = iFalse; |
82 | break; | 353 | break; |
83 | case complete_PlayerUpdate: | 354 | case complete_PlayerUpdate: |
84 | d->isDataComplete = iTrue; | 355 | inp->isComplete = iTrue; |
85 | break; | 356 | break; |
86 | } | 357 | } |
87 | unlock_Mutex(&d->mtx); | 358 | signal_Condition(&inp->changed); |
88 | } | 359 | unlock_Mutex(&inp->mtx); |
89 | |||
90 | static void writeOutputSamples_Player_(void *plr, Uint8 *stream, int len) { | ||
91 | iPlayer *d = plr; | ||
92 | memset(stream, 0, len); | ||
93 | /* TODO: Copy samples from the decoder's ring buffer. */ | ||
94 | } | 360 | } |
95 | 361 | ||
96 | iBool start_Player(iPlayer *d) { | 362 | iBool start_Player(iPlayer *d) { |
97 | if (isStarted_Player(d)) { | 363 | if (isStarted_Player(d)) { |
98 | return iFalse; | 364 | return iFalse; |
99 | } | 365 | } |
100 | SDL_AudioSpec conf; | 366 | iContentSpec content = contentSpec_Player_(d); |
101 | iZap(conf); | 367 | content.spec.callback = writeOutputSamples_Player_; |
102 | conf.freq = 44100; /* TODO: from content */ | 368 | content.spec.userdata = d; |
103 | conf.format = AUDIO_S16; | 369 | d->device = SDL_OpenAudioDevice(NULL, SDL_FALSE /* playback */, &content.spec, &d->spec, 0); |
104 | conf.channels = 2; /* TODO: from content */ | ||
105 | conf.samples = 2048; | ||
106 | conf.callback = writeOutputSamples_Player_; | ||
107 | conf.userdata = d; | ||
108 | d->device = SDL_OpenAudioDevice(NULL, SDL_FALSE /* playback */, &conf, &d->spec, 0); | ||
109 | if (!d->device) { | 370 | if (!d->device) { |
110 | return iFalse; | 371 | return iFalse; |
111 | } | 372 | } |
112 | /* TODO: Start the stream/decoder thread. */ | 373 | d->decoder = new_Decoder(d->data, &content); |
113 | /* TODO: Audio device is unpaused when there are samples ready to play. */ | 374 | SDL_PauseAudioDevice(d->device, SDL_FALSE); |
114 | return iTrue; | 375 | return iTrue; |
115 | } | 376 | } |
116 | 377 | ||
378 | void setPaused_Player(iPlayer *d, iBool isPaused) { | ||
379 | if (isStarted_Player(d)) { | ||
380 | SDL_PauseAudioDevice(d->device, isPaused ? SDL_TRUE : SDL_FALSE); | ||
381 | } | ||
382 | } | ||
383 | |||
117 | void stop_Player(iPlayer *d) { | 384 | void stop_Player(iPlayer *d) { |
118 | if (isStarted_Player(d)) { | 385 | if (isStarted_Player(d)) { |
119 | /* TODO: Stop the stream/decoder. */ | 386 | /* TODO: Stop the stream/decoder. */ |
120 | SDL_PauseAudioDevice(d->device, SDL_TRUE); | 387 | SDL_PauseAudioDevice(d->device, SDL_TRUE); |
121 | SDL_CloseAudioDevice(d->device); | 388 | SDL_CloseAudioDevice(d->device); |
122 | d->device = 0; | 389 | d->device = 0; |
390 | delete_Decoder(d->decoder); | ||
391 | d->decoder = NULL; | ||
123 | } | 392 | } |
124 | } | 393 | } |