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