Don't over-allocate the active effect slot array
[openal-soft.git] / examples / alrecord.c
blob2a1fbd2f494b17db37f83e01d5e18bfa5125b64a
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 <errno.h>
32 #include "AL/al.h"
33 #include "AL/alc.h"
34 #include "AL/alext.h"
36 #include "common/alhelpers.h"
38 #include "win_main_utf8.h"
41 #if defined(_WIN64)
42 #define SZFMT "%I64u"
43 #elif defined(_WIN32)
44 #define SZFMT "%u"
45 #else
46 #define SZFMT "%zu"
47 #endif
50 #if defined(_MSC_VER) && (_MSC_VER < 1900)
51 static float msvc_strtof(const char *str, char **end)
52 { return (float)strtod(str, end); }
53 #define strtof msvc_strtof
54 #endif
57 static void fwrite16le(ALushort val, FILE *f)
59 ALubyte data[2];
60 data[0] = (ALubyte)(val&0xff);
61 data[1] = (ALubyte)(val>>8);
62 fwrite(data, 1, 2, f);
65 static void fwrite32le(ALuint val, FILE *f)
67 ALubyte data[4];
68 data[0] = (ALubyte)(val&0xff);
69 data[1] = (ALubyte)((val>>8)&0xff);
70 data[2] = (ALubyte)((val>>16)&0xff);
71 data[3] = (ALubyte)(val>>24);
72 fwrite(data, 1, 4, f);
76 typedef struct Recorder {
77 ALCdevice *mDevice;
79 FILE *mFile;
80 long mDataSizeOffset;
81 ALuint mDataSize;
82 float mRecTime;
84 ALuint mChannels;
85 ALuint mBits;
86 ALuint mSampleRate;
87 ALuint mFrameSize;
88 ALbyte *mBuffer;
89 ALsizei mBufferSize;
90 } Recorder;
92 int main(int argc, char **argv)
94 static const char optlist[] =
95 " --channels/-c <channels> Set channel count (1 or 2)\n"
96 " --bits/-b <bits> Set channel count (8, 16, or 32)\n"
97 " --rate/-r <rate> Set sample rate (8000 to 96000)\n"
98 " --time/-t <time> Time in seconds to record (1 to 10)\n"
99 " --outfile/-o <filename> Output filename (default: record.wav)";
100 const char *fname = "record.wav";
101 const char *devname = NULL;
102 const char *progname;
103 Recorder recorder;
104 long total_size;
105 ALenum format;
106 ALCenum err;
108 progname = argv[0];
109 if(argc < 2)
111 fprintf(stderr, "Record from a device to a wav file.\n\n"
112 "Usage: %s [-device <name>] [options...]\n\n"
113 "Available options:\n%s\n", progname, optlist);
114 return 0;
117 recorder.mDevice = NULL;
118 recorder.mFile = NULL;
119 recorder.mDataSizeOffset = 0;
120 recorder.mDataSize = 0;
121 recorder.mRecTime = 4.0f;
122 recorder.mChannels = 1;
123 recorder.mBits = 16;
124 recorder.mSampleRate = 44100;
125 recorder.mFrameSize = recorder.mChannels * recorder.mBits / 8;
126 recorder.mBuffer = NULL;
127 recorder.mBufferSize = 0;
129 argv++; argc--;
130 if(argc > 1 && strcmp(argv[0], "-device") == 0)
132 devname = argv[1];
133 argv += 2;
134 argc -= 2;
137 while(argc > 0)
139 char *end;
140 if(strcmp(argv[0], "--") == 0)
141 break;
142 if(strcmp(argv[0], "--channels") == 0 || strcmp(argv[0], "-c") == 0)
144 if(argc < 2)
146 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
147 return 1;
150 recorder.mChannels = (ALuint)strtoul(argv[1], &end, 0);
151 if((recorder.mChannels != 1 && recorder.mChannels != 2) || (end && *end != '\0'))
153 fprintf(stderr, "Invalid channels: %s\n", argv[1]);
154 return 1;
156 argv += 2;
157 argc -= 2;
159 else if(strcmp(argv[0], "--bits") == 0 || strcmp(argv[0], "-b") == 0)
161 if(argc < 2)
163 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
164 return 1;
167 recorder.mBits = (ALuint)strtoul(argv[1], &end, 0);
168 if((recorder.mBits != 8 && recorder.mBits != 16 && recorder.mBits != 32) ||
169 (end && *end != '\0'))
171 fprintf(stderr, "Invalid bit count: %s\n", argv[1]);
172 return 1;
174 argv += 2;
175 argc -= 2;
177 else if(strcmp(argv[0], "--rate") == 0 || strcmp(argv[0], "-r") == 0)
179 if(argc < 2)
181 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
182 return 1;
185 recorder.mSampleRate = (ALuint)strtoul(argv[1], &end, 0);
186 if(!(recorder.mSampleRate >= 8000 && recorder.mSampleRate <= 96000) || (end && *end != '\0'))
188 fprintf(stderr, "Invalid sample rate: %s\n", argv[1]);
189 return 1;
191 argv += 2;
192 argc -= 2;
194 else if(strcmp(argv[0], "--time") == 0 || strcmp(argv[0], "-t") == 0)
196 if(argc < 2)
198 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
199 return 1;
202 recorder.mRecTime = strtof(argv[1], &end);
203 if(!(recorder.mRecTime >= 1.0f && recorder.mRecTime <= 10.0f) || (end && *end != '\0'))
205 fprintf(stderr, "Invalid record time: %s\n", argv[1]);
206 return 1;
208 argv += 2;
209 argc -= 2;
211 else if(strcmp(argv[0], "--outfile") == 0 || strcmp(argv[0], "-o") == 0)
213 if(argc < 2)
215 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
216 return 1;
219 fname = argv[1];
220 argv += 2;
221 argc -= 2;
223 else if(strcmp(argv[0], "--help") == 0 || strcmp(argv[0], "-h") == 0)
225 fprintf(stderr, "Record from a device to a wav file.\n\n"
226 "Usage: %s [-device <name>] [options...]\n\n"
227 "Available options:\n%s\n", progname, optlist);
228 return 0;
230 else
232 fprintf(stderr, "Invalid option '%s'.\n\n"
233 "Usage: %s [-device <name>] [options...]\n\n"
234 "Available options:\n%s\n", argv[0], progname, optlist);
235 return 0;
239 recorder.mFrameSize = recorder.mChannels * recorder.mBits / 8;
241 format = AL_NONE;
242 if(recorder.mChannels == 1)
244 if(recorder.mBits == 8)
245 format = AL_FORMAT_MONO8;
246 else if(recorder.mBits == 16)
247 format = AL_FORMAT_MONO16;
248 else if(recorder.mBits == 32)
249 format = AL_FORMAT_MONO_FLOAT32;
251 else if(recorder.mChannels == 2)
253 if(recorder.mBits == 8)
254 format = AL_FORMAT_STEREO8;
255 else if(recorder.mBits == 16)
256 format = AL_FORMAT_STEREO16;
257 else if(recorder.mBits == 32)
258 format = AL_FORMAT_STEREO_FLOAT32;
261 recorder.mDevice = alcCaptureOpenDevice(devname, recorder.mSampleRate, format, 32768);
262 if(!recorder.mDevice)
264 fprintf(stderr, "Failed to open %s, %s %d-bit, %s, %dhz (%d samples)\n",
265 devname ? devname : "default device",
266 (recorder.mBits == 32) ? "Float" :
267 (recorder.mBits != 8) ? "Signed" : "Unsigned", recorder.mBits,
268 (recorder.mChannels == 1) ? "Mono" : "Stereo", recorder.mSampleRate,
269 32768
271 return 1;
273 fprintf(stderr, "Opened \"%s\"\n", alcGetString(
274 recorder.mDevice, ALC_CAPTURE_DEVICE_SPECIFIER
277 recorder.mFile = fopen(fname, "wb");
278 if(!recorder.mFile)
280 fprintf(stderr, "Failed to open '%s' for writing\n", fname);
281 alcCaptureCloseDevice(recorder.mDevice);
282 return 1;
285 fputs("RIFF", recorder.mFile);
286 fwrite32le(0xFFFFFFFF, recorder.mFile); // 'RIFF' header len; filled in at close
288 fputs("WAVE", recorder.mFile);
290 fputs("fmt ", recorder.mFile);
291 fwrite32le(18, recorder.mFile); // 'fmt ' header len
293 // 16-bit val, format type id (1 = integer PCM, 3 = float PCM)
294 fwrite16le((recorder.mBits == 32) ? 0x0003 : 0x0001, recorder.mFile);
295 // 16-bit val, channel count
296 fwrite16le((ALushort)recorder.mChannels, recorder.mFile);
297 // 32-bit val, frequency
298 fwrite32le(recorder.mSampleRate, recorder.mFile);
299 // 32-bit val, bytes per second
300 fwrite32le(recorder.mSampleRate * recorder.mFrameSize, recorder.mFile);
301 // 16-bit val, frame size
302 fwrite16le((ALushort)recorder.mFrameSize, recorder.mFile);
303 // 16-bit val, bits per sample
304 fwrite16le((ALushort)recorder.mBits, recorder.mFile);
305 // 16-bit val, extra byte count
306 fwrite16le(0, recorder.mFile);
308 fputs("data", recorder.mFile);
309 fwrite32le(0xFFFFFFFF, recorder.mFile); // 'data' header len; filled in at close
311 recorder.mDataSizeOffset = ftell(recorder.mFile) - 4;
312 if(ferror(recorder.mFile) || recorder.mDataSizeOffset < 0)
314 fprintf(stderr, "Error writing header: %s\n", strerror(errno));
315 fclose(recorder.mFile);
316 alcCaptureCloseDevice(recorder.mDevice);
317 return 1;
320 fprintf(stderr, "Recording '%s', %s %d-bit, %s, %dhz (%g second%s)\n", fname,
321 (recorder.mBits == 32) ? "Float" :
322 (recorder.mBits != 8) ? "Signed" : "Unsigned", recorder.mBits,
323 (recorder.mChannels == 1) ? "Mono" : "Stereo", recorder.mSampleRate,
324 recorder.mRecTime, (recorder.mRecTime != 1.0f) ? "s" : ""
327 err = ALC_NO_ERROR;
328 alcCaptureStart(recorder.mDevice);
329 while((double)recorder.mDataSize/(double)recorder.mSampleRate < recorder.mRecTime &&
330 (err=alcGetError(recorder.mDevice)) == ALC_NO_ERROR && !ferror(recorder.mFile))
332 ALCint count = 0;
333 fprintf(stderr, "\rCaptured %u samples", recorder.mDataSize);
334 alcGetIntegerv(recorder.mDevice, ALC_CAPTURE_SAMPLES, 1, &count);
335 if(count < 1)
337 al_nssleep(10000000);
338 continue;
340 if(count > recorder.mBufferSize)
342 ALbyte *data = calloc(recorder.mFrameSize, (ALuint)count);
343 free(recorder.mBuffer);
344 recorder.mBuffer = data;
345 recorder.mBufferSize = count;
347 alcCaptureSamples(recorder.mDevice, recorder.mBuffer, count);
348 #if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN
349 /* Byteswap multibyte samples on big-endian systems (wav needs little-
350 * endian, and OpenAL gives the system's native-endian).
352 if(recorder.mBits == 16)
354 ALCint i;
355 for(i = 0;i < count*recorder.mChannels;i++)
357 ALbyte b = recorder.mBuffer[i*2 + 0];
358 recorder.mBuffer[i*2 + 0] = recorder.mBuffer[i*2 + 1];
359 recorder.mBuffer[i*2 + 1] = b;
362 else if(recorder.mBits == 32)
364 ALCint i;
365 for(i = 0;i < count*recorder.mChannels;i++)
367 ALbyte b0 = recorder.mBuffer[i*4 + 0];
368 ALbyte b1 = recorder.mBuffer[i*4 + 1];
369 recorder.mBuffer[i*4 + 0] = recorder.mBuffer[i*4 + 3];
370 recorder.mBuffer[i*4 + 1] = recorder.mBuffer[i*4 + 2];
371 recorder.mBuffer[i*4 + 2] = b1;
372 recorder.mBuffer[i*4 + 3] = b0;
375 #endif
376 recorder.mDataSize += (ALuint)fwrite(recorder.mBuffer, recorder.mFrameSize, (ALuint)count,
377 recorder.mFile);
379 alcCaptureStop(recorder.mDevice);
380 fprintf(stderr, "\rCaptured %u samples\n", recorder.mDataSize);
381 if(err != ALC_NO_ERROR)
382 fprintf(stderr, "Got device error 0x%04x: %s\n", err, alcGetString(recorder.mDevice, err));
384 alcCaptureCloseDevice(recorder.mDevice);
385 recorder.mDevice = NULL;
387 free(recorder.mBuffer);
388 recorder.mBuffer = NULL;
389 recorder.mBufferSize = 0;
391 total_size = ftell(recorder.mFile);
392 if(fseek(recorder.mFile, recorder.mDataSizeOffset, SEEK_SET) == 0)
394 fwrite32le(recorder.mDataSize*recorder.mFrameSize, recorder.mFile);
395 if(fseek(recorder.mFile, 4, SEEK_SET) == 0)
396 fwrite32le((ALuint)total_size - 8, recorder.mFile);
399 fclose(recorder.mFile);
400 recorder.mFile = NULL;
402 return 0;