History

lavdevice: Add AudioToolbox output device.

Thilo Borgmann committed 6 years ago

Commit b7375751 parent 9d80f3e6 files · +360 −0Browse files

Showing the changes to libavdevice (3 of 6 files). Show all

Unified · Split

3 files
  1. libavdevice/Makefile +1 −0
  2. libavdevice/alldevices.c +1 −0
  3. libavdevice/audiotoolbox.m +308 −0
libavdevice/Makefile+1−0
@@ -15,6 +15,7 @@ OBJS-$(CONFIG_SHARED) += reverse.o · 14 unmodified lines
1515 OBJS-$(CONFIG_ALSA_INDEV) += alsa_dec.o alsa.o timefilter.o
1616 OBJS-$(CONFIG_ALSA_OUTDEV) += alsa_enc.o alsa.o
1717 OBJS-$(CONFIG_ANDROID_CAMERA_INDEV) += android_camera.o
18+OBJS-$(CONFIG_AUDIOTOOLBOX_OUTDEV) += audiotoolbox.o
1819 OBJS-$(CONFIG_AVFOUNDATION_INDEV) += avfoundation.o
1920 OBJS-$(CONFIG_BKTR_INDEV) += bktr.o
2021 OBJS-$(CONFIG_CACA_OUTDEV) += caca.o
libavdevice/alldevices.c+1−0
@@ -27,6 +27,7 @@ · 26 unmodified lines
2727 extern AVInputFormat ff_alsa_demuxer;
2828 extern AVOutputFormat ff_alsa_muxer;
2929 extern AVInputFormat ff_android_camera_demuxer;
30+extern AVOutputFormat ff_audiotoolbox_muxer;
3031 extern AVInputFormat ff_avfoundation_demuxer;
3132 extern AVInputFormat ff_bktr_demuxer;
3233 extern AVOutputFormat ff_caca_muxer;
libavdevice/audiotoolbox.m+308−0
@@ -0,0 +1,308 @@
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+
45+ int list_devices;
46+ int audio_device_index;
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+
73+static 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+
232+static 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+
267+static 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+
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,
294+ .category = AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT,
295+};
296+
297+AVOutputFormat 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+};

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t