audiotoolbox.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. /*
  2. * AudioToolbox output device
  3. * Copyright (c) 2020 Thilo Borgmann <thilo.borgmann@mail.de>
  4. *
  5. * This file is part of FFmpeg.
  6. *
  7. * FFmpeg is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU Lesser General Public
  9. * License as published by the Free Software Foundation; either
  10. * version 2.1 of the License, or (at your option) any later version.
  11. *
  12. * FFmpeg is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public
  18. * License along with FFmpeg; if not, write to the Free Software
  19. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  20. */
  21. /**
  22. * @file
  23. * AudioToolbox output device
  24. * @author Thilo Borgmann <thilo.borgmann@mail.de>
  25. */
  26. #import <AudioToolbox/AudioToolbox.h>
  27. #include <pthread.h>
  28. #include "libavutil/opt.h"
  29. #include "libavformat/internal.h"
  30. #include "libavformat/mux.h"
  31. #include "libavutil/internal.h"
  32. #include "avdevice.h"
  33. typedef struct
  34. {
  35. AVClass *class;
  36. AudioQueueBufferRef buffer[2];
  37. pthread_mutex_t buffer_lock[2];
  38. int cur_buf;
  39. AudioQueueRef queue;
  40. int list_devices;
  41. int audio_device_index;
  42. } ATContext;
  43. static int check_status(AVFormatContext *avctx, OSStatus *status, const char *msg)
  44. {
  45. if (*status != noErr) {
  46. av_log(avctx, AV_LOG_ERROR, "Error: %s (%i)\n", msg, *status);
  47. return 1;
  48. } else {
  49. av_log(avctx, AV_LOG_DEBUG, " OK : %s\n", msg);
  50. return 0;
  51. }
  52. }
  53. static void queue_callback(void* atctx, AudioQueueRef inAQ,
  54. AudioQueueBufferRef inBuffer)
  55. {
  56. // unlock the buffer that has just been consumed
  57. ATContext *ctx = (ATContext*)atctx;
  58. for (int i = 0; i < 2; i++) {
  59. if (inBuffer == ctx->buffer[i]) {
  60. pthread_mutex_unlock(&ctx->buffer_lock[i]);
  61. }
  62. }
  63. }
  64. static av_cold int at_write_header(AVFormatContext *avctx)
  65. {
  66. ATContext *ctx = (ATContext*)avctx->priv_data;
  67. OSStatus err = noErr;
  68. CFStringRef device_UID = NULL;
  69. AudioDeviceID *devices;
  70. int num_devices;
  71. // get devices
  72. UInt32 data_size = 0;
  73. AudioObjectPropertyAddress prop;
  74. prop.mSelector = kAudioHardwarePropertyDevices;
  75. prop.mScope = kAudioObjectPropertyScopeGlobal;
  76. prop.mElement = kAudioObjectPropertyElementMaster;
  77. err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &prop, 0, NULL, &data_size);
  78. if (check_status(avctx, &err, "AudioObjectGetPropertyDataSize devices"))
  79. return AVERROR(EINVAL);
  80. num_devices = data_size / sizeof(AudioDeviceID);
  81. devices = (AudioDeviceID*)(av_malloc(data_size));
  82. err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, NULL, &data_size, devices);
  83. if (check_status(avctx, &err, "AudioObjectGetPropertyData devices")) {
  84. av_freep(&devices);
  85. return AVERROR(EINVAL);
  86. }
  87. // list devices
  88. if (ctx->list_devices) {
  89. CFStringRef device_name = NULL;
  90. prop.mScope = kAudioDevicePropertyScopeInput;
  91. av_log(ctx, AV_LOG_INFO, "CoreAudio devices:\n");
  92. for(UInt32 i = 0; i < num_devices; ++i) {
  93. // UID
  94. data_size = sizeof(device_UID);
  95. prop.mSelector = kAudioDevicePropertyDeviceUID;
  96. err = AudioObjectGetPropertyData(devices[i], &prop, 0, NULL, &data_size, &device_UID);
  97. if (check_status(avctx, &err, "AudioObjectGetPropertyData UID"))
  98. continue;
  99. // name
  100. data_size = sizeof(device_name);
  101. prop.mSelector = kAudioDevicePropertyDeviceNameCFString;
  102. err = AudioObjectGetPropertyData(devices[i], &prop, 0, NULL, &data_size, &device_name);
  103. if (check_status(avctx, &err, "AudioObjecTGetPropertyData name"))
  104. continue;
  105. av_log(ctx, AV_LOG_INFO, "[%d] %30s, %s\n", i,
  106. CFStringGetCStringPtr(device_name, kCFStringEncodingMacRoman),
  107. CFStringGetCStringPtr(device_UID, kCFStringEncodingMacRoman));
  108. }
  109. }
  110. // get user-defined device UID or use default device
  111. // -audio_device_index overrides any URL given
  112. const char *stream_name = avctx->url;
  113. if (stream_name && ctx->audio_device_index == -1) {
  114. sscanf(stream_name, "%d", &ctx->audio_device_index);
  115. }
  116. if (ctx->audio_device_index >= 0) {
  117. // get UID of selected device
  118. data_size = sizeof(device_UID);
  119. prop.mSelector = kAudioDevicePropertyDeviceUID;
  120. err = AudioObjectGetPropertyData(devices[ctx->audio_device_index], &prop, 0, NULL, &data_size, &device_UID);
  121. if (check_status(avctx, &err, "AudioObjecTGetPropertyData UID")) {
  122. av_freep(&devices);
  123. return AVERROR(EINVAL);
  124. }
  125. } else {
  126. // use default device
  127. device_UID = NULL;
  128. }
  129. av_log(ctx, AV_LOG_DEBUG, "stream_name: %s\n", stream_name);
  130. av_log(ctx, AV_LOG_DEBUG, "audio_device_idnex: %i\n", ctx->audio_device_index);
  131. av_log(ctx, AV_LOG_DEBUG, "UID: %s\n", CFStringGetCStringPtr(device_UID, kCFStringEncodingMacRoman));
  132. // check input stream
  133. if (avctx->nb_streams != 1 || avctx->streams[0]->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) {
  134. av_log(ctx, AV_LOG_ERROR, "Only a single audio stream is supported.\n");
  135. return AVERROR(EINVAL);
  136. }
  137. av_freep(&devices);
  138. AVCodecParameters *codecpar = avctx->streams[0]->codecpar;
  139. // audio format
  140. AudioStreamBasicDescription device_format = {0};
  141. device_format.mSampleRate = codecpar->sample_rate;
  142. device_format.mFormatID = kAudioFormatLinearPCM;
  143. device_format.mFormatFlags |= (codecpar->format == AV_SAMPLE_FMT_FLT) ? kLinearPCMFormatFlagIsFloat : 0;
  144. device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_S8) ? kLinearPCMFormatFlagIsSignedInteger : 0;
  145. device_format.mFormatFlags |= (codecpar->codec_id == AV_NE(AV_CODEC_ID_PCM_S16BE, AV_CODEC_ID_PCM_S16LE)) ? kLinearPCMFormatFlagIsSignedInteger : 0;
  146. device_format.mFormatFlags |= (codecpar->codec_id == AV_NE(AV_CODEC_ID_PCM_S24BE, AV_CODEC_ID_PCM_S24LE)) ? kLinearPCMFormatFlagIsSignedInteger : 0;
  147. device_format.mFormatFlags |= (codecpar->codec_id == AV_NE(AV_CODEC_ID_PCM_S32BE, AV_CODEC_ID_PCM_S32LE)) ? kLinearPCMFormatFlagIsSignedInteger : 0;
  148. device_format.mFormatFlags |= (av_sample_fmt_is_planar(codecpar->format)) ? kAudioFormatFlagIsNonInterleaved : 0;
  149. device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_F32BE) ? kAudioFormatFlagIsBigEndian : 0;
  150. device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_S16BE) ? kAudioFormatFlagIsBigEndian : 0;
  151. device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_S24BE) ? kAudioFormatFlagIsBigEndian : 0;
  152. device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_S32BE) ? kAudioFormatFlagIsBigEndian : 0;
  153. device_format.mChannelsPerFrame = codecpar->ch_layout.nb_channels;
  154. device_format.mBitsPerChannel = (codecpar->codec_id == AV_NE(AV_CODEC_ID_PCM_S24BE, AV_CODEC_ID_PCM_S24LE)) ? 24 : (av_get_bytes_per_sample(codecpar->format) << 3);
  155. device_format.mBytesPerFrame = (device_format.mBitsPerChannel >> 3) * device_format.mChannelsPerFrame;
  156. device_format.mFramesPerPacket = 1;
  157. device_format.mBytesPerPacket = device_format.mBytesPerFrame * device_format.mFramesPerPacket;
  158. device_format.mReserved = 0;
  159. av_log(ctx, AV_LOG_DEBUG, "device_format.mSampleRate = %i\n", codecpar->sample_rate);
  160. av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatID = %s\n", "kAudioFormatLinearPCM");
  161. av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->format == AV_SAMPLE_FMT_FLT) ? "kLinearPCMFormatFlagIsFloat" : "0");
  162. av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_S8) ? "kLinearPCMFormatFlagIsSignedInteger" : "0");
  163. av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_NE(AV_CODEC_ID_PCM_S32BE, AV_CODEC_ID_PCM_S32LE)) ? "kLinearPCMFormatFlagIsSignedInteger" : "0");
  164. av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_NE(AV_CODEC_ID_PCM_S16BE, AV_CODEC_ID_PCM_S16LE)) ? "kLinearPCMFormatFlagIsSignedInteger" : "0");
  165. av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_NE(AV_CODEC_ID_PCM_S24BE, AV_CODEC_ID_PCM_S24LE)) ? "kLinearPCMFormatFlagIsSignedInteger" : "0");
  166. av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (av_sample_fmt_is_planar(codecpar->format)) ? "kAudioFormatFlagIsNonInterleaved" : "0");
  167. av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_F32BE) ? "kAudioFormatFlagIsBigEndian" : "0");
  168. av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_S16BE) ? "kAudioFormatFlagIsBigEndian" : "0");
  169. av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_S24BE) ? "kAudioFormatFlagIsBigEndian" : "0");
  170. av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_S32BE) ? "kAudioFormatFlagIsBigEndian" : "0");
  171. av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags == %i\n", device_format.mFormatFlags);
  172. av_log(ctx, AV_LOG_DEBUG, "device_format.mChannelsPerFrame = %i\n", codecpar->ch_layout.nb_channels);
  173. av_log(ctx, AV_LOG_DEBUG, "device_format.mBitsPerChannel = %i\n", av_get_bytes_per_sample(codecpar->format) << 3);
  174. av_log(ctx, AV_LOG_DEBUG, "device_format.mBytesPerFrame = %i\n", (device_format.mBitsPerChannel >> 3) * codecpar->ch_layout.nb_channels);
  175. av_log(ctx, AV_LOG_DEBUG, "device_format.mBytesPerPacket = %i\n", device_format.mBytesPerFrame);
  176. av_log(ctx, AV_LOG_DEBUG, "device_format.mFramesPerPacket = %i\n", 1);
  177. av_log(ctx, AV_LOG_DEBUG, "device_format.mReserved = %i\n", 0);
  178. // create new output queue for the device
  179. err = AudioQueueNewOutput(&device_format, queue_callback, ctx,
  180. NULL, kCFRunLoopCommonModes,
  181. 0, &ctx->queue);
  182. if (check_status(avctx, &err, "AudioQueueNewOutput")) {
  183. if (err == kAudioFormatUnsupportedDataFormatError)
  184. av_log(ctx, AV_LOG_ERROR, "Unsupported output format.\n");
  185. return AVERROR(EINVAL);
  186. }
  187. // set user-defined device or leave untouched for default
  188. if (device_UID != NULL) {
  189. err = AudioQueueSetProperty(ctx->queue, kAudioQueueProperty_CurrentDevice, &device_UID, sizeof(device_UID));
  190. if (check_status(avctx, &err, "AudioQueueSetProperty output UID"))
  191. return AVERROR(EINVAL);
  192. }
  193. // start the queue
  194. err = AudioQueueStart(ctx->queue, NULL);
  195. if (check_status(avctx, &err, "AudioQueueStart"))
  196. return AVERROR(EINVAL);
  197. // init the mutexes for double-buffering
  198. pthread_mutex_init(&ctx->buffer_lock[0], NULL);
  199. pthread_mutex_init(&ctx->buffer_lock[1], NULL);
  200. return 0;
  201. }
  202. static int at_write_packet(AVFormatContext *avctx, AVPacket *pkt)
  203. {
  204. ATContext *ctx = (ATContext*)avctx->priv_data;
  205. OSStatus err = noErr;
  206. // use the other buffer
  207. ctx->cur_buf = !ctx->cur_buf;
  208. // lock for writing or wait for the buffer to be available
  209. // will be unlocked by queue callback
  210. pthread_mutex_lock(&ctx->buffer_lock[ctx->cur_buf]);
  211. // (re-)allocate the buffer if not existant or of different size
  212. if (!ctx->buffer[ctx->cur_buf] || ctx->buffer[ctx->cur_buf]->mAudioDataBytesCapacity != pkt->size) {
  213. err = AudioQueueAllocateBuffer(ctx->queue, pkt->size, &ctx->buffer[ctx->cur_buf]);
  214. if (check_status(avctx, &err, "AudioQueueAllocateBuffer")) {
  215. pthread_mutex_unlock(&ctx->buffer_lock[ctx->cur_buf]);
  216. return AVERROR(ENOMEM);
  217. }
  218. }
  219. AudioQueueBufferRef buf = ctx->buffer[ctx->cur_buf];
  220. // copy audio data into buffer and enqueue the buffer
  221. memcpy(buf->mAudioData, pkt->data, buf->mAudioDataBytesCapacity);
  222. buf->mAudioDataByteSize = buf->mAudioDataBytesCapacity;
  223. err = AudioQueueEnqueueBuffer(ctx->queue, buf, 0, NULL);
  224. if (check_status(avctx, &err, "AudioQueueEnqueueBuffer")) {
  225. pthread_mutex_unlock(&ctx->buffer_lock[ctx->cur_buf]);
  226. return AVERROR(EINVAL);
  227. }
  228. return 0;
  229. }
  230. static av_cold int at_write_trailer(AVFormatContext *avctx)
  231. {
  232. ATContext *ctx = (ATContext*)avctx->priv_data;
  233. OSStatus err = noErr;
  234. pthread_mutex_destroy(&ctx->buffer_lock[0]);
  235. pthread_mutex_destroy(&ctx->buffer_lock[1]);
  236. err = AudioQueueFlush(ctx->queue);
  237. check_status(avctx, &err, "AudioQueueFlush");
  238. err = AudioQueueDispose(ctx->queue, true);
  239. check_status(avctx, &err, "AudioQueueDispose");
  240. return 0;
  241. }
  242. static const AVOption options[] = {
  243. { "list_devices", "list available audio devices", offsetof(ATContext, list_devices), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
  244. { "audio_device_index", "select audio device by index (starts at 0)", offsetof(ATContext, audio_device_index), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
  245. { NULL },
  246. };
  247. static const AVClass at_class = {
  248. .class_name = "AudioToolbox",
  249. .item_name = av_default_item_name,
  250. .option = options,
  251. .version = LIBAVUTIL_VERSION_INT,
  252. .category = AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT,
  253. };
  254. const FFOutputFormat ff_audiotoolbox_muxer = {
  255. .p.name = "audiotoolbox",
  256. .p.long_name = NULL_IF_CONFIG_SMALL("AudioToolbox output device"),
  257. .priv_data_size = sizeof(ATContext),
  258. .p.audio_codec = AV_NE(AV_CODEC_ID_PCM_S16BE, AV_CODEC_ID_PCM_S16LE),
  259. .p.video_codec = AV_CODEC_ID_NONE,
  260. .write_header = at_write_header,
  261. .write_packet = at_write_packet,
  262. .write_trailer = at_write_trailer,
  263. .p.flags = AVFMT_NOFILE,
  264. .p.priv_class = &at_class,
  265. };