summaryrefslogtreecommitdiff
path: root/src/audio
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-10-04 17:20:41 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-10-04 17:20:41 +0300
commitefb40105d657da935d3854e6ea7a513c6210224b (patch)
tree0327d88d68c0a402f0f90307756bb8536c54dc8b /src/audio
parent25346114f96a29e8af6125e0cac3d5f8a2ffd551 (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')
-rw-r--r--src/audio/player.c337
-rw-r--r--src/audio/player.h5
2 files changed, 306 insertions, 36 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
29iDeclareType(InputBuf)
30
31struct Impl_InputBuf {
32 iMutex mtx;
33 iCondition changed;
34 iBlock data;
35 iBool isComplete;
36};
37
38void 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
45void deinit_InputBuf(iInputBuf *d) {
46 deinit_Block(&d->data);
47 deinit_Condition(&d->changed);
48 deinit_Mutex(&d->mtx);
49}
50
51size_t size_InputBuf(const iInputBuf *d) {
52 return size_Block(&d->data);
53}
54
55iDefineTypeConstruction(InputBuf)
56
57/*----------------------------------------------------------------------------------------------*/
58
59iDeclareType(SampleBuf)
60
61struct Impl_SampleBuf {
62 void * data;
63 size_t sampleSize;
64 size_t count;
65 size_t head, tail;
66};
67
68void 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
76void deinit_SampleBuf(iSampleBuf *d) {
77 free(d->data);
78}
79
80size_t size_SampleBuf(const iSampleBuf *d) {
81 return d->head - d->tail;
82}
83
84size_t vacancy_SampleBuf(const iSampleBuf *d) {
85 return d->count - size_SampleBuf(d) - 1;
86}
87
88iBool isFull_SampleBuf(const iSampleBuf *d) {
89 return vacancy_SampleBuf(d) == 0;
90}
91
92iLocalDef void *ptr_SampleBuf_(iSampleBuf *d, size_t pos) {
93 return ((char *) d->data) + (d->sampleSize * pos);
94}
95
96void 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
112void 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
130iDeclareType(ContentSpec)
131
132struct Impl_ContentSpec {
133 SDL_AudioSpec spec;
134 iRanges wavData;
135};
136
28iDeclareType(Decoder) 137iDeclareType(Decoder)
29 138
30enum iDecoderType { 139enum 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
37struct Impl_Decoder { 147struct 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
157static 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
169static 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
196void 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
208void 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
217iDefineTypeConstructionArgs(Decoder, (iInputBuf *input, const iContentSpec *spec),
218 input, spec)
219
220/*----------------------------------------------------------------------------------------------*/
221
43struct Impl_Player { 222struct 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
229iDefineTypeConstruction(Player)
230
231static size_t sampleSize_Player_(const iPlayer *d) {
232 return d->spec.channels * SDL_AUDIO_BITSIZE(d->spec.format) / 8;
233}
234
235static int silence_Player_(const iPlayer *d) {
236 return d->spec.silence;
237}
238
239static 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
305static 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
51void init_Player(iPlayer *d) { 322void 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
59void deinit_Player(iPlayer *d) { 329void 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
64iBool isStarted_Player(const iPlayer *d) { 334iBool isStarted_Player(const iPlayer *d) {
@@ -70,55 +340,54 @@ void setFormatHint_Player(iPlayer *d, const char *hint) {
70} 340}
71 341
72void updateSourceData_Player(iPlayer *d, const iBlock *data, enum iPlayerUpdate update) { 342void 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
90static 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
96iBool start_Player(iPlayer *d) { 362iBool 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
378void setPaused_Player(iPlayer *d, iBool isPaused) {
379 if (isStarted_Player(d)) {
380 SDL_PauseAudioDevice(d->device, isPaused ? SDL_TRUE : SDL_FALSE);
381 }
382}
383
117void stop_Player(iPlayer *d) { 384void 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}
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 {
36void setFormatHint_Player (iPlayer *, const char *hint); 36void setFormatHint_Player (iPlayer *, const char *hint);
37void updateSourceData_Player (iPlayer *, const iBlock *data, enum iPlayerUpdate update); 37void updateSourceData_Player (iPlayer *, const iBlock *data, enum iPlayerUpdate update);
38 38
39iBool start_Player (iPlayer *); 39iBool start_Player (iPlayer *);
40void stop_Player (iPlayer *); 40void setPaused_Player (iPlayer *, iBool isPaused);
41void stop_Player (iPlayer *);
41 42
42iBool isStarted_Player (const iPlayer *); 43iBool isStarted_Player (const iPlayer *);