Add a comment about waiting to kill the event thread
[openal-soft.git] / examples / alrecord.c
blob43b26d359966909997ae4860a0fdb08ba28c6956
1 /*
2 * OpenAL Recording Example
4 * Copyright (c) 2017 by Chris Robinson <chris.kcat@gmail.com>
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
25 /* This file contains a relatively simple recorder. */
27 #include <string.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <math.h>
32 #include "AL/al.h"
33 #include "AL/alc.h"
34 #include "AL/alext.h"
36 #include "common/alhelpers.h"
39 #if defined(_WIN64)
40 #define SZFMT "%I64u"
41 #elif defined(_WIN32)
42 #define SZFMT "%u"
43 #else
44 #define SZFMT "%zu"
45 #endif
48 #if defined(_MSC_VER) && (_MSC_VER < 1900)
49 static float msvc_strtof(const char *str, char **end)
50 { return (float)strtod(str, end); }
51 #define strtof msvc_strtof
52 #endif
55 static void fwrite16le(ALushort val, FILE *f)
57 ALubyte data[2] = { val&0xff, (val>>8)&0xff };
58 fwrite(data, 1, 2, f);
61 static void fwrite32le(ALuint val, FILE *f)
63 ALubyte data[4] = { val&0xff, (val>>8)&0xff, (val>>16)&0xff, (val>>24)&0xff };
64 fwrite(data, 1, 4, f);
68 typedef struct Recorder {
69 ALCdevice *mDevice;
71 FILE *mFile;
72 long mDataSizeOffset;
73 ALuint mDataSize;
74 float mRecTime;
76 int mChannels;
77 int mBits;
78 int mSampleRate;
79 ALuint mFrameSize;
80 ALbyte *mBuffer;
81 ALsizei mBufferSize;
82 } Recorder;
84 int main(int argc, char **argv)
86 static const char optlist[] =
87 " --channels/-c <channels> Set channel count (1 or 2)\n"
88 " --bits/-b <bits> Set channel count (8, 16, or 32)\n"
89 " --rate/-r <rate> Set sample rate (8000 to 96000)\n"
90 " --time/-t <time> Time in seconds to record (1 to 10)\n"
91 " --outfile/-o <filename> Output filename (default: record.wav)";
92 const char *fname = "record.wav";
93 const char *devname = NULL;
94 const char *progname;
95 Recorder recorder;
96 long total_size;
97 ALenum format;
98 ALCenum err;
100 progname = argv[0];
101 if(argc < 2)
103 fprintf(stderr, "Record from a device to a wav file.\n\n"
104 "Usage: %s [-device <name>] [options...]\n\n"
105 "Available options:\n%s\n", progname, optlist);
106 return 0;
109 recorder.mDevice = NULL;
110 recorder.mFile = NULL;
111 recorder.mDataSizeOffset = 0;
112 recorder.mDataSize = 0;
113 recorder.mRecTime = 4.0f;
114 recorder.mChannels = 1;
115 recorder.mBits = 16;
116 recorder.mSampleRate = 44100;
117 recorder.mFrameSize = recorder.mChannels * recorder.mBits / 8;
118 recorder.mBuffer = NULL;
119 recorder.mBufferSize = 0;
121 argv++; argc--;
122 if(argc > 1 && strcmp(argv[0], "-device") == 0)
124 devname = argv[1];
125 argv += 2;
126 argc -= 2;
129 while(argc > 0)
131 char *end;
132 if(strcmp(argv[0], "--") == 0)
133 break;
134 else if(strcmp(argv[0], "--channels") == 0 || strcmp(argv[0], "-c") == 0)
136 if(!(argc > 1))
138 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
139 return 1;
142 recorder.mChannels = strtol(argv[1], &end, 0);
143 if((recorder.mChannels != 1 && recorder.mChannels != 2) || (end && *end != '\0'))
145 fprintf(stderr, "Invalid channels: %s\n", argv[1]);
146 return 1;
148 argv += 2;
149 argc -= 2;
151 else if(strcmp(argv[0], "--bits") == 0 || strcmp(argv[0], "-b") == 0)
153 if(!(argc > 1))
155 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
156 return 1;
159 recorder.mBits = strtol(argv[1], &end, 0);
160 if((recorder.mBits != 8 && recorder.mBits != 16 && recorder.mBits != 32) ||
161 (end && *end != '\0'))
163 fprintf(stderr, "Invalid bit count: %s\n", argv[1]);
164 return 1;
166 argv += 2;
167 argc -= 2;
169 else if(strcmp(argv[0], "--rate") == 0 || strcmp(argv[0], "-r") == 0)
171 if(!(argc > 1))
173 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
174 return 1;
177 recorder.mSampleRate = strtol(argv[1], &end, 0);
178 if(!(recorder.mSampleRate >= 8000 && recorder.mSampleRate <= 96000) || (end && *end != '\0'))
180 fprintf(stderr, "Invalid sample rate: %s\n", argv[1]);
181 return 1;
183 argv += 2;
184 argc -= 2;
186 else if(strcmp(argv[0], "--time") == 0 || strcmp(argv[0], "-t") == 0)
188 if(!(argc > 1))
190 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
191 return 1;
194 recorder.mRecTime = strtof(argv[1], &end);
195 if(!(recorder.mRecTime >= 1.0f && recorder.mRecTime <= 10.0f) || (end && *end != '\0'))
197 fprintf(stderr, "Invalid record time: %s\n", argv[1]);
198 return 1;
200 argv += 2;
201 argc -= 2;
203 else if(strcmp(argv[0], "--outfile") == 0 || strcmp(argv[0], "-o") == 0)
205 if(!(argc > 1))
207 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
208 return 1;
211 fname = argv[1];
212 argv += 2;
213 argc -= 2;
215 else if(strcmp(argv[0], "--help") == 0 || strcmp(argv[0], "-h") == 0)
217 fprintf(stderr, "Record from a device to a wav file.\n\n"
218 "Usage: %s [-device <name>] [options...]\n\n"
219 "Available options:\n%s\n", progname, optlist);
220 return 0;
222 else
224 fprintf(stderr, "Invalid option '%s'.\n\n"
225 "Usage: %s [-device <name>] [options...]\n\n"
226 "Available options:\n%s\n", argv[0], progname, optlist);
227 return 0;
231 recorder.mFrameSize = recorder.mChannels * recorder.mBits / 8;
233 format = AL_NONE;
234 if(recorder.mChannels == 1)
236 if(recorder.mBits == 8)
237 format = AL_FORMAT_MONO8;
238 else if(recorder.mBits == 16)
239 format = AL_FORMAT_MONO16;
240 else if(recorder.mBits == 32)
241 format = AL_FORMAT_MONO_FLOAT32;
243 else if(recorder.mChannels == 2)
245 if(recorder.mBits == 8)
246 format = AL_FORMAT_STEREO8;
247 else if(recorder.mBits == 16)
248 format = AL_FORMAT_STEREO16;
249 else if(recorder.mBits == 32)
250 format = AL_FORMAT_STEREO_FLOAT32;
253 recorder.mDevice = alcCaptureOpenDevice(devname, recorder.mSampleRate, format, 32768);
254 if(!recorder.mDevice)
256 fprintf(stderr, "Failed to open %s, %s %d-bit, %s, %dhz (%d samples)\n",
257 devname ? devname : "default device",
258 (recorder.mBits == 32) ? "Float" :
259 (recorder.mBits != 8) ? "Signed" : "Unsigned", recorder.mBits,
260 (recorder.mChannels == 1) ? "Mono" : "Stereo", recorder.mSampleRate,
261 32768
263 return 1;
265 fprintf(stderr, "Opened \"%s\"\n", alcGetString(
266 recorder.mDevice, ALC_CAPTURE_DEVICE_SPECIFIER
269 recorder.mFile = fopen(fname, "wb");
270 if(!recorder.mFile)
272 fprintf(stderr, "Failed to open '%s' for writing\n", fname);
273 alcCaptureCloseDevice(recorder.mDevice);
274 return 1;
277 fputs("RIFF", recorder.mFile);
278 fwrite32le(0xFFFFFFFF, recorder.mFile); // 'RIFF' header len; filled in at close
280 fputs("WAVE", recorder.mFile);
282 fputs("fmt ", recorder.mFile);
283 fwrite32le(18, recorder.mFile); // 'fmt ' header len
285 // 16-bit val, format type id (1 = integer PCM, 3 = float PCM)
286 fwrite16le((recorder.mBits == 32) ? 0x0003 : 0x0001, recorder.mFile);
287 // 16-bit val, channel count
288 fwrite16le(recorder.mChannels, recorder.mFile);
289 // 32-bit val, frequency
290 fwrite32le(recorder.mSampleRate, recorder.mFile);
291 // 32-bit val, bytes per second
292 fwrite32le(recorder.mSampleRate * recorder.mFrameSize, recorder.mFile);
293 // 16-bit val, frame size
294 fwrite16le(recorder.mFrameSize, recorder.mFile);
295 // 16-bit val, bits per sample
296 fwrite16le(recorder.mBits, recorder.mFile);
297 // 16-bit val, extra byte count
298 fwrite16le(0, recorder.mFile);
300 fputs("data", recorder.mFile);
301 fwrite32le(0xFFFFFFFF, recorder.mFile); // 'data' header len; filled in at close
303 recorder.mDataSizeOffset = ftell(recorder.mFile) - 4;
304 if(ferror(recorder.mFile) || recorder.mDataSizeOffset < 0)
306 fprintf(stderr, "Error writing header: %s\n", strerror(errno));
307 fclose(recorder.mFile);
308 alcCaptureCloseDevice(recorder.mDevice);
309 return 1;
312 fprintf(stderr, "Recording '%s', %s %d-bit, %s, %dhz (%g second%s)\n", fname,
313 (recorder.mBits == 32) ? "Float" :
314 (recorder.mBits != 8) ? "Signed" : "Unsigned", recorder.mBits,
315 (recorder.mChannels == 1) ? "Mono" : "Stereo", recorder.mSampleRate,
316 recorder.mRecTime, (recorder.mRecTime != 1.0f) ? "s" : ""
319 alcCaptureStart(recorder.mDevice);
320 while((double)recorder.mDataSize/(double)recorder.mSampleRate < recorder.mRecTime &&
321 (err=alcGetError(recorder.mDevice)) == ALC_NO_ERROR && !ferror(recorder.mFile))
323 ALCint count = 0;
324 fprintf(stderr, "\rCaptured %u samples", recorder.mDataSize);
325 alcGetIntegerv(recorder.mDevice, ALC_CAPTURE_SAMPLES, 1, &count);
326 if(count < 1)
328 al_nssleep(10000000);
329 continue;
331 if(count > recorder.mBufferSize)
333 ALbyte *data = calloc(recorder.mFrameSize, count);
334 free(recorder.mBuffer);
335 recorder.mBuffer = data;
336 recorder.mBufferSize = count;
338 alcCaptureSamples(recorder.mDevice, recorder.mBuffer, count);
339 #if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN
340 /* Byteswap multibyte samples on big-endian systems (wav needs little-
341 * endian, and OpenAL gives the system's native-endian).
343 if(recorder.mBits == 16)
345 ALCint i;
346 for(i = 0;i < count*recorder.mChannels;i++)
348 ALbyte b = recorder.mBuffer[i*2 + 0];
349 recorder.mBuffer[i*2 + 0] = recorder.mBuffer[i*2 + 1];
350 recorder.mBuffer[i*2 + 1] = b;
353 else if(recorder.mBits == 32)
355 ALCint i;
356 for(i = 0;i < count*recorder.mChannels;i++)
358 ALbyte b0 = recorder.mBuffer[i*4 + 0];
359 ALbyte b1 = recorder.mBuffer[i*4 + 1];
360 recorder.mBuffer[i*4 + 0] = recorder.mBuffer[i*4 + 3];
361 recorder.mBuffer[i*4 + 1] = recorder.mBuffer[i*4 + 2];
362 recorder.mBuffer[i*4 + 2] = b1;
363 recorder.mBuffer[i*4 + 3] = b0;
366 #endif
367 recorder.mDataSize += (ALuint)fwrite(recorder.mBuffer, recorder.mFrameSize, count,
368 recorder.mFile);
370 alcCaptureStop(recorder.mDevice);
371 fprintf(stderr, "\rCaptured %u samples\n", recorder.mDataSize);
372 if(err != ALC_NO_ERROR)
373 fprintf(stderr, "Got device error 0x%04x: %s\n", err, alcGetString(recorder.mDevice, err));
375 alcCaptureCloseDevice(recorder.mDevice);
376 recorder.mDevice = NULL;
378 free(recorder.mBuffer);
379 recorder.mBuffer = NULL;
380 recorder.mBufferSize = 0;
382 total_size = ftell(recorder.mFile);
383 if(fseek(recorder.mFile, recorder.mDataSizeOffset, SEEK_SET) == 0)
385 fwrite32le(recorder.mDataSize*recorder.mFrameSize, recorder.mFile);
386 if(fseek(recorder.mFile, 4, SEEK_SET) == 0)
387 fwrite32le(total_size - 8, recorder.mFile);
390 fclose(recorder.mFile);
391 recorder.mFile = NULL;
393 return 0;