2
0

alrecord.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. /*
  2. * OpenAL Recording Example
  3. *
  4. * Copyright (c) 2017 by Chris Robinson <[email protected]>
  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. /* This file contains a relatively simple recorder. */
  25. #include <string.h>
  26. #include <stdlib.h>
  27. #include <stdio.h>
  28. #include <math.h>
  29. #include "AL/al.h"
  30. #include "AL/alc.h"
  31. #include "AL/alext.h"
  32. #include "common/alhelpers.h"
  33. #if defined(_WIN64)
  34. #define SZFMT "%I64u"
  35. #elif defined(_WIN32)
  36. #define SZFMT "%u"
  37. #else
  38. #define SZFMT "%zu"
  39. #endif
  40. #if defined(_MSC_VER) && (_MSC_VER < 1900)
  41. static float msvc_strtof(const char *str, char **end)
  42. { return (float)strtod(str, end); }
  43. #define strtof msvc_strtof
  44. #endif
  45. static void fwrite16le(ALushort val, FILE *f)
  46. {
  47. ALubyte data[2] = { val&0xff, (val>>8)&0xff };
  48. fwrite(data, 1, 2, f);
  49. }
  50. static void fwrite32le(ALuint val, FILE *f)
  51. {
  52. ALubyte data[4] = { val&0xff, (val>>8)&0xff, (val>>16)&0xff, (val>>24)&0xff };
  53. fwrite(data, 1, 4, f);
  54. }
  55. typedef struct Recorder {
  56. ALCdevice *mDevice;
  57. FILE *mFile;
  58. long mDataSizeOffset;
  59. ALuint mDataSize;
  60. float mRecTime;
  61. int mChannels;
  62. int mBits;
  63. int mSampleRate;
  64. ALuint mFrameSize;
  65. ALbyte *mBuffer;
  66. ALsizei mBufferSize;
  67. } Recorder;
  68. int main(int argc, char **argv)
  69. {
  70. static const char optlist[] =
  71. " --channels/-c <channels> Set channel count (1 or 2)\n"
  72. " --bits/-b <bits> Set channel count (8, 16, or 32)\n"
  73. " --rate/-r <rate> Set sample rate (8000 to 96000)\n"
  74. " --time/-t <time> Time in seconds to record (1 to 10)\n"
  75. " --outfile/-o <filename> Output filename (default: record.wav)";
  76. const char *fname = "record.wav";
  77. const char *devname = NULL;
  78. const char *progname;
  79. Recorder recorder;
  80. long total_size;
  81. ALenum format;
  82. ALCenum err;
  83. progname = argv[0];
  84. if(argc < 2)
  85. {
  86. fprintf(stderr, "Record from a device to a wav file.\n\n"
  87. "Usage: %s [-device <name>] [options...]\n\n"
  88. "Available options:\n%s\n", progname, optlist);
  89. return 0;
  90. }
  91. recorder.mDevice = NULL;
  92. recorder.mFile = NULL;
  93. recorder.mDataSizeOffset = 0;
  94. recorder.mDataSize = 0;
  95. recorder.mRecTime = 4.0f;
  96. recorder.mChannels = 1;
  97. recorder.mBits = 16;
  98. recorder.mSampleRate = 44100;
  99. recorder.mFrameSize = recorder.mChannels * recorder.mBits / 8;
  100. recorder.mBuffer = NULL;
  101. recorder.mBufferSize = 0;
  102. argv++; argc--;
  103. if(argc > 1 && strcmp(argv[0], "-device") == 0)
  104. {
  105. devname = argv[1];
  106. argv += 2;
  107. argc -= 2;
  108. }
  109. while(argc > 0)
  110. {
  111. char *end;
  112. if(strcmp(argv[0], "--") == 0)
  113. break;
  114. else if(strcmp(argv[0], "--channels") == 0 || strcmp(argv[0], "-c") == 0)
  115. {
  116. if(!(argc > 1))
  117. {
  118. fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
  119. return 1;
  120. }
  121. recorder.mChannels = strtol(argv[1], &end, 0);
  122. if((recorder.mChannels != 1 && recorder.mChannels != 2) || (end && *end != '\0'))
  123. {
  124. fprintf(stderr, "Invalid channels: %s\n", argv[1]);
  125. return 1;
  126. }
  127. argv += 2;
  128. argc -= 2;
  129. }
  130. else if(strcmp(argv[0], "--bits") == 0 || strcmp(argv[0], "-b") == 0)
  131. {
  132. if(!(argc > 1))
  133. {
  134. fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
  135. return 1;
  136. }
  137. recorder.mBits = strtol(argv[1], &end, 0);
  138. if((recorder.mBits != 8 && recorder.mBits != 16 && recorder.mBits != 32) ||
  139. (end && *end != '\0'))
  140. {
  141. fprintf(stderr, "Invalid bit count: %s\n", argv[1]);
  142. return 1;
  143. }
  144. argv += 2;
  145. argc -= 2;
  146. }
  147. else if(strcmp(argv[0], "--rate") == 0 || strcmp(argv[0], "-r") == 0)
  148. {
  149. if(!(argc > 1))
  150. {
  151. fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
  152. return 1;
  153. }
  154. recorder.mSampleRate = strtol(argv[1], &end, 0);
  155. if(!(recorder.mSampleRate >= 8000 && recorder.mSampleRate <= 96000) || (end && *end != '\0'))
  156. {
  157. fprintf(stderr, "Invalid sample rate: %s\n", argv[1]);
  158. return 1;
  159. }
  160. argv += 2;
  161. argc -= 2;
  162. }
  163. else if(strcmp(argv[0], "--time") == 0 || strcmp(argv[0], "-t") == 0)
  164. {
  165. if(!(argc > 1))
  166. {
  167. fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
  168. return 1;
  169. }
  170. recorder.mRecTime = strtof(argv[1], &end);
  171. if(!(recorder.mRecTime >= 1.0f && recorder.mRecTime <= 10.0f) || (end && *end != '\0'))
  172. {
  173. fprintf(stderr, "Invalid record time: %s\n", argv[1]);
  174. return 1;
  175. }
  176. argv += 2;
  177. argc -= 2;
  178. }
  179. else if(strcmp(argv[0], "--outfile") == 0 || strcmp(argv[0], "-o") == 0)
  180. {
  181. if(!(argc > 1))
  182. {
  183. fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
  184. return 1;
  185. }
  186. fname = argv[1];
  187. argv += 2;
  188. argc -= 2;
  189. }
  190. else if(strcmp(argv[0], "--help") == 0 || strcmp(argv[0], "-h") == 0)
  191. {
  192. fprintf(stderr, "Record from a device to a wav file.\n\n"
  193. "Usage: %s [-device <name>] [options...]\n\n"
  194. "Available options:\n%s\n", progname, optlist);
  195. return 0;
  196. }
  197. else
  198. {
  199. fprintf(stderr, "Invalid option '%s'.\n\n"
  200. "Usage: %s [-device <name>] [options...]\n\n"
  201. "Available options:\n%s\n", argv[0], progname, optlist);
  202. return 0;
  203. }
  204. }
  205. recorder.mFrameSize = recorder.mChannels * recorder.mBits / 8;
  206. format = AL_NONE;
  207. if(recorder.mChannels == 1)
  208. {
  209. if(recorder.mBits == 8)
  210. format = AL_FORMAT_MONO8;
  211. else if(recorder.mBits == 16)
  212. format = AL_FORMAT_MONO16;
  213. else if(recorder.mBits == 32)
  214. format = AL_FORMAT_MONO_FLOAT32;
  215. }
  216. else if(recorder.mChannels == 2)
  217. {
  218. if(recorder.mBits == 8)
  219. format = AL_FORMAT_STEREO8;
  220. else if(recorder.mBits == 16)
  221. format = AL_FORMAT_STEREO16;
  222. else if(recorder.mBits == 32)
  223. format = AL_FORMAT_STEREO_FLOAT32;
  224. }
  225. recorder.mDevice = alcCaptureOpenDevice(devname, recorder.mSampleRate, format, 32768);
  226. if(!recorder.mDevice)
  227. {
  228. fprintf(stderr, "Failed to open %s, %s %d-bit, %s, %dhz (%d samples)\n",
  229. devname ? devname : "default device",
  230. (recorder.mBits == 32) ? "Float" :
  231. (recorder.mBits != 8) ? "Signed" : "Unsigned", recorder.mBits,
  232. (recorder.mChannels == 1) ? "Mono" : "Stereo", recorder.mSampleRate,
  233. 32768
  234. );
  235. return 1;
  236. }
  237. fprintf(stderr, "Opened \"%s\"\n", alcGetString(
  238. recorder.mDevice, ALC_CAPTURE_DEVICE_SPECIFIER
  239. ));
  240. recorder.mFile = fopen(fname, "wb");
  241. if(!recorder.mFile)
  242. {
  243. fprintf(stderr, "Failed to open '%s' for writing\n", fname);
  244. alcCaptureCloseDevice(recorder.mDevice);
  245. return 1;
  246. }
  247. fputs("RIFF", recorder.mFile);
  248. fwrite32le(0xFFFFFFFF, recorder.mFile); // 'RIFF' header len; filled in at close
  249. fputs("WAVE", recorder.mFile);
  250. fputs("fmt ", recorder.mFile);
  251. fwrite32le(18, recorder.mFile); // 'fmt ' header len
  252. // 16-bit val, format type id (1 = integer PCM, 3 = float PCM)
  253. fwrite16le((recorder.mBits == 32) ? 0x0003 : 0x0001, recorder.mFile);
  254. // 16-bit val, channel count
  255. fwrite16le(recorder.mChannels, recorder.mFile);
  256. // 32-bit val, frequency
  257. fwrite32le(recorder.mSampleRate, recorder.mFile);
  258. // 32-bit val, bytes per second
  259. fwrite32le(recorder.mSampleRate * recorder.mFrameSize, recorder.mFile);
  260. // 16-bit val, frame size
  261. fwrite16le(recorder.mFrameSize, recorder.mFile);
  262. // 16-bit val, bits per sample
  263. fwrite16le(recorder.mBits, recorder.mFile);
  264. // 16-bit val, extra byte count
  265. fwrite16le(0, recorder.mFile);
  266. fputs("data", recorder.mFile);
  267. fwrite32le(0xFFFFFFFF, recorder.mFile); // 'data' header len; filled in at close
  268. recorder.mDataSizeOffset = ftell(recorder.mFile) - 4;
  269. if(ferror(recorder.mFile) || recorder.mDataSizeOffset < 0)
  270. {
  271. fprintf(stderr, "Error writing header: %s\n", strerror(errno));
  272. fclose(recorder.mFile);
  273. alcCaptureCloseDevice(recorder.mDevice);
  274. return 1;
  275. }
  276. fprintf(stderr, "Recording '%s', %s %d-bit, %s, %dhz (%g second%s)\n", fname,
  277. (recorder.mBits == 32) ? "Float" :
  278. (recorder.mBits != 8) ? "Signed" : "Unsigned", recorder.mBits,
  279. (recorder.mChannels == 1) ? "Mono" : "Stereo", recorder.mSampleRate,
  280. recorder.mRecTime, (recorder.mRecTime != 1.0f) ? "s" : ""
  281. );
  282. alcCaptureStart(recorder.mDevice);
  283. while((double)recorder.mDataSize/(double)recorder.mSampleRate < recorder.mRecTime &&
  284. (err=alcGetError(recorder.mDevice)) == ALC_NO_ERROR && !ferror(recorder.mFile))
  285. {
  286. ALCint count = 0;
  287. fprintf(stderr, "\rCaptured %u samples", recorder.mDataSize);
  288. alcGetIntegerv(recorder.mDevice, ALC_CAPTURE_SAMPLES, 1, &count);
  289. if(count < 1)
  290. {
  291. al_nssleep(10000000);
  292. continue;
  293. }
  294. if(count > recorder.mBufferSize)
  295. {
  296. ALbyte *data = calloc(recorder.mFrameSize, count);
  297. free(recorder.mBuffer);
  298. recorder.mBuffer = data;
  299. recorder.mBufferSize = count;
  300. }
  301. alcCaptureSamples(recorder.mDevice, recorder.mBuffer, count);
  302. #if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN
  303. /* Byteswap multibyte samples on big-endian systems (wav needs little-
  304. * endian, and OpenAL gives the system's native-endian).
  305. */
  306. if(recorder.mBits == 16)
  307. {
  308. ALCint i;
  309. for(i = 0;i < count*recorder.mChannels;i++)
  310. {
  311. ALbyte b = recorder.mBuffer[i*2 + 0];
  312. recorder.mBuffer[i*2 + 0] = recorder.mBuffer[i*2 + 1];
  313. recorder.mBuffer[i*2 + 1] = b;
  314. }
  315. }
  316. else if(recorder.mBits == 32)
  317. {
  318. ALCint i;
  319. for(i = 0;i < count*recorder.mChannels;i++)
  320. {
  321. ALbyte b0 = recorder.mBuffer[i*4 + 0];
  322. ALbyte b1 = recorder.mBuffer[i*4 + 1];
  323. recorder.mBuffer[i*4 + 0] = recorder.mBuffer[i*4 + 3];
  324. recorder.mBuffer[i*4 + 1] = recorder.mBuffer[i*4 + 2];
  325. recorder.mBuffer[i*4 + 2] = b1;
  326. recorder.mBuffer[i*4 + 3] = b0;
  327. }
  328. }
  329. #endif
  330. recorder.mDataSize += (ALuint)fwrite(recorder.mBuffer, recorder.mFrameSize, count,
  331. recorder.mFile);
  332. }
  333. alcCaptureStop(recorder.mDevice);
  334. fprintf(stderr, "\rCaptured %u samples\n", recorder.mDataSize);
  335. if(err != ALC_NO_ERROR)
  336. fprintf(stderr, "Got device error 0x%04x: %s\n", err, alcGetString(recorder.mDevice, err));
  337. alcCaptureCloseDevice(recorder.mDevice);
  338. recorder.mDevice = NULL;
  339. free(recorder.mBuffer);
  340. recorder.mBuffer = NULL;
  341. recorder.mBufferSize = 0;
  342. total_size = ftell(recorder.mFile);
  343. if(fseek(recorder.mFile, recorder.mDataSizeOffset, SEEK_SET) == 0)
  344. {
  345. fwrite32le(recorder.mDataSize*recorder.mFrameSize, recorder.mFile);
  346. if(fseek(recorder.mFile, 4, SEEK_SET) == 0)
  347. fwrite32le(total_size - 8, recorder.mFile);
  348. }
  349. fclose(recorder.mFile);
  350. recorder.mFile = NULL;
  351. return 0;
  352. }