]> git.tdb.fi Git - ext/openal.git/blob - examples/alrecord.c
Import OpenAL Soft 1.23.1 sources
[ext/openal.git] / examples / alrecord.c
1 /*
2  * OpenAL Recording Example
3  *
4  * Copyright (c) 2017 by Chris Robinson <chris.kcat@gmail.com>
5  *
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:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
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.
23  */
24
25 /* This file contains a relatively simple recorder. */
26
27 #include <string.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <errno.h>
31
32 #include "AL/al.h"
33 #include "AL/alc.h"
34 #include "AL/alext.h"
35
36 #include "common/alhelpers.h"
37
38 #include "win_main_utf8.h"
39
40
41 #if defined(_WIN64)
42 #define SZFMT "%I64u"
43 #elif defined(_WIN32)
44 #define SZFMT "%u"
45 #else
46 #define SZFMT "%zu"
47 #endif
48
49
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
55
56
57 static void fwrite16le(ALushort val, FILE *f)
58 {
59     ALubyte data[2];
60     data[0] = (ALubyte)(val&0xff);
61     data[1] = (ALubyte)(val>>8);
62     fwrite(data, 1, 2, f);
63 }
64
65 static void fwrite32le(ALuint val, FILE *f)
66 {
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);
73 }
74
75
76 typedef struct Recorder {
77     ALCdevice *mDevice;
78
79     FILE *mFile;
80     long mDataSizeOffset;
81     ALuint mDataSize;
82     float mRecTime;
83
84     ALuint mChannels;
85     ALuint mBits;
86     ALuint mSampleRate;
87     ALuint mFrameSize;
88     ALbyte *mBuffer;
89     ALsizei mBufferSize;
90 } Recorder;
91
92 int main(int argc, char **argv)
93 {
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;
107
108     progname = argv[0];
109     if(argc < 2)
110     {
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;
115     }
116
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;
128
129     argv++; argc--;
130     if(argc > 1 && strcmp(argv[0], "-device") == 0)
131     {
132         devname = argv[1];
133         argv += 2;
134         argc -= 2;
135     }
136
137     while(argc > 0)
138     {
139         char *end;
140         if(strcmp(argv[0], "--") == 0)
141             break;
142         else if(strcmp(argv[0], "--channels") == 0 || strcmp(argv[0], "-c") == 0)
143         {
144             if(argc < 2)
145             {
146                 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
147                 return 1;
148             }
149
150             recorder.mChannels = (ALuint)strtoul(argv[1], &end, 0);
151             if((recorder.mChannels != 1 && recorder.mChannels != 2) || (end && *end != '\0'))
152             {
153                 fprintf(stderr, "Invalid channels: %s\n", argv[1]);
154                 return 1;
155             }
156             argv += 2;
157             argc -= 2;
158         }
159         else if(strcmp(argv[0], "--bits") == 0 || strcmp(argv[0], "-b") == 0)
160         {
161             if(argc < 2)
162             {
163                 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
164                 return 1;
165             }
166
167             recorder.mBits = (ALuint)strtoul(argv[1], &end, 0);
168             if((recorder.mBits != 8 && recorder.mBits != 16 && recorder.mBits != 32) ||
169                (end && *end != '\0'))
170             {
171                 fprintf(stderr, "Invalid bit count: %s\n", argv[1]);
172                 return 1;
173             }
174             argv += 2;
175             argc -= 2;
176         }
177         else if(strcmp(argv[0], "--rate") == 0 || strcmp(argv[0], "-r") == 0)
178         {
179             if(argc < 2)
180             {
181                 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
182                 return 1;
183             }
184
185             recorder.mSampleRate = (ALuint)strtoul(argv[1], &end, 0);
186             if(!(recorder.mSampleRate >= 8000 && recorder.mSampleRate <= 96000) || (end && *end != '\0'))
187             {
188                 fprintf(stderr, "Invalid sample rate: %s\n", argv[1]);
189                 return 1;
190             }
191             argv += 2;
192             argc -= 2;
193         }
194         else if(strcmp(argv[0], "--time") == 0 || strcmp(argv[0], "-t") == 0)
195         {
196             if(argc < 2)
197             {
198                 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
199                 return 1;
200             }
201
202             recorder.mRecTime = strtof(argv[1], &end);
203             if(!(recorder.mRecTime >= 1.0f && recorder.mRecTime <= 10.0f) || (end && *end != '\0'))
204             {
205                 fprintf(stderr, "Invalid record time: %s\n", argv[1]);
206                 return 1;
207             }
208             argv += 2;
209             argc -= 2;
210         }
211         else if(strcmp(argv[0], "--outfile") == 0 || strcmp(argv[0], "-o") == 0)
212         {
213             if(argc < 2)
214             {
215                 fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
216                 return 1;
217             }
218
219             fname = argv[1];
220             argv += 2;
221             argc -= 2;
222         }
223         else if(strcmp(argv[0], "--help") == 0 || strcmp(argv[0], "-h") == 0)
224         {
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;
229         }
230         else
231         {
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;
236         }
237     }
238
239     recorder.mFrameSize = recorder.mChannels * recorder.mBits / 8;
240
241     format = AL_NONE;
242     if(recorder.mChannels == 1)
243     {
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;
250     }
251     else if(recorder.mChannels == 2)
252     {
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;
259     }
260
261     recorder.mDevice = alcCaptureOpenDevice(devname, recorder.mSampleRate, format, 32768);
262     if(!recorder.mDevice)
263     {
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
270         );
271         return 1;
272     }
273     fprintf(stderr, "Opened \"%s\"\n", alcGetString(
274         recorder.mDevice, ALC_CAPTURE_DEVICE_SPECIFIER
275     ));
276
277     recorder.mFile = fopen(fname, "wb");
278     if(!recorder.mFile)
279     {
280         fprintf(stderr, "Failed to open '%s' for writing\n", fname);
281         alcCaptureCloseDevice(recorder.mDevice);
282         return 1;
283     }
284
285     fputs("RIFF", recorder.mFile);
286     fwrite32le(0xFFFFFFFF, recorder.mFile); // 'RIFF' header len; filled in at close
287
288     fputs("WAVE", recorder.mFile);
289
290     fputs("fmt ", recorder.mFile);
291     fwrite32le(18, recorder.mFile); // 'fmt ' header len
292
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);
307
308     fputs("data", recorder.mFile);
309     fwrite32le(0xFFFFFFFF, recorder.mFile); // 'data' header len; filled in at close
310
311     recorder.mDataSizeOffset = ftell(recorder.mFile) - 4;
312     if(ferror(recorder.mFile) || recorder.mDataSizeOffset < 0)
313     {
314         fprintf(stderr, "Error writing header: %s\n", strerror(errno));
315         fclose(recorder.mFile);
316         alcCaptureCloseDevice(recorder.mDevice);
317         return 1;
318     }
319
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" : ""
325     );
326
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))
331     {
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)
336         {
337             al_nssleep(10000000);
338             continue;
339         }
340         if(count > recorder.mBufferSize)
341         {
342             ALbyte *data = calloc(recorder.mFrameSize, (ALuint)count);
343             free(recorder.mBuffer);
344             recorder.mBuffer = data;
345             recorder.mBufferSize = count;
346         }
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).
351          */
352         if(recorder.mBits == 16)
353         {
354             ALCint i;
355             for(i = 0;i < count*recorder.mChannels;i++)
356             {
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;
360             }
361         }
362         else if(recorder.mBits == 32)
363         {
364             ALCint i;
365             for(i = 0;i < count*recorder.mChannels;i++)
366             {
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;
373             }
374         }
375 #endif
376         recorder.mDataSize += (ALuint)fwrite(recorder.mBuffer, recorder.mFrameSize, (ALuint)count,
377                                              recorder.mFile);
378     }
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));
383
384     alcCaptureCloseDevice(recorder.mDevice);
385     recorder.mDevice = NULL;
386
387     free(recorder.mBuffer);
388     recorder.mBuffer = NULL;
389     recorder.mBufferSize = 0;
390
391     total_size = ftell(recorder.mFile);
392     if(fseek(recorder.mFile, recorder.mDataSizeOffset, SEEK_SET) == 0)
393     {
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);
397     }
398
399     fclose(recorder.mFile);
400     recorder.mFile = NULL;
401
402     return 0;
403 }