You've already forked FFmpeg
							
							
				mirror of
				https://github.com/FFmpeg/FFmpeg.git
				synced 2025-10-30 23:18:11 +02:00 
			
		
		
		
	This commit does for AVOutputFormat what commit
20f9727018 did for AVCodec:
It adds a new type FFOutputFormat, moves all the internals
of AVOutputFormat to it and adds a now reduced AVOutputFormat
as first member.
This does not affect/improve extensibility of both public
or private fields for muxers (it is still a mess due to lavd).
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
Signed-off-by: Anton Khirnov <anton@khirnov.net>
		
	
		
			
				
	
	
		
			310 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
			
		
		
	
	
			310 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
| /*
 | |
|  * 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/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;
 | |
|     prop.mElement  = kAudioObjectPropertyElementMaster;
 | |
|     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,
 | |
| };
 |