diff options
author | Chris Robinson <[email protected]> | 2017-06-23 05:19:24 -0700 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2017-06-23 05:19:24 -0700 |
commit | e07166e93cf392e3dac0a8ee71696a8d6d5114fc (patch) | |
tree | 628ed0885ff0f01fb181617293f2daec6303dced | |
parent | d1077795deb8367d8e6fc39ecfc5f51b3c39d9e5 (diff) |
Add a recording example app
-rw-r--r-- | CMakeLists.txt | 14 | ||||
-rw-r--r-- | examples/alrecord.c | 363 |
2 files changed, 377 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 6fcdb964..f8f10fe3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1409,6 +1409,20 @@ IF(ALSOFT_TESTS) ENDIF() IF(ALSOFT_EXAMPLES) + ADD_EXECUTABLE(alrecord examples/alrecord.c ${COMMON_OBJS}) + TARGET_LINK_LIBRARIES(alrecord OpenAL) + SET_PROPERTY(TARGET alrecord APPEND PROPERTY COMPILE_FLAGS ${EXTRA_CFLAGS}) + + IF(ALSOFT_INSTALL) + INSTALL(TARGETS alrecord + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) + ENDIF() + + MESSAGE(STATUS "Building example programs") + IF(SDL2_FOUND) IF(SDL_SOUND_FOUND) SET(EX_COMMON_OBJS examples/common/alhelpers.c) 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; +} |