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