File: openal_capture.c

package info (click to toggle)
emscripten 3.1.6~dfsg-5
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 114,112 kB
  • sloc: ansic: 583,052; cpp: 391,943; javascript: 79,361; python: 54,180; sh: 49,997; pascal: 4,658; makefile: 3,426; asm: 2,191; lisp: 1,869; ruby: 488; cs: 142
file content (346 lines) | stat: -rw-r--r-- 10,230 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
/*
 * Copyright 2017 The Emscripten Authors.  All rights reserved.
 * Emscripten is available under two separate licenses, the MIT license and the
 * University of Illinois/NCSA Open Source License.  Both these licenses can be
 * found in the LICENSE file.
 */

// This tests captures a fixed amount of audio data,
// then plays it back.
//
// Wishlist:
// - Try multiple devices simultaneously;
// - Have several recording passes over the same fixed buffer_size.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <unistd.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#define ASSUME_AL_FLOAT32
#endif
#include <AL/al.h>
#include <AL/alc.h>
#ifdef ASSUME_AL_FLOAT32
#define AL_FORMAT_MONO_FLOAT32                   0x10010
#define AL_FORMAT_STEREO_FLOAT32                 0x10011
#endif

const char* alformat_string(ALenum format) {
    switch(format) {
    #define CASE(X) case X: return #X;
    CASE(AL_FORMAT_MONO8)
    CASE(AL_FORMAT_MONO16)
    CASE(AL_FORMAT_STEREO8)
    CASE(AL_FORMAT_STEREO16)
#ifdef ASSUME_AL_FLOAT32
    CASE(AL_FORMAT_MONO_FLOAT32)
    CASE(AL_FORMAT_STEREO_FLOAT32)
#endif
    #undef CASE
    }
    return "<no_string_available>";
}

#ifdef __EMSCRIPTEN__
EMSCRIPTEN_KEEPALIVE
#endif
void end_test(int result) {
#ifdef __EMSCRIPTEN__
    REPORT_RESULT(result);
#else
    exit(result);
#endif
}

#ifndef TEST_DURATION
#define TEST_DURATION 8000000 // 8s -> 8000000us
#endif
#ifndef TEST_SAMPLERATE
#define TEST_SAMPLERATE 44100
#endif
#ifndef TEST_FORMAT
#define TEST_FORMAT AL_FORMAT_MONO16
#endif
#ifndef TEST_BUFFERSIZE
#define TEST_BUFFERSIZE TEST_SAMPLERATE * 50 / 1000 // 50ms buffer
#endif

// The "arg" pointer passed to iter().
// It's also a state machine with only two states (see is_playing_back):
// either capturing audio or playing back the captured samples.
typedef struct {
    bool is_playing_back;

    // When capturing
    const char *capture_device_name;
    ALCuint sample_rate;
    ALenum format;
    ALCsizei buffer_size;
    ALCdevice *capture_device;
    size_t sample_size;
    unsigned nchannels;

    // Playback
    ALuint source, buffer;
    ALCcontext *context;
    ALCdevice *playback_device;
    size_t captured;
    long long duration;
    int recorded_size;
    char * recorded;
} App;

App app = {
    .is_playing_back = false,
    .sample_rate = TEST_SAMPLERATE,
    .format = TEST_FORMAT,
    .buffer_size = TEST_BUFFERSIZE,
    .captured = 0,
    .duration = TEST_DURATION
};

// frames -> bytes
int bytesForFrames(int frames) {
    return frames * app.nchannels * app.sample_size;
}

// usec -> frames
int framesForDuration(long long usec) {
    return (usec * app.sample_rate) / 1000000LL;
}

// usec -> bytes
int bytesForDuration(long long usec) {
    return bytesForFrames(framesForDuration(usec));
}

// frames -> usec
long long durationForFrames(int frames) {
    return frames * 1000000LL / app.sample_rate;
}

void iter() {
    if(app.is_playing_back) {
        ALint state;
        alGetSourcei(app.source, AL_SOURCE_STATE, &state);

#ifdef __EMSCRIPTEN__
        return;
#else
        if(state != AL_STOPPED)
            return;
#endif
   
        alDeleteSources(1, &app.source);
        alDeleteBuffers(1, &app.buffer);
        alcMakeContextCurrent(NULL);
        alcDestroyContext(app.context);
        alcCloseDevice(app.playback_device); 
        end_test(EXIT_SUCCESS);
    }

    ALCint ncaptured = 0;
    alcGetIntegerv(app.capture_device, ALC_CAPTURE_SAMPLES, 1, &ncaptured);

    // take samples when the buffer is at least filled for 1/3 of its size
    if (ncaptured < app.buffer_size / 3)
        return;
    
    const int target = framesForDuration(app.duration);
    ALCint readSize = ncaptured;
    
    // check if there are more frames than what's needed
    if (app.captured + readSize > target)
        readSize = target - app.captured;

    alcCaptureSamples(app.capture_device, app.recorded + bytesForFrames(app.captured), readSize);
    ALCenum err = alcGetError(app.capture_device);
    if(err != ALC_NO_ERROR) {
        fprintf(stderr, "alcCaptureSamples() yielded an error, but wasn't supposed to! (%x, %s)\n", err, alcGetString(NULL, err));
        end_test(EXIT_FAILURE);
    }
    
    app.captured += readSize;
    
    if (app.captured < target)
        return;
    else if (app.captured > target) {
        fprintf(stderr, "Captured frames exeedes expectations!\n");
        end_test(EXIT_FAILURE);
    }
    
    
    // This was here to see if alcCaptureSamples() would reset the number of
    // available captured samples as a side-effect.
    // Turns out, it does (on Linux with OpenAL-Soft).
    // That's important to know because this behaviour, while reasonably 
    // expected, isn't documented anywhere.
    /*
    {
        ALCint ncaptured_now = 0;
        alcGetIntegerv(app.capture_device, ALC_CAPTURE_SAMPLES, 1, &ncaptured_now);

        printf(
            "For information, number of captured sample frames :\n"
            "- Before alcCaptureSamples(): %u;\n"
            "- After  alcCaptureSamples(): %u.\n"
            , (unsigned)ncaptured, (unsigned)ncaptured_now
        );
    }
    */

    alcCaptureStop(app.capture_device);

#ifdef __EMSCRIPTEN__
    // Restarting capture must zero the reported number of captured samples.
    // Works in our case because no processing takes place until the current
    // iteration yields to the javascript main loop.
    alcCaptureStart(app.capture_device);
    alcCaptureStop(app.capture_device);
    ALCint zeroed_ncaptured = 0xdead;
    alcGetIntegerv(app.capture_device, ALC_CAPTURE_SAMPLES, 1, &zeroed_ncaptured);
    if(zeroed_ncaptured) {
        fprintf(stderr, "Restarting capture didn't zero the reported number of available sample frames!\n");
    }
#endif

    ALCboolean could_close = alcCaptureCloseDevice(app.capture_device);
    if(!could_close) {
        fprintf(stderr, "Could not close device \"%s\"!\n", app.capture_device_name);
        end_test(EXIT_FAILURE);
    }

    // We're not as careful with playback - this is already tested
    // elsewhere.
    app.playback_device = alcOpenDevice(NULL);
    assert(app.playback_device);
    app.context = alcCreateContext(app.playback_device, NULL);
    assert(app.context);
    alcMakeContextCurrent(app.context);
    alGenBuffers(1, &app.buffer);
    alGenSources(1, &app.source);
    alBufferData(app.buffer, app.format, app.recorded, app.recorded_size, app.sample_rate);
    alSourcei(app.source, AL_BUFFER, app.buffer);

    free(app.recorded);

#ifdef __EMSCRIPTEN__
    EM_ASM(
        var succeed_btn = document.createElement('input');
        var fail_btn    = document.createElement('input');
        succeed_btn.type = fail_btn.type = 'button';
        succeed_btn.name = succeed_btn.value = 'Succeed';
        fail_btn.name = fail_btn.value = 'Fail';
        succeed_btn.onclick = function() {
            //Module.ccall('end_test', null, ['number'], [0]);
            _end_test(0);
        };
        fail_btn.onclick = function() {
            //Module.ccall('end_test', null, ['number'], [1]);
            _end_test(1);
        };
        document.body.appendChild(succeed_btn);
        document.body.appendChild(fail_btn);
    );
#endif

    app.is_playing_back = true;
    alSourcePlay(app.source);
    printf(
        "You should now hear the captured audio data.\n"
#ifdef __EMSCRIPTEN__
        "Press the [Succeed] button to end the test successfully, or the [Fail] button otherwise.\n"
#endif
    );
}

#ifdef __EMSCRIPTEN__
EMSCRIPTEN_KEEPALIVE
#endif
void ignite() {
    app.capture_device_name = alcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);

    app.capture_device = alcCaptureOpenDevice(
        app.capture_device_name, app.sample_rate, app.format, app.buffer_size
    );
    
    if(!app.capture_device) {
        ALCenum err = alcGetError(app.capture_device);
        fprintf(stderr, 
            "alcCaptureOpenDevice(\"%s\", sample_rate=%u, format=%s, "
            "buffer_size=%u) failed with ALC error %x (%s)\n", 
            app.capture_device_name, 
            (unsigned) app.sample_rate, alformat_string(app.format),
            (unsigned) app.buffer_size,
            (unsigned) err, alcGetString(NULL, err)
        );
        end_test(EXIT_FAILURE);
    }

    switch(app.format) {
    case AL_FORMAT_MONO8:          app.sample_size=1; app.nchannels=1; break;
    case AL_FORMAT_MONO16:         app.sample_size=2; app.nchannels=1; break;
    case AL_FORMAT_STEREO8:        app.sample_size=1; app.nchannels=2; break;
    case AL_FORMAT_STEREO16:       app.sample_size=2; app.nchannels=2; break;
#ifdef ASSUME_AL_FLOAT32
    case AL_FORMAT_MONO_FLOAT32:   app.sample_size=4; app.nchannels=1; break;
    case AL_FORMAT_STEREO_FLOAT32: app.sample_size=4; app.nchannels=2; break;
#endif
    }

    //allocate memory to store captured audio
    app.recorded_size = bytesForDuration(app.duration);
    app.recorded = malloc(app.recorded_size);
    if(!app.recorded) {
        fprintf(stderr, "Out of memory!\n");
        end_test(EXIT_FAILURE);
    }
    
    alcCaptureStart(app.capture_device);

#ifdef __EMSCRIPTEN__
    emscripten_set_main_loop_arg(iter, NULL, 0, 0);
#else
    for(;;) {
        iter(&app);
        usleep(durationForFrames(app.buffer_size) / 3);
    }
#endif
}

int main() {

    printf(
        "This test will attempt to capture %f seconds "
        "worth of audio data from your default audio "
        "input device, and then play it back.\n"
        , app.duration / 1000000.F
    );
#ifdef __EMSCRIPTEN__
    printf(
        "Press the [Start Recording] button below when you're ready, then "
        "allow audio capture when asked by the browser.\n"
        "No sample should be captured until that moment.\n"
    );
    EM_ASM(
        var btn = document.createElement('input');
        btn.type = 'button';
        btn.name = btn.value = 'Start recording';
        btn.onclick = function() {
            _ignite();
            document.body.removeChild(btn);
        };
        document.body.appendChild(btn);
    );
#else
    printf("Press [Enter] when you're ready.\n");
    getchar();
    ignite();
#endif

    return EXIT_SUCCESS;
}