FFmpeg
Loading...
Searching...
No Matches
audiotoolbox.m
Go to the documentation of this file.
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/mem.h"
32#include "libavutil/opt.h"
34#include "libavformat/mux.h"
35#include "libavutil/internal.h"
36#include "avdevice.h"
37
38typedef struct
39{
40 AVClass *class;
41
42 AudioQueueBufferRef buffer[2];
45 AudioQueueRef queue;
46
49
50} ATContext;
51
52static int check_status(AVFormatContext *avctx, OSStatus *status, const char *msg)
53{
54 if (*status != noErr) {
55 av_log(avctx, AV_LOG_ERROR, "Error: %s (%i)\n", msg, *status);
56 return 1;
57 } else {
58 av_log(avctx, AV_LOG_DEBUG, " OK : %s\n", msg);
59 return 0;
60 }
61}
62
63static void queue_callback(void* atctx, AudioQueueRef inAQ,
64 AudioQueueBufferRef inBuffer)
65{
66 // unlock the buffer that has just been consumed
67 ATContext *ctx = (ATContext*)atctx;
68 for (int i = 0; i < 2; i++) {
69 if (inBuffer == ctx->buffer[i]) {
70 pthread_mutex_unlock(&ctx->buffer_lock[i]);
71 }
72 }
73}
74
76{
77 ATContext *ctx = (ATContext*)avctx->priv_data;
78 OSStatus err = noErr;
79 CFStringRef device_UID = NULL;
80 AudioDeviceID *devices;
81 int num_devices;
82
83
84 // get devices
85 UInt32 data_size = 0;
86 AudioObjectPropertyAddress prop;
87 prop.mSelector = kAudioHardwarePropertyDevices;
88 prop.mScope = kAudioObjectPropertyScopeGlobal;
89#if !TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 120000
90 prop.mElement = kAudioObjectPropertyElementMain;
91#else
92 prop.mElement = kAudioObjectPropertyElementMaster;
93#endif
94 err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &prop, 0, NULL, &data_size);
95 if (check_status(avctx, &err, "AudioObjectGetPropertyDataSize devices"))
96 return AVERROR(EINVAL);
97
98 num_devices = data_size / sizeof(AudioDeviceID);
99
100 devices = (AudioDeviceID*)(av_malloc(data_size));
101 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, NULL, &data_size, devices);
102 if (check_status(avctx, &err, "AudioObjectGetPropertyData devices")) {
103 av_freep(&devices);
104 return AVERROR(EINVAL);
105 }
106
107 // list devices
108 if (ctx->list_devices) {
109 CFStringRef device_name = NULL;
110 prop.mScope = kAudioDevicePropertyScopeInput;
111
112 av_log(ctx, AV_LOG_INFO, "CoreAudio devices:\n");
113 for(UInt32 i = 0; i < num_devices; ++i) {
114 // UID
115 data_size = sizeof(device_UID);
116 prop.mSelector = kAudioDevicePropertyDeviceUID;
117 err = AudioObjectGetPropertyData(devices[i], &prop, 0, NULL, &data_size, &device_UID);
118 if (check_status(avctx, &err, "AudioObjectGetPropertyData UID"))
119 continue;
120
121 // name
122 data_size = sizeof(device_name);
123 prop.mSelector = kAudioDevicePropertyDeviceNameCFString;
124 err = AudioObjectGetPropertyData(devices[i], &prop, 0, NULL, &data_size, &device_name);
125 if (check_status(avctx, &err, "AudioObjecTGetPropertyData name"))
126 continue;
127
128 av_log(ctx, AV_LOG_INFO, "[%d] %30s, %s\n", i,
129 CFStringGetCStringPtr(device_name, kCFStringEncodingMacRoman),
130 CFStringGetCStringPtr(device_UID, kCFStringEncodingMacRoman));
131 }
132 }
133
134 // get user-defined device UID or use default device
135 // -audio_device_index overrides any URL given
136 const char *stream_name = avctx->url;
137 if (stream_name && ctx->audio_device_index == -1) {
138 sscanf(stream_name, "%d", &ctx->audio_device_index);
139 }
140
141 if (ctx->audio_device_index >= 0) {
142 // get UID of selected device
143 data_size = sizeof(device_UID);
144 prop.mSelector = kAudioDevicePropertyDeviceUID;
145 err = AudioObjectGetPropertyData(devices[ctx->audio_device_index], &prop, 0, NULL, &data_size, &device_UID);
146 if (check_status(avctx, &err, "AudioObjecTGetPropertyData UID")) {
147 av_freep(&devices);
148 return AVERROR(EINVAL);
149 }
150 } else {
151 // use default device
152 device_UID = NULL;
153 }
154
155 av_log(ctx, AV_LOG_DEBUG, "stream_name: %s\n", stream_name);
156 av_log(ctx, AV_LOG_DEBUG, "audio_device_idnex: %i\n", ctx->audio_device_index);
157 av_log(ctx, AV_LOG_DEBUG, "UID: %s\n", CFStringGetCStringPtr(device_UID, kCFStringEncodingMacRoman));
158
159 // check input stream
160 if (avctx->nb_streams != 1 || avctx->streams[0]->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) {
161 av_log(ctx, AV_LOG_ERROR, "Only a single audio stream is supported.\n");
162 return AVERROR(EINVAL);
163 }
164
165 av_freep(&devices);
166 AVCodecParameters *codecpar = avctx->streams[0]->codecpar;
167
168 // audio format
169 AudioStreamBasicDescription device_format = {0};
170 device_format.mSampleRate = codecpar->sample_rate;
171 device_format.mFormatID = kAudioFormatLinearPCM;
172 device_format.mFormatFlags |= (codecpar->format == AV_SAMPLE_FMT_FLT) ? kLinearPCMFormatFlagIsFloat : 0;
173 device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_S8) ? kLinearPCMFormatFlagIsSignedInteger : 0;
174 device_format.mFormatFlags |= (codecpar->codec_id == AV_NE(AV_CODEC_ID_PCM_S16BE, AV_CODEC_ID_PCM_S16LE)) ? kLinearPCMFormatFlagIsSignedInteger : 0;
175 device_format.mFormatFlags |= (codecpar->codec_id == AV_NE(AV_CODEC_ID_PCM_S24BE, AV_CODEC_ID_PCM_S24LE)) ? kLinearPCMFormatFlagIsSignedInteger : 0;
176 device_format.mFormatFlags |= (codecpar->codec_id == AV_NE(AV_CODEC_ID_PCM_S32BE, AV_CODEC_ID_PCM_S32LE)) ? kLinearPCMFormatFlagIsSignedInteger : 0;
177 device_format.mFormatFlags |= (av_sample_fmt_is_planar(codecpar->format)) ? kAudioFormatFlagIsNonInterleaved : 0;
178 device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_F32BE) ? kAudioFormatFlagIsBigEndian : 0;
179 device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_S16BE) ? kAudioFormatFlagIsBigEndian : 0;
180 device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_S24BE) ? kAudioFormatFlagIsBigEndian : 0;
181 device_format.mFormatFlags |= (codecpar->codec_id == AV_CODEC_ID_PCM_S32BE) ? kAudioFormatFlagIsBigEndian : 0;
182 device_format.mChannelsPerFrame = codecpar->ch_layout.nb_channels;
183 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);
184 device_format.mBytesPerFrame = (device_format.mBitsPerChannel >> 3) * device_format.mChannelsPerFrame;
185 device_format.mFramesPerPacket = 1;
186 device_format.mBytesPerPacket = device_format.mBytesPerFrame * device_format.mFramesPerPacket;
187 device_format.mReserved = 0;
188
189 av_log(ctx, AV_LOG_DEBUG, "device_format.mSampleRate = %i\n", codecpar->sample_rate);
190 av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatID = %s\n", "kAudioFormatLinearPCM");
191 av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->format == AV_SAMPLE_FMT_FLT) ? "kLinearPCMFormatFlagIsFloat" : "0");
192 av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_S8) ? "kLinearPCMFormatFlagIsSignedInteger" : "0");
193 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");
194 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");
195 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");
196 av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (av_sample_fmt_is_planar(codecpar->format)) ? "kAudioFormatFlagIsNonInterleaved" : "0");
197 av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_F32BE) ? "kAudioFormatFlagIsBigEndian" : "0");
198 av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_S16BE) ? "kAudioFormatFlagIsBigEndian" : "0");
199 av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_S24BE) ? "kAudioFormatFlagIsBigEndian" : "0");
200 av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags |= %s\n", (codecpar->codec_id == AV_CODEC_ID_PCM_S32BE) ? "kAudioFormatFlagIsBigEndian" : "0");
201 av_log(ctx, AV_LOG_DEBUG, "device_format.mFormatFlags == %i\n", device_format.mFormatFlags);
202 av_log(ctx, AV_LOG_DEBUG, "device_format.mChannelsPerFrame = %i\n", codecpar->ch_layout.nb_channels);
203 av_log(ctx, AV_LOG_DEBUG, "device_format.mBitsPerChannel = %i\n", av_get_bytes_per_sample(codecpar->format) << 3);
204 av_log(ctx, AV_LOG_DEBUG, "device_format.mBytesPerFrame = %i\n", (device_format.mBitsPerChannel >> 3) * codecpar->ch_layout.nb_channels);
205 av_log(ctx, AV_LOG_DEBUG, "device_format.mBytesPerPacket = %i\n", device_format.mBytesPerFrame);
206 av_log(ctx, AV_LOG_DEBUG, "device_format.mFramesPerPacket = %i\n", 1);
207 av_log(ctx, AV_LOG_DEBUG, "device_format.mReserved = %i\n", 0);
208
209 // create new output queue for the device
210 err = AudioQueueNewOutput(&device_format, queue_callback, ctx,
211 NULL, kCFRunLoopCommonModes,
212 0, &ctx->queue);
213 if (check_status(avctx, &err, "AudioQueueNewOutput")) {
214 if (err == kAudioFormatUnsupportedDataFormatError)
215 av_log(ctx, AV_LOG_ERROR, "Unsupported output format.\n");
216 return AVERROR(EINVAL);
217 }
218
219 // set user-defined device or leave untouched for default
220 if (device_UID != NULL) {
221 err = AudioQueueSetProperty(ctx->queue, kAudioQueueProperty_CurrentDevice, &device_UID, sizeof(device_UID));
222 if (check_status(avctx, &err, "AudioQueueSetProperty output UID"))
223 return AVERROR(EINVAL);
224 }
225
226 // start the queue
227 err = AudioQueueStart(ctx->queue, NULL);
228 if (check_status(avctx, &err, "AudioQueueStart"))
229 return AVERROR(EINVAL);
230
231 // init the mutexes for double-buffering
232 pthread_mutex_init(&ctx->buffer_lock[0], NULL);
233 pthread_mutex_init(&ctx->buffer_lock[1], NULL);
234
235 return 0;
236}
237
239{
240 ATContext *ctx = (ATContext*)avctx->priv_data;
241 OSStatus err = noErr;
242
243 // use the other buffer
244 ctx->cur_buf = !ctx->cur_buf;
245
246 // lock for writing or wait for the buffer to be available
247 // will be unlocked by queue callback
248 pthread_mutex_lock(&ctx->buffer_lock[ctx->cur_buf]);
249
250 // (re-)allocate the buffer if not existent or of different size
251 if (!ctx->buffer[ctx->cur_buf] || ctx->buffer[ctx->cur_buf]->mAudioDataBytesCapacity != pkt->size) {
252 err = AudioQueueAllocateBuffer(ctx->queue, pkt->size, &ctx->buffer[ctx->cur_buf]);
253 if (check_status(avctx, &err, "AudioQueueAllocateBuffer")) {
254 pthread_mutex_unlock(&ctx->buffer_lock[ctx->cur_buf]);
255 return AVERROR(ENOMEM);
256 }
257 }
258
259 AudioQueueBufferRef buf = ctx->buffer[ctx->cur_buf];
260
261 // copy audio data into buffer and enqueue the buffer
262 memcpy(buf->mAudioData, pkt->data, buf->mAudioDataBytesCapacity);
263 buf->mAudioDataByteSize = buf->mAudioDataBytesCapacity;
264 err = AudioQueueEnqueueBuffer(ctx->queue, buf, 0, NULL);
265 if (check_status(avctx, &err, "AudioQueueEnqueueBuffer")) {
266 pthread_mutex_unlock(&ctx->buffer_lock[ctx->cur_buf]);
267 return AVERROR(EINVAL);
268 }
269
270 return 0;
271}
272
274{
275 ATContext *ctx = (ATContext*)avctx->priv_data;
276 OSStatus err = noErr;
277
278 pthread_mutex_destroy(&ctx->buffer_lock[0]);
279 pthread_mutex_destroy(&ctx->buffer_lock[1]);
280
281 err = AudioQueueFlush(ctx->queue);
282 check_status(avctx, &err, "AudioQueueFlush");
283 err = AudioQueueDispose(ctx->queue, true);
284 check_status(avctx, &err, "AudioQueueDispose");
285
286 return 0;
287}
288
289static const AVOption options[] = {
290 { "list_devices", "list available audio devices", offsetof(ATContext, list_devices), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
291 { "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 },
292 { NULL },
293};
294
295static const AVClass at_class = {
296 .class_name = "AudioToolbox",
297 .item_name = av_default_item_name,
298 .option = options,
299 .version = LIBAVUTIL_VERSION_INT,
301};
302
304 .p.name = "audiotoolbox",
305 .p.long_name = NULL_IF_CONFIG_SMALL("AudioToolbox output device"),
306 .priv_data_size = sizeof(ATContext),
308 .p.video_codec = AV_CODEC_ID_NONE,
309 .write_header = at_write_header,
310 .write_packet = at_write_packet,
311 .write_trailer = at_write_trailer,
312 .p.flags = AVFMT_NOFILE,
313 .p.priv_class = &at_class,
314};
const FFOutputFormat ff_audiotoolbox_muxer
static av_cold int at_write_trailer(AVFormatContext *avctx)
static const AVClass at_class
static av_cold int at_write_header(AVFormatContext *avctx)
static int at_write_packet(AVFormatContext *avctx, AVPacket *pkt)
static void queue_callback(void *atctx, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
static int check_status(AVFormatContext *avctx, OSStatus *status, const char *msg)
Main libavdevice API header.
#define AVFMT_NOFILE
Demuxer will use avio_open, no opened file should be provided by the caller.
Definition avformat.h:488
#define i(width, name, range_min, range_max)
Definition cbs_h264.c:63
#define NULL
Definition coverity.c:32
static AVPacket * pkt
#define AV_OPT_FLAG_ENCODING_PARAM
A generic parameter which can be set by the user for muxing or encoding.
Definition opt.h:351
@ AV_OPT_TYPE_INT
Underlying C type is int.
Definition opt.h:258
@ AV_OPT_TYPE_BOOL
Underlying C type is int.
Definition opt.h:326
@ AV_CODEC_ID_PCM_S24BE
Definition codec_id.h:343
@ AV_CODEC_ID_PCM_S16LE
Definition codec_id.h:330
@ AV_CODEC_ID_PCM_F32BE
Definition codec_id.h:350
@ AV_CODEC_ID_NONE
Definition codec_id.h:48
@ AV_CODEC_ID_PCM_S16BE
Definition codec_id.h:331
@ AV_CODEC_ID_PCM_S24LE
Definition codec_id.h:342
@ AV_CODEC_ID_PCM_S32LE
Definition codec_id.h:338
@ AV_CODEC_ID_PCM_S8
Definition codec_id.h:334
@ AV_CODEC_ID_PCM_S32BE
Definition codec_id.h:339
#define AVERROR(e)
Definition error.h:45
#define AV_LOG_DEBUG
Stuff which is only useful for libav* developers.
Definition log.h:231
#define AV_LOG_INFO
Standard information.
Definition log.h:221
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition log.h:210
const char * av_default_item_name(void *ptr)
Return the context name.
Definition log.c:241
@ AVMEDIA_TYPE_AUDIO
Definition avutil.h:201
int av_sample_fmt_is_planar(enum AVSampleFormat sample_fmt)
Check if the sample format is planar.
Definition samplefmt.c:114
int av_get_bytes_per_sample(enum AVSampleFormat sample_fmt)
Return number of bytes per sample.
Definition samplefmt.c:108
@ AV_SAMPLE_FMT_FLT
float
Definition samplefmt.h:60
#define LIBAVUTIL_VERSION_INT
Definition version.h:85
#define av_cold
Definition attributes.h:117
common internal API header
#define NULL_IF_CONFIG_SMALL(x)
Return NULL if CONFIG_SMALL is true, otherwise the argument without modification.
Definition internal.h:88
@ AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT
Definition log.h:43
#define AV_NE(be, le)
Definition macros.h:33
Memory handling functions.
#define av_malloc(s)
Definition ops_static.c:52
AVOptions.
static av_always_inline int pthread_mutex_lock(pthread_mutex_t *mutex)
Definition os2threads.h:119
static av_always_inline int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)
Definition os2threads.h:104
_fmutex pthread_mutex_t
Definition os2threads.h:53
static av_always_inline int pthread_mutex_unlock(pthread_mutex_t *mutex)
Definition os2threads.h:126
static av_always_inline int pthread_mutex_destroy(pthread_mutex_t *mutex)
Definition os2threads.h:112
pthread_mutex_t buffer_lock[2]
AudioQueueBufferRef buffer[2]
int list_devices
AudioQueueRef queue
int audio_device_index
int nb_channels
Number of channels in this layout.
Describe the class of an AVClass context structure.
Definition log.h:76
This struct describes the properties of an encoded stream.
Definition codec_par.h:49
AVChannelLayout ch_layout
The channel layout and number of channels.
Definition codec_par.h:207
enum AVMediaType codec_type
General type of the encoded data.
Definition codec_par.h:53
enum AVCodecID codec_id
Specific type of the encoded data (the codec used).
Definition codec_par.h:57
int sample_rate
The number of audio samples per second.
Definition codec_par.h:213
Format I/O context.
Definition avformat.h:1333
unsigned int nb_streams
Number of elements in AVFormatContext.streams.
Definition avformat.h:1389
char * url
input or output URL.
Definition avformat.h:1449
void * priv_data
Format private data.
Definition avformat.h:1361
AVStream ** streams
A list of all streams in the file.
Definition avformat.h:1401
AVOption.
Definition opt.h:428
This structure stores compressed data.
Definition packet.h:580
AVCodecParameters * codecpar
Codec parameters associated with this stream.
Definition avformat.h:789
#define av_freep(p)
#define av_log(a,...)
static AVFormatContext * ctx
Definition movenc.c:49