PageRenderTime 59ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/ext/coreaudio/audiofile.m

https://github.com/nagachika/ruby-coreaudio
Objective C | 442 lines | 331 code | 81 blank | 30 comment | 68 complexity | 61252cdb0753fe9d9b80b04242f2a19c MD5 | raw file
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <Foundation/Foundation.h>
  4. #include <CoreAudio/CoreAudio.h>
  5. #include <ruby.h>
  6. #include <AudioToolbox/AudioToolbox.h>
  7. #include "coreaudio.h"
  8. VALUE rb_cAudioFile;
  9. static VALUE sym_read, sym_write, sym_format;
  10. static VALUE sym_rate, sym_file_rate, sym_channels, sym_file_channels;
  11. static VALUE sym_wav, sym_m4a;
  12. static void
  13. setASBD(AudioStreamBasicDescription *asbd,
  14. Float64 rate,
  15. UInt32 format,
  16. UInt32 flags,
  17. UInt32 channels,
  18. UInt32 bitsPerChannel,
  19. UInt32 framePerPacket)
  20. {
  21. asbd->mSampleRate = rate;
  22. asbd->mFormatID = format;
  23. asbd->mFormatFlags = flags;
  24. asbd->mBitsPerChannel = bitsPerChannel;
  25. asbd->mChannelsPerFrame = channels;
  26. asbd->mFramesPerPacket = framePerPacket;
  27. asbd->mBytesPerFrame = bitsPerChannel/8*channels;
  28. asbd->mBytesPerPacket = bitsPerChannel/8*channels*framePerPacket;
  29. }
  30. typedef struct {
  31. AudioStreamBasicDescription file_desc;
  32. AudioStreamBasicDescription inner_desc;
  33. Boolean for_write;
  34. ExtAudioFileRef file;
  35. } ca_audio_file_t;
  36. static void
  37. ca_audio_file_free(void *ptr)
  38. {
  39. ca_audio_file_t *data = ptr;
  40. if ( data->file ) {
  41. ExtAudioFileDispose(data->file);
  42. data->file = NULL;
  43. }
  44. }
  45. static size_t
  46. ca_audio_file_memsize(const void *ptr)
  47. {
  48. (void)ptr;
  49. return sizeof(ca_audio_file_t);
  50. }
  51. static const rb_data_type_t ca_audio_file_type = {
  52. "ca_audio_file",
  53. {NULL, ca_audio_file_free, ca_audio_file_memsize}
  54. };
  55. static VALUE
  56. ca_audio_file_alloc(VALUE klass)
  57. {
  58. VALUE obj;
  59. ca_audio_file_t *ptr;
  60. obj = TypedData_Make_Struct(klass, ca_audio_file_t, &ca_audio_file_type, ptr);
  61. return obj;
  62. }
  63. static void
  64. parse_audio_file_options(VALUE opt, Boolean for_write,
  65. Float64 *rate, Float64 *file_rate,
  66. UInt32 *channels, UInt32 *file_channels)
  67. {
  68. if (NIL_P(opt) || NIL_P(rb_hash_aref(opt, sym_rate))) {
  69. if (for_write)
  70. *rate = 44100.0;
  71. else
  72. *rate = *file_rate;
  73. } else {
  74. *rate = NUM2DBL(rb_hash_aref(opt, sym_rate));
  75. }
  76. if (NIL_P(opt) || NIL_P(rb_hash_aref(opt, sym_channels))) {
  77. if (for_write)
  78. *channels = 2;
  79. else
  80. *channels = *file_channels;
  81. } else {
  82. *channels = NUM2UINT(rb_hash_aref(opt, sym_channels));
  83. }
  84. if (for_write) {
  85. if (NIL_P(opt) || NIL_P(rb_hash_aref(opt, sym_file_rate))) {
  86. *file_rate = *rate;
  87. } else {
  88. *file_rate = NUM2DBL(rb_hash_aref(opt, sym_file_rate));
  89. }
  90. if (NIL_P(opt) || NIL_P(rb_hash_aref(opt, sym_file_channels))) {
  91. *file_channels = *channels;
  92. } else {
  93. *file_channels = NUM2UINT(rb_hash_aref(opt, sym_file_channels));
  94. }
  95. }
  96. }
  97. /*
  98. * call-seq:
  99. * AudioFile.new(filepath, mode, opt)
  100. *
  101. * open audio file at +filepath+. +mode+ should be :read or :write
  102. * corresponding to file mode argument of Kernel#open.
  103. * +opt+ must contain :format key.
  104. *
  105. * 'client data' means audio data pass to AudioFile#write or from
  106. * AudioFile#read method.
  107. *
  108. * :format :: audio file format. currently audio file format and
  109. * codec type is hardcoded. (:wav, :m4a)
  110. * :rate :: sample rate of data pass from AudioFile#read or to AudioFile#write
  111. * If not specified, :file_rate value is used. (Float)
  112. * :channels :: number of channels
  113. * :file_rate :: file data sample rate. only work when open for output. (Float)
  114. * :file_channels :: file data number of channels. only work when open for
  115. * output.
  116. */
  117. static VALUE
  118. ca_audio_file_initialize(int argc, VALUE *argv, VALUE self)
  119. {
  120. ca_audio_file_t *data;
  121. VALUE path, mode, opt, format;
  122. Float64 rate, file_rate;
  123. UInt32 channels, file_channels;
  124. CFURLRef url = NULL;
  125. AudioFileTypeID filetype = 0;
  126. OSStatus err = noErr;
  127. TypedData_Get_Struct(self, ca_audio_file_t, &ca_audio_file_type, data);
  128. rb_scan_args(argc, argv, "12", &path, &mode, &opt);
  129. StringValue(path);
  130. /* check mode */
  131. if (NIL_P(mode) || mode == sym_read)
  132. data->for_write = FALSE;
  133. else if (mode == sym_write)
  134. data->for_write = TRUE;
  135. else
  136. rb_raise(rb_eArgError, "coreaudio: mode should be :read or :write");
  137. if (data->for_write) {
  138. /* when open for write, parse options before open ExtAudioFile */
  139. parse_audio_file_options(opt, data->for_write, &rate, &file_rate,
  140. &channels, &file_channels);
  141. format = rb_hash_aref(opt, sym_format);
  142. if (NIL_P(format))
  143. rb_raise(rb_eArgError, "coreaudio: :format option must be specified");
  144. if (format == sym_wav) {
  145. filetype = kAudioFileWAVEType;
  146. setASBD(&data->file_desc, file_rate, kAudioFormatLinearPCM,
  147. kLinearPCMFormatFlagIsSignedInteger |
  148. kAudioFormatFlagIsPacked,
  149. file_channels, 16, 1);
  150. } else if (format == sym_m4a) {
  151. filetype = kAudioFileM4AType;
  152. setASBD(&data->file_desc, file_rate, kAudioFormatMPEG4AAC,
  153. 0, file_channels, 0, 0);
  154. } else {
  155. volatile VALUE str = rb_inspect(format);
  156. RB_GC_GUARD(str);
  157. rb_raise(rb_eArgError, "coreaudio: unsupported format (%s)",
  158. RSTRING_PTR(str));
  159. }
  160. }
  161. /* create URL represent the target filepath */
  162. url = CFURLCreateFromFileSystemRepresentation(
  163. NULL, StringValueCStr(path), (CFIndex)RSTRING_LEN(path), FALSE);
  164. /* open ExtAudioFile */
  165. if (data->for_write)
  166. err = ExtAudioFileCreateWithURL(url, filetype, &data->file_desc,
  167. NULL, kAudioFileFlags_EraseFile,
  168. &data->file);
  169. else
  170. err = ExtAudioFileOpenURL(url, &data->file);
  171. CFRelease(url);
  172. url = NULL;
  173. if (err != noErr) {
  174. rb_raise(rb_eArgError,
  175. "coreaudio: fail to open ExtAudioFile: %d", (int)err);
  176. }
  177. /* get Audio Stream Basic Description (ASBD) from input file */
  178. if (!data->for_write) {
  179. UInt32 size = sizeof(data->file_desc);
  180. err = ExtAudioFileGetProperty(data->file,
  181. kExtAudioFileProperty_FileDataFormat,
  182. &size, &data->file_desc);
  183. if (err != noErr)
  184. rb_raise(rb_eRuntimeError,
  185. "coreaudio: fail to Get ExtAudioFile Property %d", err);
  186. /* parse options */
  187. file_rate = data->file_desc.mSampleRate;
  188. file_channels = data->file_desc.mChannelsPerFrame;
  189. parse_audio_file_options(opt, data->for_write, &rate, &file_rate,
  190. &channels, &file_channels);
  191. }
  192. setASBD(&data->inner_desc, rate, kAudioFormatLinearPCM,
  193. kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked,
  194. channels, 16, 1);
  195. err = ExtAudioFileSetProperty(
  196. data->file, kExtAudioFileProperty_ClientDataFormat,
  197. sizeof(data->inner_desc), &data->inner_desc);
  198. if (err != noErr) {
  199. ExtAudioFileDispose(data->file);
  200. data->file = NULL;
  201. rb_raise(rb_eArgError, "coreaudio: fail to set client data format: %d",
  202. (int)err);
  203. }
  204. return self;
  205. }
  206. static VALUE
  207. ca_audio_file_close(VALUE self)
  208. {
  209. ca_audio_file_t *data;
  210. TypedData_Get_Struct(self, ca_audio_file_t, &ca_audio_file_type, data);
  211. if (data->file) {
  212. ExtAudioFileDispose(data->file);
  213. data->file = NULL;
  214. }
  215. return self;
  216. }
  217. static VALUE
  218. ca_audio_file_write(VALUE self, VALUE data)
  219. {
  220. ca_audio_file_t *file;
  221. UInt32 n_ch;
  222. int rank;
  223. short *buf = NULL;
  224. AudioBufferList buf_list;
  225. UInt32 frames;
  226. size_t alloc_size;
  227. volatile VALUE tmpstr = Qundef;
  228. OSStatus err = noErr;
  229. /* cast to NArray of SINT and check rank of NArray */
  230. data = na_cast_object(data, NA_SINT);
  231. rank = NA_RANK(data);
  232. if (rank > 3)
  233. rb_raise(rb_eArgError, "coreaudio: audio buffer rank must be 1 or 2.");
  234. TypedData_Get_Struct(self, ca_audio_file_t, &ca_audio_file_type, file);
  235. if (file->file == NULL)
  236. rb_raise(rb_eIOError, "coreaudio: already closed file");
  237. if (!file->for_write)
  238. rb_raise(rb_eRuntimeError, "coreaudio: audio file opened for reading");
  239. n_ch = file->inner_desc.mChannelsPerFrame;
  240. if (rank == 2 && NA_SHAPE0(data) != (int)n_ch)
  241. rb_raise(rb_eArgError,
  242. "coreaudio: audio buffer size of first dimension must be "
  243. "equal to number of channels");
  244. frames = rank == 1 ? NA_SHAPE0(data) : NA_SHAPE1(data);
  245. alloc_size = (file->inner_desc.mBitsPerChannel/8) * frames * n_ch;
  246. /* prepare interleaved audio buffer */
  247. buf_list.mNumberBuffers = 1;
  248. buf_list.mBuffers[0].mNumberChannels = n_ch;
  249. buf_list.mBuffers[0].mDataByteSize = (UInt32)alloc_size;
  250. if ((rank == 1 && n_ch == 1) || rank == 2) {
  251. /* no need to allocate buffer. NArray buffer can be used as mData */
  252. buf_list.mBuffers[0].mData = NA_PTR_TYPE(data, void *);
  253. } else {
  254. UInt32 i, j;
  255. short *na_buf = NA_PTR_TYPE(data, short *);
  256. buf_list.mBuffers[0].mData = rb_alloc_tmp_buffer(&tmpstr, alloc_size);
  257. buf = buf_list.mBuffers[0].mData;
  258. for (i = 0; i < frames; i++) {
  259. for (j = 0; j < n_ch; j++) {
  260. buf[i*n_ch+j] = na_buf[i];
  261. }
  262. }
  263. }
  264. err = ExtAudioFileWrite(file->file, frames, &buf_list);
  265. if (tmpstr != Qundef)
  266. rb_free_tmp_buffer(&tmpstr);
  267. if (err != noErr) {
  268. rb_raise(rb_eRuntimeError,
  269. "coreaudio: ExtAudioFileWrite() fails: %d", (int)err);
  270. }
  271. return self;
  272. }
  273. static VALUE
  274. ca_audio_file_read_frames(VALUE self, VALUE frame_val)
  275. {
  276. ca_audio_file_t *file;
  277. UInt32 channels, frames, read_frames;
  278. AudioBufferList buf_list;
  279. size_t alloc_size;
  280. VALUE nary;
  281. int shape[2];
  282. OSStatus err = noErr;
  283. frames = NUM2UINT(frame_val);
  284. TypedData_Get_Struct(self, ca_audio_file_t, &ca_audio_file_type, file);
  285. if (file->file == NULL)
  286. rb_raise(rb_eIOError, "coreaudio: already closend file");
  287. if (file->for_write)
  288. rb_raise(rb_eRuntimeError, "coreaudio: audio file open for writing");
  289. channels = file->inner_desc.mChannelsPerFrame;
  290. shape[0] = channels;
  291. shape[1] = frames;
  292. nary = na_make_object(NA_SINT, 2, shape, cNArray);
  293. alloc_size = (file->inner_desc.mBitsPerChannel/8) * channels * frames;
  294. /* prepare interleaved audio buffer */
  295. buf_list.mNumberBuffers = 1;
  296. buf_list.mBuffers[0].mNumberChannels = channels;
  297. buf_list.mBuffers[0].mDataByteSize = (UInt32)alloc_size;
  298. buf_list.mBuffers[0].mData = NA_PTR_TYPE(nary, void *);
  299. read_frames = frames;
  300. err = ExtAudioFileRead(file->file, &read_frames, &buf_list);
  301. if (err != noErr) {
  302. rb_raise(rb_eRuntimeError,
  303. "coreaudio: ExtAudioFileRead() fails: %d", (int)err);
  304. }
  305. if (read_frames == 0)
  306. return Qnil;
  307. NA_SHAPE1(nary) = read_frames;
  308. return nary;
  309. }
  310. static VALUE
  311. ca_audio_file_rate(VALUE self)
  312. {
  313. ca_audio_file_t *data;
  314. TypedData_Get_Struct(self, ca_audio_file_t, &ca_audio_file_type, data);
  315. return DBL2NUM(data->file_desc.mSampleRate);
  316. }
  317. static VALUE
  318. ca_audio_file_channels(VALUE self)
  319. {
  320. ca_audio_file_t *data;
  321. TypedData_Get_Struct(self, ca_audio_file_t, &ca_audio_file_type, data);
  322. return UINT2NUM((unsigned int)data->file_desc.mChannelsPerFrame);
  323. }
  324. static VALUE
  325. ca_audio_file_inner_rate(VALUE self)
  326. {
  327. ca_audio_file_t *data;
  328. TypedData_Get_Struct(self, ca_audio_file_t, &ca_audio_file_type, data);
  329. return DBL2NUM(data->inner_desc.mSampleRate);
  330. }
  331. static VALUE
  332. ca_audio_file_inner_channels(VALUE self)
  333. {
  334. ca_audio_file_t *data;
  335. TypedData_Get_Struct(self, ca_audio_file_t, &ca_audio_file_type, data);
  336. return UINT2NUM((unsigned int)data->inner_desc.mChannelsPerFrame);
  337. }
  338. void
  339. Init_coreaudio_audiofile(void)
  340. {
  341. sym_read = ID2SYM(rb_intern("read"));
  342. sym_write = ID2SYM(rb_intern("write"));
  343. sym_format = ID2SYM(rb_intern("format"));
  344. sym_rate = ID2SYM(rb_intern("rate"));
  345. sym_file_rate = ID2SYM(rb_intern("file_rate"));
  346. sym_channels = ID2SYM(rb_intern("channels"));
  347. sym_file_channels = ID2SYM(rb_intern("file_channels"));
  348. sym_wav = ID2SYM(rb_intern("wav"));
  349. sym_m4a = ID2SYM(rb_intern("m4a"));
  350. rb_cAudioFile = rb_define_class_under(rb_mCoreAudio, "AudioFile",
  351. rb_cObject);
  352. rb_define_alloc_func(rb_cAudioFile, ca_audio_file_alloc);
  353. rb_define_method(rb_cAudioFile, "initialize", ca_audio_file_initialize, -1);
  354. rb_define_method(rb_cAudioFile, "close", ca_audio_file_close, 0);
  355. rb_define_method(rb_cAudioFile, "write", ca_audio_file_write, 1);
  356. rb_define_method(rb_cAudioFile, "read_frames", ca_audio_file_read_frames, 1);
  357. rb_define_method(rb_cAudioFile, "rate", ca_audio_file_rate, 0);
  358. rb_define_method(rb_cAudioFile, "channels", ca_audio_file_channels, 0);
  359. rb_define_method(rb_cAudioFile, "inner_rate", ca_audio_file_inner_rate, 0);
  360. rb_define_method(rb_cAudioFile, "inner_channels", ca_audio_file_inner_channels, 0);
  361. }