FFmpeg  4.4.5
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/opt.h"
32 #include "libavformat/internal.h"
33 #include "libavutil/internal.h"
34 #include "avdevice.h"
35 
36 typedef 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 
47 
48 } ATContext;
49 
50 static 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 
61 static 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 
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 
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 
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 
283 static 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 
289 static const AVClass at_class = {
290  .class_name = "AudioToolbox",
291  .item_name = av_default_item_name,
292  .option = options,
293  .version = LIBAVUTIL_VERSION_INT,
295 };
296 
298  .name = "audiotoolbox",
299  .long_name = NULL_IF_CONFIG_SMALL("AudioToolbox output device"),
300  .priv_data_size = sizeof(ATContext),
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 };
#define av_cold
Definition: attributes.h:88
static av_cold int at_write_trailer(AVFormatContext *avctx)
Definition: audiotoolbox.m:267
static const AVClass at_class
Definition: audiotoolbox.m:289
static const AVOption options[]
Definition: audiotoolbox.m:283
static av_cold int at_write_header(AVFormatContext *avctx)
Definition: audiotoolbox.m:73
static int at_write_packet(AVFormatContext *avctx, AVPacket *pkt)
Definition: audiotoolbox.m:232
AVOutputFormat ff_audiotoolbox_muxer
Definition: audiotoolbox.m:297
static void queue_callback(void *atctx, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
Definition: audiotoolbox.m:61
static int check_status(AVFormatContext *avctx, OSStatus *status, const char *msg)
Definition: audiotoolbox.m:50
Main libavdevice API header.
#define AVFMT_NOFILE
Demuxer will use avio_open, no opened file should be provided by the caller.
Definition: avformat.h:458
#define AV_NE(be, le)
Definition: common.h:50
#define NULL
Definition: coverity.c:32
#define pthread_mutex_lock(a)
Definition: ffprobe.c:63
#define pthread_mutex_unlock(a)
Definition: ffprobe.c:67
@ AV_OPT_TYPE_INT
Definition: opt.h:225
@ AV_OPT_TYPE_BOOL
Definition: opt.h:242
@ AV_CODEC_ID_PCM_S24BE
Definition: codec_id.h:326
@ AV_CODEC_ID_PCM_S16LE
Definition: codec_id.h:313
@ AV_CODEC_ID_PCM_F32BE
Definition: codec_id.h:333
@ AV_CODEC_ID_NONE
Definition: codec_id.h:47
@ AV_CODEC_ID_PCM_S16BE
Definition: codec_id.h:314
@ AV_CODEC_ID_PCM_S24LE
Definition: codec_id.h:325
@ AV_CODEC_ID_PCM_S32LE
Definition: codec_id.h:321
@ AV_CODEC_ID_PCM_S8
Definition: codec_id.h:317
@ AV_CODEC_ID_PCM_S32BE
Definition: codec_id.h:322
#define AVERROR(e)
Definition: error.h:43
#define AV_LOG_DEBUG
Stuff which is only useful for libav* developers.
Definition: log.h:215
#define AV_LOG_INFO
Standard information.
Definition: log.h:205
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition: log.h:194
const char * av_default_item_name(void *ptr)
Return the context name.
Definition: log.c:235
@ AVMEDIA_TYPE_AUDIO
Definition: avutil.h:202
int av_sample_fmt_is_planar(enum AVSampleFormat sample_fmt)
Check if the sample format is planar.
Definition: samplefmt.c:112
int av_get_bytes_per_sample(enum AVSampleFormat sample_fmt)
Return number of bytes per sample.
Definition: samplefmt.c:106
@ AV_SAMPLE_FMT_FLT
float
Definition: samplefmt.h:63
#define LIBAVUTIL_VERSION_INT
Definition: version.h:85
int i
Definition: input.c:407
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:117
@ AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT
Definition: log.h:43
AVOptions.
#define AV_OPT_FLAG_ENCODING_PARAM
a generic parameter which can be set by the user for muxing or encoding
Definition: opt.h:278
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_destroy(pthread_mutex_t *mutex)
Definition: os2threads.h:112
static char buffer[20]
Definition: seek.c:32
int list_devices
Definition: audiotoolbox.m:45
AudioQueueRef queue
Definition: audiotoolbox.m:43
int audio_device_index
Definition: audiotoolbox.m:46
Describe the class of an AVClass context structure.
Definition: log.h:67
const char * class_name
The name of the class; usually it is the same name as the context structure type to which the AVClass...
Definition: log.h:72
This struct describes the properties of an encoded stream.
Definition: codec_par.h:52
int channels
Audio only.
Definition: codec_par.h:166
enum AVMediaType codec_type
General type of the encoded data.
Definition: codec_par.h:56
enum AVCodecID codec_id
Specific type of the encoded data (the codec used).
Definition: codec_par.h:60
int sample_rate
Audio only.
Definition: codec_par.h:170
Format I/O context.
Definition: avformat.h:1232
unsigned int nb_streams
Number of elements in AVFormatContext.streams.
Definition: avformat.h:1288
char * url
input or output URL.
Definition: avformat.h:1328
void * priv_data
Format private data.
Definition: avformat.h:1260
AVStream ** streams
A list of all streams in the file.
Definition: avformat.h:1300
AVOption.
Definition: opt.h:248
const char * name
Definition: avformat.h:491
This structure stores compressed data.
Definition: packet.h:346
int size
Definition: packet.h:370
uint8_t * data
Definition: packet.h:369
AVCodecParameters * codecpar
Codec parameters associated with this stream.
Definition: avformat.h:1038
#define av_freep(p)
#define av_malloc(s)
#define av_log(a,...)
AVPacket * pkt
Definition: movenc.c:59
AVFormatContext * ctx
Definition: movenc.c:48