123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- /*
- * AudioToolbox output device
- * Copyright (c) 2020 Thilo Borgmann <thilo.borgmann@mail.de>
- *
- * This file is part of FFmpeg.
- *
- * FFmpeg is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * FFmpeg is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with FFmpeg; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
- /**
- * @file
- * AudioToolbox output device
- * @author Thilo Borgmann <thilo.borgmann@mail.de>
- */
- #import <AudioToolbox/AudioToolbox.h>
- #include <pthread.h>
- #include "libavutil/mem.h"
- #include "libavutil/opt.h"
- #include "libavformat/internal.h"
- #include "libavformat/mux.h"
- #include "libavutil/internal.h"
- #include "avdevice.h"
- typedef struct
- {
- AVClass *class;
- AudioQueueBufferRef buffer[2];
- pthread_mutex_t buffer_lock[2];
- int cur_buf;
- AudioQueueRef queue;
- int list_devices;
- int audio_device_index;
- } ATContext;
- static int check_status(AVFormatContext *avctx, OSStatus *status, const char *msg)
- {
- if (*status != noErr) {
- av_log(avctx, AV_LOG_ERROR, "Error: %s (%i)\n", msg, *status);
- return 1;
- } else {
- av_log(avctx, AV_LOG_DEBUG, " OK : %s\n", msg);
- return 0;
- }
- }
- static void queue_callback(void* atctx, AudioQueueRef inAQ,
- AudioQueueBufferRef inBuffer)
- {
- // unlock the buffer that has just been consumed
- ATContext *ctx = (ATContext*)atctx;
- for (int i = 0; i < 2; i++) {
- if (inBuffer == ctx->buffer[i]) {
- pthread_mutex_unlock(&ctx->buffer_lock[i]);
- }
- }
- }
- static av_cold int at_write_header(AVFormatContext *avctx)
- {
- ATContext *ctx = (ATContext*)avctx->priv_data;
- OSStatus err = noErr;
- CFStringRef device_UID = NULL;
- AudioDeviceID *devices;
- int num_devices;
- // get devices
- UInt32 data_size = 0;
- AudioObjectPropertyAddress prop;
- prop.mSelector = kAudioHardwarePropertyDevices;
- prop.mScope = kAudioObjectPropertyScopeGlobal;
- #if !TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 120000
- prop.mElement = kAudioObjectPropertyElementMain;
- #else
- prop.mElement = kAudioObjectPropertyElementMaster;
- #endif
- err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &prop, 0, NULL, &data_size);
- if (check_status(avctx, &err, "AudioObjectGetPropertyDataSize devices"))
- return AVERROR(EINVAL);
- num_devices = data_size / sizeof(AudioDeviceID);
- devices = (AudioDeviceID*)(av_malloc(data_size));
- err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, NULL, &data_size, devices);
- if (check_status(avctx, &err, "AudioObjectGetPropertyData devices")) {
- av_freep(&devices);
- return AVERROR(EINVAL);
- }
- // list devices
- if (ctx->list_devices) {
- CFStringRef device_name = NULL;
- prop.mScope = kAudioDevicePropertyScopeInput;
- av_log(ctx, AV_LOG_INFO, "CoreAudio devices:\n");
- for(UInt32 i = 0; i < num_devices; ++i) {
- // UID
- data_size = sizeof(device_UID);
- prop.mSelector = kAudioDevicePropertyDeviceUID;
- err = AudioObjectGetPropertyData(devices[i], &prop, 0, NULL, &data_size, &device_UID);
- if (check_status(avctx, &err, "AudioObjectGetPropertyData UID"))
- continue;
- // name
- data_size = sizeof(device_name);
- prop.mSelector = kAudioDevicePropertyDeviceNameCFString;
- err = AudioObjectGetPropertyData(devices[i], &prop, 0, NULL, &data_size, &device_name);
- if (check_status(avctx, &err, "AudioObjecTGetPropertyData name"))
- continue;
- av_log(ctx, AV_LOG_INFO, "[%d] %30s, %s\n", i,
- CFStringGetCStringPtr(device_name, kCFStringEncodingMacRoman),
- CFStringGetCStringPtr(device_UID, kCFStringEncodingMacRoman));
- }
- }
- // get user-defined device UID or use default device
- // -audio_device_index overrides any URL given
- const char *stream_name = avctx->url;
- if (stream_name && ctx->audio_device_index == -1) {
- sscanf(stream_name, "%d", &ctx->audio_device_index);
- }
- if (ctx->audio_device_index >= 0) {
- // get UID of selected device
- data_size = sizeof(device_UID);
- prop.mSelector = kAudioDevicePropertyDeviceUID;
- err = AudioObjectGetPropertyData(devices[ctx->audio_device_index], &prop, 0, NULL, &data_size, &device_UID);
- if (check_status(avctx, &err, "AudioObjecTGetPropertyData UID")) {
- av_freep(&devices);
- return AVERROR(EINVAL);
- }
- } else {
- // use default device
- device_UID = NULL;
- }
- av_log(ctx, AV_LOG_DEBUG, "stream_name: %s\n", stream_name);
- av_log(ctx, AV_LOG_DEBUG, "audio_device_idnex: %i\n", ctx->audio_device_index);
- av_log(ctx, AV_LOG_DEBUG, "UID: %s\n", CFStringGetCStringPtr(device_UID, kCFStringEncodingMacRoman));
- // check input stream
- if (avctx->nb_streams != 1 || avctx->streams[0]->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) {
- av_log(ctx, AV_LOG_ERROR, "Only a single audio stream is supported.\n");
- return AVERROR(EINVAL);
- }
- av_freep(&devices);
- AVCodecParameters *codecpar = avctx->streams[0]->codecpar;
- // audio format
- AudioStreamBasicDescription device_format = {0};
- device_format.mSampleRate = codecpar->sample_rate;
- device_format.mFormatID = kAudioFormatLinearPCM;
- device_format.mFormatFlags |= (codecpar->format == AV_SAMPLE_FMT_FLT) ? kLinearPCMFormatFlagIsFloat : 0;
- device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_S8) ? kLinearPCMFormatFlagIsSignedInteger : 0;
- device_format.mFormatFlags |= (codecpar->codec_id == AV_NE(AV_CODEC_ID_PCM_S16BE, AV_CODEC_ID_PCM_S16LE)) ? kLinearPCMFormatFlagIsSignedInteger : 0;
- device_format.mFormatFlags |= (codecpar->codec_id == AV_NE(AV_CODEC_ID_PCM_S24BE, AV_CODEC_ID_PCM_S24LE)) ? kLinearPCMFormatFlagIsSignedInteger : 0;
- device_format.mFormatFlags |= (codecpar->codec_id == AV_NE(AV_CODEC_ID_PCM_S32BE, AV_CODEC_ID_PCM_S32LE)) ? kLinearPCMFormatFlagIsSignedInteger : 0;
- device_format.mFormatFlags |= (av_sample_fmt_is_planar(codecpar->format)) ? kAudioFormatFlagIsNonInterleaved : 0;
- device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_F32BE) ? kAudioFormatFlagIsBigEndian : 0;
- device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_S16BE) ? kAudioFormatFlagIsBigEndian : 0;
- device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_S24BE) ? kAudioFormatFlagIsBigEndian : 0;
- device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_S32BE) ? kAudioFormatFlagIsBigEndian : 0;
- device_format.mChannelsPerFrame = codecpar->ch_layout.nb_channels;
- 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);
- device_format.mBytesPerFrame = (device_format.mBitsPerChannel >> 3) * device_format.mChannelsPerFrame;
- device_format.mFramesPerPacket = 1;
- device_format.mBytesPerPacket = device_format.mBytesPerFrame * device_format.mFramesPerPacket;
- device_format.mReserved = 0;
- av_log(ctx, AV_LOG_DEBUG, "device_format.mSampleRate = %i\n", codecpar->sample_rate);
- av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatID = %s\n", "kAudioFormatLinearPCM");
- av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->format == AV_SAMPLE_FMT_FLT) ? "kLinearPCMFormatFlagIsFloat" : "0");
- av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_S8) ? "kLinearPCMFormatFlagIsSignedInteger" : "0");
- 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");
- 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");
- 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");
- av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (av_sample_fmt_is_planar(codecpar->format)) ? "kAudioFormatFlagIsNonInterleaved" : "0");
- av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_F32BE) ? "kAudioFormatFlagIsBigEndian" : "0");
- av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_S16BE) ? "kAudioFormatFlagIsBigEndian" : "0");
- av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_S24BE) ? "kAudioFormatFlagIsBigEndian" : "0");
- av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_S32BE) ? "kAudioFormatFlagIsBigEndian" : "0");
- av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags == %i\n", device_format.mFormatFlags);
- av_log(ctx, AV_LOG_DEBUG, "device_format.mChannelsPerFrame = %i\n", codecpar->ch_layout.nb_channels);
- av_log(ctx, AV_LOG_DEBUG, "device_format.mBitsPerChannel = %i\n", av_get_bytes_per_sample(codecpar->format) << 3);
- av_log(ctx, AV_LOG_DEBUG, "device_format.mBytesPerFrame = %i\n", (device_format.mBitsPerChannel >> 3) * codecpar->ch_layout.nb_channels);
- av_log(ctx, AV_LOG_DEBUG, "device_format.mBytesPerPacket = %i\n", device_format.mBytesPerFrame);
- av_log(ctx, AV_LOG_DEBUG, "device_format.mFramesPerPacket = %i\n", 1);
- av_log(ctx, AV_LOG_DEBUG, "device_format.mReserved = %i\n", 0);
- // create new output queue for the device
- err = AudioQueueNewOutput(&device_format, queue_callback, ctx,
- NULL, kCFRunLoopCommonModes,
- 0, &ctx->queue);
- if (check_status(avctx, &err, "AudioQueueNewOutput")) {
- if (err == kAudioFormatUnsupportedDataFormatError)
- av_log(ctx, AV_LOG_ERROR, "Unsupported output format.\n");
- return AVERROR(EINVAL);
- }
- // set user-defined device or leave untouched for default
- if (device_UID != NULL) {
- err = AudioQueueSetProperty(ctx->queue, kAudioQueueProperty_CurrentDevice, &device_UID, sizeof(device_UID));
- if (check_status(avctx, &err, "AudioQueueSetProperty output UID"))
- return AVERROR(EINVAL);
- }
- // start the queue
- err = AudioQueueStart(ctx->queue, NULL);
- if (check_status(avctx, &err, "AudioQueueStart"))
- return AVERROR(EINVAL);
- // init the mutexes for double-buffering
- pthread_mutex_init(&ctx->buffer_lock[0], NULL);
- pthread_mutex_init(&ctx->buffer_lock[1], NULL);
- return 0;
- }
- static int at_write_packet(AVFormatContext *avctx, AVPacket *pkt)
- {
- ATContext *ctx = (ATContext*)avctx->priv_data;
- OSStatus err = noErr;
- // use the other buffer
- ctx->cur_buf = !ctx->cur_buf;
- // lock for writing or wait for the buffer to be available
- // will be unlocked by queue callback
- pthread_mutex_lock(&ctx->buffer_lock[ctx->cur_buf]);
- // (re-)allocate the buffer if not existant or of different size
- if (!ctx->buffer[ctx->cur_buf] || ctx->buffer[ctx->cur_buf]->mAudioDataBytesCapacity != pkt->size) {
- err = AudioQueueAllocateBuffer(ctx->queue, pkt->size, &ctx->buffer[ctx->cur_buf]);
- if (check_status(avctx, &err, "AudioQueueAllocateBuffer")) {
- pthread_mutex_unlock(&ctx->buffer_lock[ctx->cur_buf]);
- return AVERROR(ENOMEM);
- }
- }
- AudioQueueBufferRef buf = ctx->buffer[ctx->cur_buf];
- // copy audio data into buffer and enqueue the buffer
- memcpy(buf->mAudioData, pkt->data, buf->mAudioDataBytesCapacity);
- buf->mAudioDataByteSize = buf->mAudioDataBytesCapacity;
- err = AudioQueueEnqueueBuffer(ctx->queue, buf, 0, NULL);
- if (check_status(avctx, &err, "AudioQueueEnqueueBuffer")) {
- pthread_mutex_unlock(&ctx->buffer_lock[ctx->cur_buf]);
- return AVERROR(EINVAL);
- }
- return 0;
- }
- static av_cold int at_write_trailer(AVFormatContext *avctx)
- {
- ATContext *ctx = (ATContext*)avctx->priv_data;
- OSStatus err = noErr;
- pthread_mutex_destroy(&ctx->buffer_lock[0]);
- pthread_mutex_destroy(&ctx->buffer_lock[1]);
- err = AudioQueueFlush(ctx->queue);
- check_status(avctx, &err, "AudioQueueFlush");
- err = AudioQueueDispose(ctx->queue, true);
- check_status(avctx, &err, "AudioQueueDispose");
- return 0;
- }
- static const AVOption options[] = {
- { "list_devices", "list available audio devices", offsetof(ATContext, list_devices), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
- { "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 },
- { NULL },
- };
- static const AVClass at_class = {
- .class_name = "AudioToolbox",
- .item_name = av_default_item_name,
- .option = options,
- .version = LIBAVUTIL_VERSION_INT,
- .category = AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT,
- };
- const FFOutputFormat ff_audiotoolbox_muxer = {
- .p.name = "audiotoolbox",
- .p.long_name = NULL_IF_CONFIG_SMALL("AudioToolbox output device"),
- .priv_data_size = sizeof(ATContext),
- .p.audio_codec = AV_NE(AV_CODEC_ID_PCM_S16BE, AV_CODEC_ID_PCM_S16LE),
- .p.video_codec = AV_CODEC_ID_NONE,
- .write_header = at_write_header,
- .write_packet = at_write_packet,
- .write_trailer = at_write_trailer,
- .p.flags = AVFMT_NOFILE,
- .p.priv_class = &at_class,
- };
|