1 /*
2 * Copyright 2017 The Emscripten Authors. All rights reserved.
3 * Emscripten is available under two separate licenses, the MIT license and the
4 * University of Illinois/NCSA Open Source License. Both these licenses can be
5 * found in the LICENSE file.
6 */
7
8 // This tests captures a fixed amount of audio data,
9 // then plays it back.
10 //
11 // Wishlist:
12 // - Try multiple devices simultaneously;
13 // - Have several recording passes over the same fixed buffer_size.
14
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <stdint.h>
18 #include <stdbool.h>
19 #include <assert.h>
20 #include <unistd.h>
21 #ifdef __EMSCRIPTEN__
22 #include <emscripten.h>
23 #define ASSUME_AL_FLOAT32
24 #endif
25 #include <AL/al.h>
26 #include <AL/alc.h>
27 #ifdef ASSUME_AL_FLOAT32
28 #define AL_FORMAT_MONO_FLOAT32 0x10010
29 #define AL_FORMAT_STEREO_FLOAT32 0x10011
30 #endif
31
alformat_string(ALenum format)32 static const char* alformat_string(ALenum format) {
33 switch(format) {
34 #define CASE(X) case X: return #X;
35 CASE(AL_FORMAT_MONO8)
36 CASE(AL_FORMAT_MONO16)
37 CASE(AL_FORMAT_STEREO8)
38 CASE(AL_FORMAT_STEREO16)
39 #ifdef ASSUME_AL_FLOAT32
40 CASE(AL_FORMAT_MONO_FLOAT32)
41 CASE(AL_FORMAT_STEREO_FLOAT32)
42 #endif
43 #undef CASE
44 }
45 return "<no_string_available>";
46 }
47
48 #ifdef __EMSCRIPTEN__
49 EMSCRIPTEN_KEEPALIVE
50 #endif
end_test(int result)51 void end_test(int result) {
52 #ifdef __EMSCRIPTEN__
53 REPORT_RESULT(result);
54 #else
55 exit(result);
56 #endif
57 }
58
59 #ifndef TEST_SAMPLERATE
60 #define TEST_SAMPLERATE 44100
61 #endif
62 #ifndef TEST_FORMAT
63 #define TEST_FORMAT AL_FORMAT_MONO16
64 #endif
65 #ifndef TEST_BUFFERSIZE
66 #define TEST_BUFFERSIZE TEST_SAMPLERATE*8 // 8 seconds of data
67 #endif
68
69 // The "arg" pointer passed to iter().
70 // It's also a state machine with only two states (see is_playing_back):
71 // either capturing audio or playing back the captured samples.
72 typedef struct {
73 bool is_playing_back;
74
75 // When capturing
76 const char *capture_device_name;
77 ALCuint sample_rate;
78 ALenum format;
79 ALCsizei buffer_size;
80 ALCdevice *capture_device;
81 size_t sample_size;
82 unsigned nchannels;
83
84 // Playback
85 ALuint source, buffer;
86 ALCcontext *context;
87 ALCdevice *playback_device;
88 } App;
89
90
iter(void * papp)91 static void iter(void *papp) {
92 App* const app = papp;
93
94 if(app->is_playing_back) {
95 ALint state;
96 alGetSourcei(app->source, AL_SOURCE_STATE, &state);
97
98 #ifdef __EMSCRIPTEN__
99 return;
100 #else
101 if(state != AL_STOPPED)
102 return;
103 #endif
104
105 alDeleteSources(1, &app->source);
106 alDeleteBuffers(1, &app->buffer);
107 alcMakeContextCurrent(NULL);
108 alcDestroyContext(app->context);
109 alcCloseDevice(app->playback_device);
110 end_test(EXIT_SUCCESS);
111 }
112
113 ALCint ncaptured = 0;
114 alcGetIntegerv(app->capture_device, ALC_CAPTURE_SAMPLES, 1, &ncaptured);
115
116 const ALCint WANTED_NCAPTURED = 7 * app->sample_rate;
117
118 if(ncaptured < WANTED_NCAPTURED)
119 return;
120
121 size_t datasize = WANTED_NCAPTURED * app->nchannels * app->sample_size;
122
123 ALCubyte *data = malloc(datasize);
124 if(!data) {
125 fprintf(stderr, "Out of memory!\n");
126 end_test(EXIT_FAILURE);
127 }
128
129 alcCaptureSamples(app->capture_device, data, WANTED_NCAPTURED);
130 ALCenum err = alcGetError(app->capture_device);
131 if(err != ALC_NO_ERROR) {
132 fprintf(stderr, "alcCaptureSamples() yielded an error, but wasn't supposed to! (%x, %s)\n", err, alcGetString(NULL, err));
133 end_test(EXIT_FAILURE);
134 }
135
136 // This was here to see if alcCaptureSamples() would reset the number of
137 // available captured samples as a side-effect.
138 // Turns out, it does (on Linux with OpenAL-Soft).
139 // That's important to know because this behaviour, while reasonably
140 // expected, isn't documented anywhere.
141 /*
142 {
143 ALCint ncaptured_now = 0;
144 alcGetIntegerv(app->capture_device, ALC_CAPTURE_SAMPLES, 1, &ncaptured_now);
145
146 printf(
147 "For information, number of captured sample frames :\n"
148 "- Before alcCaptureSamples(): %u;\n"
149 "- After alcCaptureSamples(): %u.\n"
150 , (unsigned)ncaptured, (unsigned)ncaptured_now
151 );
152 }
153 */
154
155 alcCaptureStop(app->capture_device);
156
157 #ifdef __EMSCRIPTEN__
158 // Restarting capture must zero the reported number of captured samples.
159 // Works in our case because no processing takes place until the current
160 // iteration yields to the javascript main loop.
161 alcCaptureStart(app->capture_device);
162 alcCaptureStop(app->capture_device);
163 ALCint zeroed_ncaptured = 0xdead;
164 alcGetIntegerv(app->capture_device, ALC_CAPTURE_SAMPLES, 1, &zeroed_ncaptured);
165 if(zeroed_ncaptured) {
166 fprintf(stderr, "Restarting capture didn't zero the reported number of available sample frames!\n");
167 }
168 #endif
169
170 ALCboolean could_close = alcCaptureCloseDevice(app->capture_device);
171 if(!could_close) {
172 fprintf(stderr, "Could not close device \"%s\"!\n", app->capture_device_name);
173 end_test(EXIT_FAILURE);
174 }
175
176 // We're not as careful with playback - this is already tested
177 // elsewhere.
178 app->playback_device = alcOpenDevice(NULL);
179 assert(app->playback_device);
180 app->context = alcCreateContext(app->playback_device, NULL);
181 assert(app->context);
182 alcMakeContextCurrent(app->context);
183 alGenBuffers(1, &app->buffer);
184 alGenSources(1, &app->source);
185 alBufferData(app->buffer, app->format, data, datasize, app->sample_rate);
186 alSourcei(app->source, AL_BUFFER, app->buffer);
187
188 free(data);
189
190 #ifdef __EMSCRIPTEN__
191 EM_ASM(
192 var succeed_btn = document.createElement('input');
193 var fail_btn = document.createElement('input');
194 succeed_btn.type = fail_btn.type = 'button';
195 succeed_btn.name = succeed_btn.value = 'Succeed';
196 fail_btn.name = fail_btn.value = 'Fail';
197 succeed_btn.onclick = function() {
198 //Module.ccall('end_test', null, ['number'], [0]);
199 _end_test(0);
200 };
201 fail_btn.onclick = function() {
202 //Module.ccall('end_test', null, ['number'], [1]);
203 _end_test(1);
204 };
205 document.body.appendChild(succeed_btn);
206 document.body.appendChild(fail_btn);
207 );
208 #endif
209
210 app->is_playing_back = true;
211 alSourcePlay(app->source);
212 printf(
213 "You should now hear the captured audio data.\n"
214 #ifdef __EMSCRIPTEN__
215 "Press the [Succeed] button to end the test successfully, or the [Fail] button otherwise.\n"
216 #endif
217 );
218 }
219
220
221 static App app = {
222 .is_playing_back = false,
223 .sample_rate = TEST_SAMPLERATE,
224 .format = TEST_FORMAT,
225 .buffer_size = TEST_BUFFERSIZE
226 };
227
228 #ifdef __EMSCRIPTEN__
229 EMSCRIPTEN_KEEPALIVE
230 #endif
ignite()231 static void ignite() {
232
233 app.capture_device_name = alcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);
234
235 app.capture_device = alcCaptureOpenDevice(
236 app.capture_device_name, app.sample_rate, app.format, app.buffer_size
237 );
238 if(!app.capture_device) {
239 ALCenum err = alcGetError(app.capture_device);
240 fprintf(stderr,
241 "alcCaptureOpenDevice(\"%s\", sample_rate=%u, format=%s, "
242 "buffer_size=%u) failed with ALC error %x (%s)\n",
243 app.capture_device_name,
244 (unsigned) app.sample_rate, alformat_string(app.format),
245 (unsigned) app.buffer_size,
246 (unsigned) err, alcGetString(NULL, err)
247 );
248 end_test(EXIT_FAILURE);
249 }
250
251 switch(app.format) {
252 case AL_FORMAT_MONO8: app.sample_size=1; app.nchannels=1; break;
253 case AL_FORMAT_MONO16: app.sample_size=2; app.nchannels=1; break;
254 case AL_FORMAT_STEREO8: app.sample_size=1; app.nchannels=2; break;
255 case AL_FORMAT_STEREO16: app.sample_size=2; app.nchannels=2; break;
256 #ifdef ASSUME_AL_FLOAT32
257 case AL_FORMAT_MONO_FLOAT32: app.sample_size=4; app.nchannels=1; break;
258 case AL_FORMAT_STEREO_FLOAT32: app.sample_size=4; app.nchannels=2; break;
259 #endif
260 }
261
262 alcCaptureStart(app.capture_device);
263
264 #ifdef __EMSCRIPTEN__
265 emscripten_set_main_loop_arg(iter, &app, 0, 0);
266 #else
267 for(;;) {
268 iter(&app);
269 usleep(16666);
270 }
271 #endif
272 }
273
main()274 int main() {
275
276 printf(
277 "This test will attempt to capture %f seconds "
278 "worth of audio data from your default audio "
279 "input device, and then play it back.\n"
280 , TEST_BUFFERSIZE / (float) TEST_SAMPLERATE
281 );
282 #ifdef __EMSCRIPTEN__
283 printf(
284 "Press the [Start Recording] button below when you're ready, then "
285 "allow audio capture when asked by the browser.\n"
286 "No sample should be captured until that moment.\n"
287 );
288 EM_ASM(
289 var btn = document.createElement('input');
290 btn.type = 'button';
291 btn.name = btn.value = 'Start recording';
292 btn.onclick = function() {
293 _ignite();
294 document.body.removeChild(btn);
295 };
296 document.body.appendChild(btn);
297 );
298 #else
299 printf("Press [Enter] when you're ready.\n");
300 getchar();
301 ignite();
302 #endif
303
304 return EXIT_SUCCESS;
305 }
306