aboutsummaryrefslogtreecommitdiffstats
path: root/examples/alrecord.c
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2017-06-23 05:19:24 -0700
committerChris Robinson <[email protected]>2017-06-23 05:19:24 -0700
commite07166e93cf392e3dac0a8ee71696a8d6d5114fc (patch)
tree628ed0885ff0f01fb181617293f2daec6303dced /examples/alrecord.c
parentd1077795deb8367d8e6fc39ecfc5f51b3c39d9e5 (diff)
Add a recording example app
Diffstat (limited to 'examples/alrecord.c')
-rw-r--r--examples/alrecord.c363
1 files changed, 363 insertions, 0 deletions
diff --git a/examples/alrecord.c b/examples/alrecord.c
new file mode 100644
index 00000000..da8d20ce
--- /dev/null
+++ b/examples/alrecord.c
@@ -0,0 +1,363 @@
+/*
+ * OpenAL Recording Example
+ *
+ * Copyright (c) 2017 by Chris Robinson <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/* This file contains a relatively simple recorder. */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/alext.h"
+
+#include "common/alhelpers.h"
+
+
+#if defined(_WIN64)
+#define SZFMT "%I64u"
+#elif defined(_WIN32)
+#define SZFMT "%u"
+#else
+#define SZFMT "%zu"
+#endif
+
+
+static void fwrite16le(ALushort val, FILE *f)
+{
+ ALubyte data[2] = { val&0xff, (val>>8)&0xff };
+ fwrite(data, 1, 2, f);
+}
+
+static void fwrite32le(ALuint val, FILE *f)
+{
+ ALubyte data[4] = { val&0xff, (val>>8)&0xff, (val>>16)&0xff, (val>>24)&0xff };
+ fwrite(data, 1, 4, f);
+}
+
+
+typedef struct Recorder {
+ ALCdevice *mDevice;
+
+ FILE *mFile;
+ long mDataSizeOffset;
+ size_t mDataSize;
+ float mRecTime;
+
+ int mChannels;
+ int mBits;
+ int mSampleRate;
+ size_t mFrameSize;
+ ALbyte *mBuffer;
+ ALsizei mBufferSize;
+} Recorder;
+
+int main(int argc, char **argv)
+{
+ static const char optlist[] =
+" --channels/-c <channels> Set channel count (1 or 2)\n"
+" --bits/-b <bits> Set channel count (8 or 16)\n"
+" --rate/-r <rate> Set sample rate (8000 to 96000)\n"
+" --time/-t <time> Time in seconds to record (1 to 10)\n"
+" --outfile/-o <filename> Output filename (default: record.wav)";
+ const char *fname = "record.wav";
+ const char *devname = NULL;
+ const char *progname;
+ Recorder recorder;
+ long total_size;
+ ALenum format;
+ ALCenum err;
+
+ progname = argv[0];
+ if(argc < 2)
+ {
+ fprintf(stderr, "Record from a device to a wav file.\n\n"
+ "Usage: %s [-device <name>] [options...]\n\n"
+ "Available options:\n%s\n", progname, optlist);
+ return 0;
+ }
+
+ recorder.mDevice = NULL;
+ recorder.mFile = NULL;
+ recorder.mDataSizeOffset = 0;
+ recorder.mDataSize = 0;
+ recorder.mRecTime = 4.0f;
+ recorder.mChannels = 1;
+ recorder.mBits = 16;
+ recorder.mSampleRate = 44100;
+ recorder.mFrameSize = recorder.mChannels * recorder.mBits / 8;
+ recorder.mBuffer = NULL;
+ recorder.mBufferSize = 0;
+
+ argv++; argc--;
+ if(argc > 1 && strcmp(argv[0], "-device") == 0)
+ {
+ devname = argv[1];
+ argv += 2;
+ argc -= 2;
+ }
+
+ while(argc > 0)
+ {
+ char *end;
+ if(strcmp(argv[0], "--") == 0)
+ break;
+ else if(strcmp(argv[0], "--channels") == 0 || strcmp(argv[0], "-c") == 0)
+ {
+ if(!(argc > 1))
+ {
+ fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
+ return 1;
+ }
+
+ recorder.mChannels = strtol(argv[1], &end, 0);
+ if((recorder.mChannels != 1 && recorder.mChannels != 2) || (end && *end != '\0'))
+ {
+ fprintf(stderr, "Invalid channels: %s\n", argv[1]);
+ return 1;
+ }
+ argv += 2;
+ argc -= 2;
+ }
+ else if(strcmp(argv[0], "--bits") == 0 || strcmp(argv[0], "-b") == 0)
+ {
+ if(!(argc > 1))
+ {
+ fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
+ return 1;
+ }
+
+ recorder.mBits = strtol(argv[1], &end, 0);
+ if((recorder.mBits != 8 && recorder.mBits != 16) || (end && *end != '\0'))
+ {
+ fprintf(stderr, "Invalid bit count: %s\n", argv[1]);
+ return 1;
+ }
+ argv += 2;
+ argc -= 2;
+ }
+ else if(strcmp(argv[0], "--rate") == 0 || strcmp(argv[0], "-r") == 0)
+ {
+ if(!(argc > 1))
+ {
+ fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
+ return 1;
+ }
+
+ recorder.mSampleRate = strtol(argv[1], &end, 0);
+ if(!(recorder.mSampleRate >= 8000 && recorder.mSampleRate <= 96000) || (end && *end != '\0'))
+ {
+ fprintf(stderr, "Invalid sample rate: %s\n", argv[1]);
+ return 1;
+ }
+ argv += 2;
+ argc -= 2;
+ }
+ else if(strcmp(argv[0], "--time") == 0 || strcmp(argv[0], "-t") == 0)
+ {
+ if(!(argc > 1))
+ {
+ fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
+ return 1;
+ }
+
+ recorder.mRecTime = strtod(argv[1], &end);
+ if(!(recorder.mRecTime >= 1.0f && recorder.mRecTime <= 10.0f) || (end && *end != '\0'))
+ {
+ fprintf(stderr, "Invalid record time: %s\n", argv[1]);
+ return 1;
+ }
+ argv += 2;
+ argc -= 2;
+ }
+ else if(strcmp(argv[0], "--outfile") == 0 || strcmp(argv[0], "-o") == 0)
+ {
+ if(!(argc > 1))
+ {
+ fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
+ return 1;
+ }
+
+ fname = argv[1];
+ argv += 2;
+ argc -= 2;
+ }
+ else if(strcmp(argv[0], "--help") == 0 || strcmp(argv[0], "-h") == 0)
+ {
+ fprintf(stderr, "Record from a device to a wav file.\n\n"
+ "Usage: %s [-device <name>] [options...]\n\n"
+ "Available options:\n%s\n", progname, optlist);
+ return 0;
+ }
+ else
+ {
+ fprintf(stderr, "Invalid option '%s'.\n\n"
+ "Usage: %s [-device <name>] [options...]\n\n"
+ "Available options:\n%s\n", argv[0], progname, optlist);
+ return 0;
+ }
+ }
+
+ recorder.mFrameSize = recorder.mChannels * recorder.mBits / 8;
+
+ format = AL_NONE;
+ if(recorder.mChannels == 1)
+ {
+ if(recorder.mBits == 8)
+ format = AL_FORMAT_MONO8;
+ else if(recorder.mBits == 16)
+ format = AL_FORMAT_MONO16;
+ }
+ else if(recorder.mChannels == 2)
+ {
+ if(recorder.mBits == 8)
+ format = AL_FORMAT_STEREO8;
+ else if(recorder.mBits == 16)
+ format = AL_FORMAT_STEREO16;
+ }
+
+ recorder.mDevice = alcCaptureOpenDevice(devname, recorder.mSampleRate, format, 32768);
+ if(!recorder.mDevice)
+ {
+ fprintf(stderr, "Failed to open %s, %s %d-bit %dhz %d samples\n", devname?devname:"default device",
+ (recorder.mChannels == 1) ? "mono" : "stereo", recorder.mBits,recorder.mSampleRate,
+ 32768
+ );
+ return 1;
+ }
+ fprintf(stderr, "Opened \"%s\"\n", alcGetString(
+ recorder.mDevice, ALC_CAPTURE_DEVICE_SPECIFIER
+ ));
+
+ recorder.mFile = fopen(fname, "wb");
+ if(!recorder.mFile)
+ {
+ fprintf(stderr, "Failed to open '%s' for writing\n", fname);
+ alcCaptureCloseDevice(recorder.mDevice);
+ return 1;
+ }
+
+ fputs("RIFF", recorder.mFile);
+ fwrite32le(0xFFFFFFFF, recorder.mFile); // 'RIFF' header len; filled in at close
+
+ fputs("WAVE", recorder.mFile);
+
+ fputs("fmt ", recorder.mFile);
+ fwrite32le(18, recorder.mFile); // 'fmt ' header len
+
+ // 16-bit val, format type id
+ fwrite16le(0x0001, recorder.mFile);
+ // 16-bit val, channel count
+ fwrite16le(recorder.mChannels, recorder.mFile);
+ // 32-bit val, frequency
+ fwrite32le(recorder.mSampleRate, recorder.mFile);
+ // 32-bit val, bytes per second
+ fwrite32le(recorder.mSampleRate * recorder.mFrameSize, recorder.mFile);
+ // 16-bit val, frame size
+ fwrite16le(recorder.mFrameSize, recorder.mFile);
+ // 16-bit val, bits per sample
+ fwrite16le(recorder.mBits, recorder.mFile);
+ // 16-bit val, extra byte count
+ fwrite16le(0, recorder.mFile);
+
+ fputs("data", recorder.mFile);
+ fwrite32le(0xFFFFFFFF, recorder.mFile); // 'data' header len; filled in at close
+
+ recorder.mDataSizeOffset = ftell(recorder.mFile) - 4;
+ if(ferror(recorder.mFile) || recorder.mDataSizeOffset < 0)
+ {
+ fprintf(stderr, "Error writing header: %s\n", strerror(errno));
+ fclose(recorder.mFile);
+ alcCaptureCloseDevice(recorder.mDevice);
+ return 1;
+ }
+
+ fprintf(stderr, "Recording '%s', %s %d-bit %dhz, %f sec\n", fname,
+ (recorder.mChannels == 1) ? "mono" : "stereo", recorder.mBits,recorder.mSampleRate,
+ ceilf(recorder.mRecTime)
+ );
+
+ alcCaptureStart(recorder.mDevice);
+ while((double)recorder.mDataSize/(double)recorder.mSampleRate < recorder.mRecTime &&
+ (err=alcGetError(recorder.mDevice)) == ALC_NO_ERROR && !ferror(recorder.mFile))
+ {
+ ALCint count = 0;
+ fprintf(stderr, "\rCaptured "SZFMT" samples", recorder.mDataSize);
+ alcGetIntegerv(recorder.mDevice, ALC_CAPTURE_SAMPLES, 1, &count);
+ if(count < 1)
+ {
+ al_nssleep(10000000);
+ continue;
+ }
+ if(count > recorder.mBufferSize)
+ {
+ ALbyte *data = calloc(recorder.mFrameSize, count);
+ free(recorder.mBuffer);
+ recorder.mBuffer = data;
+ recorder.mBufferSize = count;
+ }
+ alcCaptureSamples(recorder.mDevice, recorder.mBuffer, count);
+#if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN
+ if(recorder.mBits == 16)
+ {
+ ALCint i;
+ /* Byteswap short samples on big-endian systems (wav needs little-
+ * endian, and OpenAL gives the system's native-endian).
+ */
+ for(i = 0;i < count*recorder.mChannels;i++)
+ {
+ ALbyte b = recorder.mBuffer[i*2 + 0];
+ recorder.mBuffer[i*2 + 0] = recorder.mBuffer[i*2 + 1];
+ recorder.mBuffer[i*2 + 1] = b;
+ }
+ }
+#endif
+ recorder.mDataSize += fwrite(recorder.mBuffer, recorder.mFrameSize, count, recorder.mFile);
+ }
+ alcCaptureStop(recorder.mDevice);
+ fprintf(stderr, "\rCaptured "SZFMT" samples\n", recorder.mDataSize);
+ if(err != ALC_NO_ERROR)
+ fprintf(stderr, "Got device error 0x%04x: %s\n", err, alcGetString(recorder.mDevice, err));
+
+ alcCaptureCloseDevice(recorder.mDevice);
+ recorder.mDevice = NULL;
+
+ free(recorder.mBuffer);
+ recorder.mBuffer = NULL;
+ recorder.mBufferSize = 0;
+
+ total_size = ftell(recorder.mFile);
+ if(fseek(recorder.mFile, recorder.mDataSizeOffset, SEEK_SET) == 0)
+ {
+ fwrite32le(recorder.mDataSize*recorder.mFrameSize, recorder.mFile);
+ if(fseek(recorder.mFile, 4, SEEK_SET) == 0)
+ fwrite32le(total_size - 8, recorder.mFile);
+ }
+
+ fclose(recorder.mFile);
+ recorder.mFile = NULL;
+
+ return 0;
+}