🛠️🐜 Antkeeper superbuild with dependencies included https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

403 lines
13 KiB

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