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
25 /* This file contains a relatively simple recorder. */
36 #include "common/alhelpers.h"
38 #include "win_main_utf8.h"
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
57 static void fwrite16le(ALushort val
, FILE *f
)
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
)
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
{
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
;
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
);
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;
124 recorder
.mSampleRate
= 44100;
125 recorder
.mFrameSize
= recorder
.mChannels
* recorder
.mBits
/ 8;
126 recorder
.mBuffer
= NULL
;
127 recorder
.mBufferSize
= 0;
130 if(argc
> 1 && strcmp(argv
[0], "-device") == 0)
140 if(strcmp(argv
[0], "--") == 0)
142 else if(strcmp(argv
[0], "--channels") == 0 || strcmp(argv
[0], "-c") == 0)
146 fprintf(stderr
, "Missing argument for option: %s\n", argv
[0]);
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]);
159 else if(strcmp(argv
[0], "--bits") == 0 || strcmp(argv
[0], "-b") == 0)
163 fprintf(stderr
, "Missing argument for option: %s\n", argv
[0]);
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]);
177 else if(strcmp(argv
[0], "--rate") == 0 || strcmp(argv
[0], "-r") == 0)
181 fprintf(stderr
, "Missing argument for option: %s\n", argv
[0]);
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]);
194 else if(strcmp(argv
[0], "--time") == 0 || strcmp(argv
[0], "-t") == 0)
198 fprintf(stderr
, "Missing argument for option: %s\n", argv
[0]);
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]);
211 else if(strcmp(argv
[0], "--outfile") == 0 || strcmp(argv
[0], "-o") == 0)
215 fprintf(stderr
, "Missing argument for option: %s\n", argv
[0]);
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
);
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
);
239 recorder
.mFrameSize
= recorder
.mChannels
* recorder
.mBits
/ 8;
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
,
273 fprintf(stderr
, "Opened \"%s\"\n", alcGetString(
274 recorder
.mDevice
, ALC_CAPTURE_DEVICE_SPECIFIER
277 recorder
.mFile
= fopen(fname
, "wb");
280 fprintf(stderr
, "Failed to open '%s' for writing\n", fname
);
281 alcCaptureCloseDevice(recorder
.mDevice
);
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
);
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" : ""
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
))
333 fprintf(stderr
, "\rCaptured %u samples", recorder
.mDataSize
);
334 alcGetIntegerv(recorder
.mDevice
, ALC_CAPTURE_SAMPLES
, 1, &count
);
337 al_nssleep(10000000);
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)
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)
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
;
376 recorder
.mDataSize
+= (ALuint
)fwrite(recorder
.mBuffer
, recorder
.mFrameSize
, (ALuint
)count
,
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
;