Forward Sample_ALuint to Sample_ALint
[openal-soft.git] / examples / alrecord.c
blobda8d20ceb6e256480c14e4c43aeecd3c94eb320d
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 static void fwrite16le(ALushort val, FILE *f)
50 ALubyte data[2] = { val&0xff, (val>>8)&0xff };
51 fwrite(data, 1, 2, f);
54 static void fwrite32le(ALuint val, FILE *f)
56 ALubyte data[4] = { val&0xff, (val>>8)&0xff, (val>>16)&0xff, (val>>24)&0xff };
57 fwrite(data, 1, 4, f);
61 typedef struct Recorder {
62 ALCdevice *mDevice;
64 FILE *mFile;
65 long mDataSizeOffset;
66 size_t mDataSize;
67 float mRecTime;
69 int mChannels;
70 int mBits;
71 int mSampleRate;
72 size_t mFrameSize;
73 ALbyte *mBuffer;
74 ALsizei mBufferSize;
75 } Recorder;
77 int main(int argc, char **argv)
79 static const char optlist[] =
80 " --channels/-c <channels> Set channel count (1 or 2)\n"
81 " --bits/-b <bits> Set channel count (8 or 16)\n"
82 " --rate/-r <rate> Set sample rate (8000 to 96000)\n"
83 " --time/-t <time> Time in seconds to record (1 to 10)\n"
84 " --outfile/-o <filename> Output filename (default: record.wav)";
85 const char *fname = "record.wav";
86 const char *devname = NULL;
87 const char *progname;
88 Recorder recorder;
89 long total_size;
90 ALenum format;
91 ALCenum err;
93 progname = argv[0];
94 if(argc < 2)
96 fprintf(stderr, "Record from a device to a wav file.\n\n"
97 "Usage: %s [-device <name>] [options...]\n\n"
98 "Available options:\n%s\n", progname, optlist);
99 return 0;
102 recorder.mDevice = NULL;
103 recorder.mFile = NULL;
104 recorder.mDataSizeOffset = 0;
105 recorder.mDataSize = 0;
106 recorder.mRecTime = 4.0f;
107 recorder.mChannels = 1;
108 recorder.mBits = 16;
109 recorder.mSampleRate = 44100;
110 recorder.mFrameSize = recorder.mChannels * recorder.mBits / 8;
111 recorder.mBuffer = NULL;
112 recorder.mBufferSize = 0;
114 argv++; argc--;
115 if(argc > 1 && strcmp(argv[0], "-device") == 0)
117 devname = argv[1];
118 argv += 2;
119 argc -= 2;
122 while(argc > 0)
124 char *end;
125 if(strcmp(argv[0], "--") == 0)
126 break;
127 else if(strcmp(argv[0], "--channels") == 0 || strcmp(argv[0], "-c") == 0)
129 if(!(argc > 1))
131 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
132 return 1;
135 recorder.mChannels = strtol(argv[1], &end, 0);
136 if((recorder.mChannels != 1 && recorder.mChannels != 2) || (end && *end != '\0'))
138 fprintf(stderr, "Invalid channels: %s\n", argv[1]);
139 return 1;
141 argv += 2;
142 argc -= 2;
144 else if(strcmp(argv[0], "--bits") == 0 || strcmp(argv[0], "-b") == 0)
146 if(!(argc > 1))
148 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
149 return 1;
152 recorder.mBits = strtol(argv[1], &end, 0);
153 if((recorder.mBits != 8 && recorder.mBits != 16) || (end && *end != '\0'))
155 fprintf(stderr, "Invalid bit count: %s\n", argv[1]);
156 return 1;
158 argv += 2;
159 argc -= 2;
161 else if(strcmp(argv[0], "--rate") == 0 || strcmp(argv[0], "-r") == 0)
163 if(!(argc > 1))
165 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
166 return 1;
169 recorder.mSampleRate = strtol(argv[1], &end, 0);
170 if(!(recorder.mSampleRate >= 8000 && recorder.mSampleRate <= 96000) || (end && *end != '\0'))
172 fprintf(stderr, "Invalid sample rate: %s\n", argv[1]);
173 return 1;
175 argv += 2;
176 argc -= 2;
178 else if(strcmp(argv[0], "--time") == 0 || strcmp(argv[0], "-t") == 0)
180 if(!(argc > 1))
182 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
183 return 1;
186 recorder.mRecTime = strtod(argv[1], &end);
187 if(!(recorder.mRecTime >= 1.0f && recorder.mRecTime <= 10.0f) || (end && *end != '\0'))
189 fprintf(stderr, "Invalid record time: %s\n", argv[1]);
190 return 1;
192 argv += 2;
193 argc -= 2;
195 else if(strcmp(argv[0], "--outfile") == 0 || strcmp(argv[0], "-o") == 0)
197 if(!(argc > 1))
199 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
200 return 1;
203 fname = argv[1];
204 argv += 2;
205 argc -= 2;
207 else if(strcmp(argv[0], "--help") == 0 || strcmp(argv[0], "-h") == 0)
209 fprintf(stderr, "Record from a device to a wav file.\n\n"
210 "Usage: %s [-device <name>] [options...]\n\n"
211 "Available options:\n%s\n", progname, optlist);
212 return 0;
214 else
216 fprintf(stderr, "Invalid option '%s'.\n\n"
217 "Usage: %s [-device <name>] [options...]\n\n"
218 "Available options:\n%s\n", argv[0], progname, optlist);
219 return 0;
223 recorder.mFrameSize = recorder.mChannels * recorder.mBits / 8;
225 format = AL_NONE;
226 if(recorder.mChannels == 1)
228 if(recorder.mBits == 8)
229 format = AL_FORMAT_MONO8;
230 else if(recorder.mBits == 16)
231 format = AL_FORMAT_MONO16;
233 else if(recorder.mChannels == 2)
235 if(recorder.mBits == 8)
236 format = AL_FORMAT_STEREO8;
237 else if(recorder.mBits == 16)
238 format = AL_FORMAT_STEREO16;
241 recorder.mDevice = alcCaptureOpenDevice(devname, recorder.mSampleRate, format, 32768);
242 if(!recorder.mDevice)
244 fprintf(stderr, "Failed to open %s, %s %d-bit %dhz %d samples\n", devname?devname:"default device",
245 (recorder.mChannels == 1) ? "mono" : "stereo", recorder.mBits,recorder.mSampleRate,
246 32768
248 return 1;
250 fprintf(stderr, "Opened \"%s\"\n", alcGetString(
251 recorder.mDevice, ALC_CAPTURE_DEVICE_SPECIFIER
254 recorder.mFile = fopen(fname, "wb");
255 if(!recorder.mFile)
257 fprintf(stderr, "Failed to open '%s' for writing\n", fname);
258 alcCaptureCloseDevice(recorder.mDevice);
259 return 1;
262 fputs("RIFF", recorder.mFile);
263 fwrite32le(0xFFFFFFFF, recorder.mFile); // 'RIFF' header len; filled in at close
265 fputs("WAVE", recorder.mFile);
267 fputs("fmt ", recorder.mFile);
268 fwrite32le(18, recorder.mFile); // 'fmt ' header len
270 // 16-bit val, format type id
271 fwrite16le(0x0001, recorder.mFile);
272 // 16-bit val, channel count
273 fwrite16le(recorder.mChannels, recorder.mFile);
274 // 32-bit val, frequency
275 fwrite32le(recorder.mSampleRate, recorder.mFile);
276 // 32-bit val, bytes per second
277 fwrite32le(recorder.mSampleRate * recorder.mFrameSize, recorder.mFile);
278 // 16-bit val, frame size
279 fwrite16le(recorder.mFrameSize, recorder.mFile);
280 // 16-bit val, bits per sample
281 fwrite16le(recorder.mBits, recorder.mFile);
282 // 16-bit val, extra byte count
283 fwrite16le(0, recorder.mFile);
285 fputs("data", recorder.mFile);
286 fwrite32le(0xFFFFFFFF, recorder.mFile); // 'data' header len; filled in at close
288 recorder.mDataSizeOffset = ftell(recorder.mFile) - 4;
289 if(ferror(recorder.mFile) || recorder.mDataSizeOffset < 0)
291 fprintf(stderr, "Error writing header: %s\n", strerror(errno));
292 fclose(recorder.mFile);
293 alcCaptureCloseDevice(recorder.mDevice);
294 return 1;
297 fprintf(stderr, "Recording '%s', %s %d-bit %dhz, %f sec\n", fname,
298 (recorder.mChannels == 1) ? "mono" : "stereo", recorder.mBits,recorder.mSampleRate,
299 ceilf(recorder.mRecTime)
302 alcCaptureStart(recorder.mDevice);
303 while((double)recorder.mDataSize/(double)recorder.mSampleRate < recorder.mRecTime &&
304 (err=alcGetError(recorder.mDevice)) == ALC_NO_ERROR && !ferror(recorder.mFile))
306 ALCint count = 0;
307 fprintf(stderr, "\rCaptured "SZFMT" samples", recorder.mDataSize);
308 alcGetIntegerv(recorder.mDevice, ALC_CAPTURE_SAMPLES, 1, &count);
309 if(count < 1)
311 al_nssleep(10000000);
312 continue;
314 if(count > recorder.mBufferSize)
316 ALbyte *data = calloc(recorder.mFrameSize, count);
317 free(recorder.mBuffer);
318 recorder.mBuffer = data;
319 recorder.mBufferSize = count;
321 alcCaptureSamples(recorder.mDevice, recorder.mBuffer, count);
322 #if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN
323 if(recorder.mBits == 16)
325 ALCint i;
326 /* Byteswap short samples on big-endian systems (wav needs little-
327 * endian, and OpenAL gives the system's native-endian).
329 for(i = 0;i < count*recorder.mChannels;i++)
331 ALbyte b = recorder.mBuffer[i*2 + 0];
332 recorder.mBuffer[i*2 + 0] = recorder.mBuffer[i*2 + 1];
333 recorder.mBuffer[i*2 + 1] = b;
336 #endif
337 recorder.mDataSize += fwrite(recorder.mBuffer, recorder.mFrameSize, count, recorder.mFile);
339 alcCaptureStop(recorder.mDevice);
340 fprintf(stderr, "\rCaptured "SZFMT" samples\n", recorder.mDataSize);
341 if(err != ALC_NO_ERROR)
342 fprintf(stderr, "Got device error 0x%04x: %s\n", err, alcGetString(recorder.mDevice, err));
344 alcCaptureCloseDevice(recorder.mDevice);
345 recorder.mDevice = NULL;
347 free(recorder.mBuffer);
348 recorder.mBuffer = NULL;
349 recorder.mBufferSize = 0;
351 total_size = ftell(recorder.mFile);
352 if(fseek(recorder.mFile, recorder.mDataSizeOffset, SEEK_SET) == 0)
354 fwrite32le(recorder.mDataSize*recorder.mFrameSize, recorder.mFile);
355 if(fseek(recorder.mFile, 4, SEEK_SET) == 0)
356 fwrite32le(total_size - 8, recorder.mFile);
359 fclose(recorder.mFile);
360 recorder.mFile = NULL;
362 return 0;