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/** 23 * @file 24 * AudioToolbox output device 25 * @author Thilo Borgmann <thilo.borgmann@mail.de> 26 */ 27 28#import <AudioToolbox/AudioToolbox.h> 29#include <pthread.h> 30 31#include "libavutil/opt.h" 32#include "libavformat/internal.h" 33#include "libavutil/internal.h" 34#include "avdevice.h" 35 36typedef struct 37{ 38 AVClass *class; 39 40 AudioQueueBufferRef buffer[2]; 41 pthread_mutex_t buffer_lock[2]; 42 int cur_buf; 43 AudioQueueRef queue; 44 45 int list_devices; 46 int audio_device_index; 47 48} ATContext; 49 50static int check_status(AVFormatContext *avctx, OSStatus *status, const char *msg) 51{ 52 if (*status != noErr) { 53 av_log(avctx, AV_LOG_ERROR, "Error: %s (%i)\n", msg, *status); 54 return 1; 55 } else { 56 av_log(avctx, AV_LOG_DEBUG, " OK : %s\n", msg); 57 return 0; 58 } 59} 60 61static void queue_callback(void* atctx, AudioQueueRef inAQ, 62 AudioQueueBufferRef inBuffer) 63{ 64 // unlock the buffer that has just been consumed 65 ATContext *ctx = (ATContext*)atctx; 66 for (int i = 0; i < 2; i++) { 67 if (inBuffer == ctx->buffer[i]) { 68 pthread_mutex_unlock(&ctx->buffer_lock[i]); 69 } 70 } 71} 72 73static av_cold int at_write_header(AVFormatContext *avctx) 74{ 75 ATContext *ctx = (ATContext*)avctx->priv_data; 76 OSStatus err = noErr; 77 CFStringRef device_UID = NULL; 78 AudioDeviceID *devices; 79 int num_devices; 80 81 82 // get devices 83 UInt32 data_size = 0; 84 AudioObjectPropertyAddress prop; 85 prop.mSelector = kAudioHardwarePropertyDevices; 86 prop.mScope = kAudioObjectPropertyScopeGlobal; 87 prop.mElement = kAudioObjectPropertyElementMaster; 88 err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &prop, 0, NULL, &data_size); 89 if (check_status(avctx, &err, "AudioObjectGetPropertyDataSize devices")) 90 return AVERROR(EINVAL); 91 92 num_devices = data_size / sizeof(AudioDeviceID); 93 94 devices = (AudioDeviceID*)(av_malloc(data_size)); 95 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, NULL, &data_size, devices); 96 if (check_status(avctx, &err, "AudioObjectGetPropertyData devices")) { 97 av_freep(&devices); 98 return AVERROR(EINVAL); 99 } 100 101 // list devices 102 if (ctx->list_devices) { 103 CFStringRef device_name = NULL; 104 prop.mScope = kAudioDevicePropertyScopeInput; 105 106 av_log(ctx, AV_LOG_INFO, "CoreAudio devices:\n"); 107 for(UInt32 i = 0; i < num_devices; ++i) { 108 // UID 109 data_size = sizeof(device_UID); 110 prop.mSelector = kAudioDevicePropertyDeviceUID; 111 err = AudioObjectGetPropertyData(devices[i], &prop, 0, NULL, &data_size, &device_UID); 112 if (check_status(avctx, &err, "AudioObjectGetPropertyData UID")) 113 continue; 114 115 // name 116 data_size = sizeof(device_name); 117 prop.mSelector = kAudioDevicePropertyDeviceNameCFString; 118 err = AudioObjectGetPropertyData(devices[i], &prop, 0, NULL, &data_size, &device_name); 119 if (check_status(avctx, &err, "AudioObjecTGetPropertyData name")) 120 continue; 121 122 av_log(ctx, AV_LOG_INFO, "[%d] %30s, %s\n", i, 123 CFStringGetCStringPtr(device_name, kCFStringEncodingMacRoman), 124 CFStringGetCStringPtr(device_UID, kCFStringEncodingMacRoman)); 125 } 126 } 127 128 // get user-defined device UID or use default device 129 // -audio_device_index overrides any URL given 130 const char *stream_name = avctx->url; 131 if (stream_name && ctx->audio_device_index == -1) { 132 sscanf(stream_name, "%d", &ctx->audio_device_index); 133 } 134 135 if (ctx->audio_device_index >= 0) { 136 // get UID of selected device 137 data_size = sizeof(device_UID); 138 prop.mSelector = kAudioDevicePropertyDeviceUID; 139 err = AudioObjectGetPropertyData(devices[ctx->audio_device_index], &prop, 0, NULL, &data_size, &device_UID); 140 if (check_status(avctx, &err, "AudioObjecTGetPropertyData UID")) { 141 av_freep(&devices); 142 return AVERROR(EINVAL); 143 } 144 } else { 145 // use default device 146 device_UID = NULL; 147 } 148 149 av_log(ctx, AV_LOG_DEBUG, "stream_name: %s\n", stream_name); 150 av_log(ctx, AV_LOG_DEBUG, "audio_device_idnex: %i\n", ctx->audio_device_index); 151 av_log(ctx, AV_LOG_DEBUG, "UID: %s\n", CFStringGetCStringPtr(device_UID, kCFStringEncodingMacRoman)); 152 153 // check input stream 154 if (avctx->nb_streams != 1 || avctx->streams[0]->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) { 155 av_log(ctx, AV_LOG_ERROR, "Only a single audio stream is supported.\n"); 156 return AVERROR(EINVAL); 157 } 158 159 av_freep(&devices); 160 AVCodecParameters *codecpar = avctx->streams[0]->codecpar; 161 162 // audio format 163 AudioStreamBasicDescription device_format = {0}; 164 device_format.mSampleRate = codecpar->sample_rate; 165 device_format.mFormatID = kAudioFormatLinearPCM; 166 device_format.mFormatFlags |= (codecpar->format == AV_SAMPLE_FMT_FLT) ? kLinearPCMFormatFlagIsFloat : 0; 167 device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_S8) ? kLinearPCMFormatFlagIsSignedInteger : 0; 168 device_format.mFormatFlags |= (codecpar->codec_id == AV_NE(AV_CODEC_ID_PCM_S16BE, AV_CODEC_ID_PCM_S16LE)) ? kLinearPCMFormatFlagIsSignedInteger : 0; 169 device_format.mFormatFlags |= (codecpar->codec_id == AV_NE(AV_CODEC_ID_PCM_S24BE, AV_CODEC_ID_PCM_S24LE)) ? kLinearPCMFormatFlagIsSignedInteger : 0; 170 device_format.mFormatFlags |= (codecpar->codec_id == AV_NE(AV_CODEC_ID_PCM_S32BE, AV_CODEC_ID_PCM_S32LE)) ? kLinearPCMFormatFlagIsSignedInteger : 0; 171 device_format.mFormatFlags |= (av_sample_fmt_is_planar(codecpar->format)) ? kAudioFormatFlagIsNonInterleaved : 0; 172 device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_F32BE) ? kAudioFormatFlagIsBigEndian : 0; 173 device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_S16BE) ? kAudioFormatFlagIsBigEndian : 0; 174 device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_S24BE) ? kAudioFormatFlagIsBigEndian : 0; 175 device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_S32BE) ? kAudioFormatFlagIsBigEndian : 0; 176 device_format.mChannelsPerFrame = codecpar->channels; 177 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); 178 device_format.mBytesPerFrame = (device_format.mBitsPerChannel >> 3) * device_format.mChannelsPerFrame; 179 device_format.mFramesPerPacket = 1; 180 device_format.mBytesPerPacket = device_format.mBytesPerFrame * device_format.mFramesPerPacket; 181 device_format.mReserved = 0; 182 183 av_log(ctx, AV_LOG_DEBUG, "device_format.mSampleRate = %i\n", codecpar->sample_rate); 184 av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatID = %s\n", "kAudioFormatLinearPCM"); 185 av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->format == AV_SAMPLE_FMT_FLT) ? "kLinearPCMFormatFlagIsFloat" : "0"); 186 av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_S8) ? "kLinearPCMFormatFlagIsSignedInteger" : "0"); 187 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"); 188 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"); 189 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"); 190 av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (av_sample_fmt_is_planar(codecpar->format)) ? "kAudioFormatFlagIsNonInterleaved" : "0"); 191 av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_F32BE) ? "kAudioFormatFlagIsBigEndian" : "0"); 192 av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_S16BE) ? "kAudioFormatFlagIsBigEndian" : "0"); 193 av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_S24BE) ? "kAudioFormatFlagIsBigEndian" : "0"); 194 av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_S32BE) ? "kAudioFormatFlagIsBigEndian" : "0"); 195 av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags == %i\n", device_format.mFormatFlags); 196 av_log(ctx, AV_LOG_DEBUG, "device_format.mChannelsPerFrame = %i\n", codecpar->channels); 197 av_log(ctx, AV_LOG_DEBUG, "device_format.mBitsPerChannel = %i\n", av_get_bytes_per_sample(codecpar->format) << 3); 198 av_log(ctx, AV_LOG_DEBUG, "device_format.mBytesPerFrame = %i\n", (device_format.mBitsPerChannel >> 3) * codecpar->channels); 199 av_log(ctx, AV_LOG_DEBUG, "device_format.mBytesPerPacket = %i\n", device_format.mBytesPerFrame); 200 av_log(ctx, AV_LOG_DEBUG, "device_format.mFramesPerPacket = %i\n", 1); 201 av_log(ctx, AV_LOG_DEBUG, "device_format.mReserved = %i\n", 0); 202 203 // create new output queue for the device 204 err = AudioQueueNewOutput(&device_format, queue_callback, ctx, 205 NULL, kCFRunLoopCommonModes, 206 0, &ctx->queue); 207 if (check_status(avctx, &err, "AudioQueueNewOutput")) { 208 if (err == kAudioFormatUnsupportedDataFormatError) 209 av_log(ctx, AV_LOG_ERROR, "Unsupported output format.\n"); 210 return AVERROR(EINVAL); 211 } 212 213 // set user-defined device or leave untouched for default 214 if (device_UID != NULL) { 215 err = AudioQueueSetProperty(ctx->queue, kAudioQueueProperty_CurrentDevice, &device_UID, sizeof(device_UID)); 216 if (check_status(avctx, &err, "AudioQueueSetProperty output UID")) 217 return AVERROR(EINVAL); 218 } 219 220 // start the queue 221 err = AudioQueueStart(ctx->queue, NULL); 222 if (check_status(avctx, &err, "AudioQueueStart")) 223 return AVERROR(EINVAL); 224 225 // init the mutexes for double-buffering 226 pthread_mutex_init(&ctx->buffer_lock[0], NULL); 227 pthread_mutex_init(&ctx->buffer_lock[1], NULL); 228 229 return 0; 230} 231 232static int at_write_packet(AVFormatContext *avctx, AVPacket *pkt) 233{ 234 ATContext *ctx = (ATContext*)avctx->priv_data; 235 OSStatus err = noErr; 236 237 // use the other buffer 238 ctx->cur_buf = !ctx->cur_buf; 239 240 // lock for writing or wait for the buffer to be available 241 // will be unlocked by queue callback 242 pthread_mutex_lock(&ctx->buffer_lock[ctx->cur_buf]); 243 244 // (re-)allocate the buffer if not existant or of different size 245 if (!ctx->buffer[ctx->cur_buf] || ctx->buffer[ctx->cur_buf]->mAudioDataBytesCapacity != pkt->size) { 246 err = AudioQueueAllocateBuffer(ctx->queue, pkt->size, &ctx->buffer[ctx->cur_buf]); 247 if (check_status(avctx, &err, "AudioQueueAllocateBuffer")) { 248 pthread_mutex_unlock(&ctx->buffer_lock[ctx->cur_buf]); 249 return AVERROR(ENOMEM); 250 } 251 } 252 253 AudioQueueBufferRef buf = ctx->buffer[ctx->cur_buf]; 254 255 // copy audio data into buffer and enqueue the buffer 256 memcpy(buf->mAudioData, pkt->data, buf->mAudioDataBytesCapacity); 257 buf->mAudioDataByteSize = buf->mAudioDataBytesCapacity; 258 err = AudioQueueEnqueueBuffer(ctx->queue, buf, 0, NULL); 259 if (check_status(avctx, &err, "AudioQueueEnqueueBuffer")) { 260 pthread_mutex_unlock(&ctx->buffer_lock[ctx->cur_buf]); 261 return AVERROR(EINVAL); 262 } 263 264 return 0; 265} 266 267static av_cold int at_write_trailer(AVFormatContext *avctx) 268{ 269 ATContext *ctx = (ATContext*)avctx->priv_data; 270 OSStatus err = noErr; 271 272 pthread_mutex_destroy(&ctx->buffer_lock[0]); 273 pthread_mutex_destroy(&ctx->buffer_lock[1]); 274 275 err = AudioQueueFlush(ctx->queue); 276 check_status(avctx, &err, "AudioQueueFlush"); 277 err = AudioQueueDispose(ctx->queue, true); 278 check_status(avctx, &err, "AudioQueueDispose"); 279 280 return 0; 281} 282 283static const AVOption options[] = { 284 { "list_devices", "list available audio devices", offsetof(ATContext, list_devices), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM }, 285 { "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 }, 286 { NULL }, 287}; 288 289static const AVClass at_class = { 290 .class_name = "AudioToolbox", 291 .item_name = av_default_item_name, 292 .option = options, 293 .version = LIBAVUTIL_VERSION_INT, 294 .category = AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT, 295}; 296 297AVOutputFormat ff_audiotoolbox_muxer = { 298 .name = "audiotoolbox", 299 .long_name = NULL_IF_CONFIG_SMALL("AudioToolbox output device"), 300 .priv_data_size = sizeof(ATContext), 301 .audio_codec = AV_NE(AV_CODEC_ID_PCM_S16BE, AV_CODEC_ID_PCM_S16LE), 302 .video_codec = AV_CODEC_ID_NONE, 303 .write_header = at_write_header, 304 .write_packet = at_write_packet, 305 .write_trailer = at_write_trailer, 306 .flags = AVFMT_NOFILE, 307 .priv_class = &at_class, 308}; 309