* [FFmpeg-devel] [PATCH 0/6] Various libavdevice cleanup & enhancements
@ 2022-03-22 13:39 toots
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 1/6] Fix dshow device name/description toots
` (6 more replies)
0 siblings, 7 replies; 11+ messages in thread
From: toots @ 2022-03-22 13:39 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Romain Beauxis
From: Romain Beauxis <toots@rastageeks.org>
This is a series of patches that fix and enhances libavdevice & have been submitted multiple times in the past 6 months. In the series, the most straightforward patches have been placed first in the list.
While I have a lot of appreciation for all the solid work done in this project, seeing the difficulty in getting them included makes one wonder what is the status of libavdevice and if it is still actively maintained.
Typically, in the first patch, we fix the name/machine readable ID of dshow devices. If the API has any significant use or maintenance, this should have been caught and fixed much sooner.
Next, the patches fixing the avfoundation device audio format and concurrency models are absolutely required to get any use of this operator as audio input and, quite likely, as video input as well.
This is a lot of changes to make the library usable for a cross-platform project and, with the lack of traction with the changes, this makes the developer wonder if the library is ready for production use and if it wouldn't be easier to implement hardware handling separately and plug into FFmpeg after retreiving media data.
There are more that I have noticed with the API, in particular the fact that audio/video devices are labelled as video. I would love to submit changes to enhance that aspect but, not with all these patches already pending..
Thanks for y'all consideration.
-- Romain
Romain Beauxis (6):
Fix dshow device name/description
Use appropriate method for device discovery, fix crash with bogus
device index.
libavdevice/avfoundation.m: Allow to select devices by unique ID
libavdevice/avfoundation.m: use setAudioSettings, extend supported
formats
libavdevice/avfoundation.m: Replace mutex-based concurrency handling
in avfoundation.m by a thread-safe fifo queue with maximum length
Add AudioToolbox audio input device.
configure | 5 +
doc/indevs.texi | 41 ++-
libavdevice/Makefile | 1 +
libavdevice/alldevices.c | 1 +
libavdevice/audiotoolbox.m | 8 +-
libavdevice/audiotoolbox_dec.m | 530 ++++++++++++++++++++++++++++++++
libavdevice/avfoundation.m | 547 ++++++++++++++++-----------------
libavdevice/dshow.c | 4 +-
8 files changed, 843 insertions(+), 294 deletions(-)
create mode 100644 libavdevice/audiotoolbox_dec.m
--
2.32.0 (Apple Git-132)
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 11+ messages in thread
* [FFmpeg-devel] [PATCH 1/6] Fix dshow device name/description
2022-03-22 13:39 [FFmpeg-devel] [PATCH 0/6] Various libavdevice cleanup & enhancements toots
@ 2022-03-22 13:39 ` toots
2022-03-22 14:10 ` Roger Pack
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 2/6] Use appropriate method for device discovery, fix crash with bogus device index toots
` (5 subsequent siblings)
6 siblings, 1 reply; 11+ messages in thread
From: toots @ 2022-03-22 13:39 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Romain Beauxis
From: Romain Beauxis <romain.beauxis@nextstep.com>
diff --git a/libavdevice/dshow.c b/libavdevice/dshow.c
index 6039578ff9..4ee3f6e194 100644
--- a/libavdevice/dshow.c
+++ b/libavdevice/dshow.c
@@ -552,8 +552,8 @@ dshow_cycle_devices(AVFormatContext *avctx, ICreateDevEnum *devenum,
if (!device)
goto fail;
- device->device_name = av_strdup(friendly_name);
- device->device_description = av_strdup(unique_name);
+ device->device_name = av_strdup(unique_name);
+ device->device_description = av_strdup(friendly_name);
if (!device->device_name || !device->device_description)
goto fail;
--
2.32.0 (Apple Git-132)
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 11+ messages in thread
* [FFmpeg-devel] [PATCH 2/6] Use appropriate method for device discovery, fix crash with bogus device index.
2022-03-22 13:39 [FFmpeg-devel] [PATCH 0/6] Various libavdevice cleanup & enhancements toots
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 1/6] Fix dshow device name/description toots
@ 2022-03-22 13:39 ` toots
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 3/6] libavdevice/avfoundation.m: Allow to select devices by unique ID toots
` (4 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: toots @ 2022-03-22 13:39 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Romain Beauxis
From: Romain Beauxis <toots@rastageeks.org>
diff --git a/libavdevice/avfoundation.m b/libavdevice/avfoundation.m
index c9de93f774..719276cabf 100644
--- a/libavdevice/avfoundation.m
+++ b/libavdevice/avfoundation.m
@@ -27,6 +27,7 @@
#import <AVFoundation/AVFoundation.h>
#include <pthread.h>
+#include <Availability.h>
#include "libavutil/channel_layout.h"
#include "libavutil/pixdesc.h"
@@ -771,8 +772,34 @@ static int avf_read_header(AVFormatContext *s)
AVCaptureDevice *video_device = nil;
AVCaptureDevice *audio_device = nil;
// Find capture device
- NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
- NSArray *devices_muxed = [AVCaptureDevice devicesWithMediaType:AVMediaTypeMuxed];
+#if defined(__MAC_10_15) || (TARGET_OS_IPHONE && defined(__IPHONE_10_0))
+ AVCaptureDeviceDiscoverySession *discoverySession =
+ [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[
+#if TARGET_OS_IPHONE
+ AVCaptureDeviceTypeBuiltInDualCamera,
+ AVCaptureDeviceTypeBuiltInDualWideCamera,
+ AVCaptureDeviceTypeBuiltInUltraWideCamera,
+ AVCaptureDeviceTypeBuiltInTrueDepthCamera,
+ AVCaptureDeviceTypeBuiltInTelephotoCamera,
+#endif
+ AVCaptureDeviceTypeBuiltInWideAngleCamera,
+ AVCaptureDeviceTypeExternalUnknown
+ ]
+ mediaType:NULL
+ position:AVCaptureDevicePositionUnspecified];
+
+ NSMutableArray *devices = [NSMutableArray array];
+ NSMutableArray *devices_muxed = [NSMutableArray array];
+ for (AVCaptureDevice *device in [discoverySession devices]) {
+ if ([device hasMediaType:AVMediaTypeVideo])
+ [devices addObject:device];
+ else if ([device hasMediaType:AVMediaTypeMuxed])
+ [devices_muxed addObject:device];
+ }
+#else
+ NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
+ NSArray *devices_muxed = [AVCaptureDevice devicesWithMediaType:AVMediaTypeMuxed];
+#endif
ctx->num_video_devices = [devices count] + [devices_muxed count];
@@ -782,6 +809,21 @@ static int avf_read_header(AVFormatContext *s)
CGGetActiveDisplayList(0, NULL, &num_screens);
#endif
+ NSArray *audio_devices;
+#if defined(__MAC_10_15) || (TARGET_OS_IPHONE && defined(__IPHONE_10_0))
+ discoverySession =
+ [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[
+ AVCaptureDeviceTypeBuiltInMicrophone,
+ AVCaptureDeviceTypeExternalUnknown
+ ]
+ mediaType:AVMediaTypeAudio
+ position:AVCaptureDevicePositionUnspecified];
+
+ audio_devices = [discoverySession devices];
+#else
+ audio_devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio];
+#endif
+
// List devices if requested
if (ctx->list_devices) {
int index = 0;
@@ -807,8 +849,7 @@ static int avf_read_header(AVFormatContext *s)
#endif
av_log(ctx, AV_LOG_INFO, "AVFoundation audio devices:\n");
- devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio];
- for (AVCaptureDevice *device in devices) {
+ for (AVCaptureDevice *device in audio_devices) {
const char *name = [[device localizedName] UTF8String];
int index = [devices indexOfObject:device];
av_log(ctx, AV_LOG_INFO, "[%d] %s\n", index, name);
@@ -833,9 +874,12 @@ static int avf_read_header(AVFormatContext *s)
if (ctx->video_device_index < ctx->num_video_devices) {
if (ctx->video_device_index < [devices count]) {
video_device = [devices objectAtIndex:ctx->video_device_index];
- } else {
+ } else if (ctx->video_device_index - [devices count] < [devices_muxed count]) {
video_device = [devices_muxed objectAtIndex:(ctx->video_device_index - [devices count])];
ctx->video_is_muxed = 1;
+ } else {
+ av_log(ctx, AV_LOG_ERROR, "Invalid video device index\n");
+ goto fail;
}
} else if (ctx->video_device_index < ctx->num_video_devices + num_screens) {
#if !TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
@@ -931,9 +975,7 @@ static int avf_read_header(AVFormatContext *s)
// get audio device
if (ctx->audio_device_index >= 0) {
- NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio];
-
- if (ctx->audio_device_index >= [devices count]) {
+ if (ctx->audio_device_index >= [audio_devices count]) {
av_log(ctx, AV_LOG_ERROR, "Invalid audio device index\n");
goto fail;
}
@@ -944,15 +986,14 @@ static int avf_read_header(AVFormatContext *s)
if (!strncmp(ctx->audio_filename, "default", 7)) {
audio_device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
} else {
- NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio];
-
- for (AVCaptureDevice *device in devices) {
- if (!strncmp(ctx->audio_filename, [[device localizedName] UTF8String], strlen(ctx->audio_filename))) {
- audio_device = device;
- break;
+ for (AVCaptureDevice *device in audio_devices) {
+ const char *name = [[device localizedName] UTF8String];
+ if (!strncmp(ctx->audio_filename, name, strlen(ctx->audio_filename))) {
+ audio_device = device;
+ break;
+ }
}
}
- }
if (!audio_device) {
av_log(ctx, AV_LOG_ERROR, "Audio device not found\n");
--
2.32.0 (Apple Git-132)
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 11+ messages in thread
* [FFmpeg-devel] [PATCH 3/6] libavdevice/avfoundation.m: Allow to select devices by unique ID
2022-03-22 13:39 [FFmpeg-devel] [PATCH 0/6] Various libavdevice cleanup & enhancements toots
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 1/6] Fix dshow device name/description toots
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 2/6] Use appropriate method for device discovery, fix crash with bogus device index toots
@ 2022-03-22 13:39 ` toots
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 4/6] libavdevice/avfoundation.m: use setAudioSettings, extend supported formats toots
` (3 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: toots @ 2022-03-22 13:39 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Romain Beauxis
From: Romain Beauxis <toots@rastageeks.org>
diff --git a/doc/indevs.texi b/doc/indevs.texi
index 9d8020311a..858c0fa4e4 100644
--- a/doc/indevs.texi
+++ b/doc/indevs.texi
@@ -114,7 +114,7 @@ The input filename has to be given in the following syntax:
-i "[[VIDEO]:[AUDIO]]"
@end example
The first entry selects the video input while the latter selects the audio input.
-The stream has to be specified by the device name or the device index as shown by the device list.
+The stream has to be specified by the device name, index or ID as shown by the device list.
Alternatively, the video and/or audio input device can be chosen by index using the
@option{
-video_device_index <INDEX>
@@ -127,7 +127,9 @@ and/or
device name or index given in the input filename.
All available devices can be enumerated by using @option{-list_devices true}, listing
-all device names and corresponding indices.
+all device names, corresponding indices and IDs, when available. Device name can be
+tricky to use when localized and device index can change when devices are plugged or unplugged. A device
+hash, when available, uniquely identifies a device and should not change over time.
There are two device name aliases:
@table @code
diff --git a/libavdevice/avfoundation.m b/libavdevice/avfoundation.m
index 719276cabf..af52246bf3 100644
--- a/libavdevice/avfoundation.m
+++ b/libavdevice/avfoundation.m
@@ -40,6 +40,8 @@
#include "libavutil/imgutils.h"
#include "avdevice.h"
+#define CLEANUP_DEVICE_ID(s) [[s stringByReplacingOccurrencesOfString:@":" withString:@"."] UTF8String]
+
static const int avf_time_base = 1000000;
static const AVRational avf_time_base_q = {
@@ -829,21 +831,23 @@ static int avf_read_header(AVFormatContext *s)
int index = 0;
av_log(ctx, AV_LOG_INFO, "AVFoundation video devices:\n");
for (AVCaptureDevice *device in devices) {
- const char *name = [[device localizedName] UTF8String];
- index = [devices indexOfObject:device];
- av_log(ctx, AV_LOG_INFO, "[%d] %s\n", index, name);
+ const char *name = [[device localizedName] UTF8String];
+ const char *uniqueId = CLEANUP_DEVICE_ID([device uniqueID]);
+ index = [devices indexOfObject:device];
+ av_log(ctx, AV_LOG_INFO, "[%d] %s (ID: %s)\n", index, name, uniqueId);
}
for (AVCaptureDevice *device in devices_muxed) {
- const char *name = [[device localizedName] UTF8String];
- index = [devices count] + [devices_muxed indexOfObject:device];
- av_log(ctx, AV_LOG_INFO, "[%d] %s\n", index, name);
+ const char *name = [[device localizedName] UTF8String];
+ const char *uniqueId = CLEANUP_DEVICE_ID([device uniqueID]);
+ index = [devices count] + [devices_muxed indexOfObject:device];
+ av_log(ctx, AV_LOG_INFO, "[%d] %s (ID: %s)\n", index, name, uniqueId);
}
#if !TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
if (num_screens > 0) {
CGDirectDisplayID screens[num_screens];
CGGetActiveDisplayList(num_screens, screens, &num_screens);
for (int i = 0; i < num_screens; i++) {
- av_log(ctx, AV_LOG_INFO, "[%d] Capture screen %d\n", ctx->num_video_devices + i, i);
+ av_log(ctx, AV_LOG_INFO, "[%d] Capture screen %d (ID: AvfilterAvfoundationCaptureScreen%d)\n", ctx->num_video_devices + i, i, screens[i]);
}
}
#endif
@@ -851,7 +855,9 @@ static int avf_read_header(AVFormatContext *s)
av_log(ctx, AV_LOG_INFO, "AVFoundation audio devices:\n");
for (AVCaptureDevice *device in audio_devices) {
const char *name = [[device localizedName] UTF8String];
+ const char *uniqueId = CLEANUP_DEVICE_ID([device uniqueID]);
int index = [devices indexOfObject:device];
+ av_log(ctx, AV_LOG_INFO, "[%d] %s (ID: %s)\n", index, name, uniqueId);
av_log(ctx, AV_LOG_INFO, "[%d] %s\n", index, name);
}
goto fail;
@@ -919,14 +925,29 @@ static int avf_read_header(AVFormatContext *s)
} else {
// looking for video inputs
for (AVCaptureDevice *device in devices) {
- if (!strncmp(ctx->video_filename, [[device localizedName] UTF8String], strlen(ctx->video_filename))) {
+ const char *name = [[device localizedName] UTF8String];
+ if (!strncmp(ctx->video_filename, name, strlen(ctx->video_filename))) {
+ video_device = device;
+ break;
+ }
+
+ const char *uniqueId = CLEANUP_DEVICE_ID([device uniqueID]);
+ if (!strncmp(ctx->video_filename, uniqueId, strlen(ctx->video_filename))) {
video_device = device;
break;
}
}
// looking for muxed inputs
for (AVCaptureDevice *device in devices_muxed) {
- if (!strncmp(ctx->video_filename, [[device localizedName] UTF8String], strlen(ctx->video_filename))) {
+ const char *name = [[device localizedName] UTF8String];
+ if (!strncmp(ctx->video_filename, name, strlen(ctx->video_filename))) {
+ video_device = device;
+ ctx->video_is_muxed = 1;
+ break;
+ }
+
+ const char *uniqueId = CLEANUP_DEVICE_ID([device uniqueID]);
+ if (!strncmp(ctx->video_filename, uniqueId, strlen(ctx->video_filename))) {
video_device = device;
ctx->video_is_muxed = 1;
break;
@@ -937,10 +958,23 @@ static int avf_read_header(AVFormatContext *s)
// looking for screen inputs
if (!video_device) {
int idx;
+ CGDirectDisplayID screens[num_screens];
+ CGGetActiveDisplayList(num_screens, screens, &num_screens);
+ AVCaptureScreenInput* capture_screen_input = NULL;
+
if(sscanf(ctx->video_filename, "Capture screen %d", &idx) && idx < num_screens) {
- CGDirectDisplayID screens[num_screens];
- CGGetActiveDisplayList(num_screens, screens, &num_screens);
- AVCaptureScreenInput* capture_screen_input = [[[AVCaptureScreenInput alloc] initWithDisplayID:screens[idx]] autorelease];
+ capture_screen_input = [[[AVCaptureScreenInput alloc] initWithDisplayID:screens[idx]] autorelease];
+ }
+
+ if(sscanf(ctx->video_filename, "AvfilterAvfoundationCaptureScreen%d", &idx)) {
+ for (int i = 0; i < num_screens; i++) {
+ if (screens[i] == idx) {
+ capture_screen_input = [[[AVCaptureScreenInput alloc] initWithDisplayID:idx] autorelease];
+ }
+ }
+ }
+
+ if (capture_screen_input) {
video_device = (AVCaptureDevice*) capture_screen_input;
ctx->video_device_index = ctx->num_video_devices + idx;
ctx->video_is_screen = 1;
@@ -992,6 +1026,12 @@ static int avf_read_header(AVFormatContext *s)
audio_device = device;
break;
}
+
+ const char *uniqueId = CLEANUP_DEVICE_ID([device uniqueID]);
+ if (!strncmp(ctx->audio_filename, uniqueId, strlen(ctx->audio_filename))) {
+ audio_device = device;
+ break;
+ }
}
}
--
2.32.0 (Apple Git-132)
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 11+ messages in thread
* [FFmpeg-devel] [PATCH 4/6] libavdevice/avfoundation.m: use setAudioSettings, extend supported formats
2022-03-22 13:39 [FFmpeg-devel] [PATCH 0/6] Various libavdevice cleanup & enhancements toots
` (2 preceding siblings ...)
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 3/6] libavdevice/avfoundation.m: Allow to select devices by unique ID toots
@ 2022-03-22 13:39 ` toots
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 5/6] libavdevice/avfoundation.m: Replace mutex-based concurrency handling in avfoundation.m by a thread-safe fifo queue with maximum length toots
` (2 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: toots @ 2022-03-22 13:39 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Romain Beauxis
From: Romain Beauxis <toots@rastageeks.org>
Previous version of these changes used the AudioConverter API to perform audio conversion explicitly however, it was found to be bug prone with issues seemingly coming from the underlying OS.
This fixes: https://trac.ffmpeg.org/ticket/9502
---
libavdevice/avfoundation.m | 200 ++++++++++++-------------------------
1 file changed, 63 insertions(+), 137 deletions(-)
diff --git a/libavdevice/avfoundation.m b/libavdevice/avfoundation.m
index af52246bf3..055e8f62e4 100644
--- a/libavdevice/avfoundation.m
+++ b/libavdevice/avfoundation.m
@@ -96,6 +96,11 @@
AVRational framerate;
int width, height;
+ int channels;
+ int big_endian;
+ int sample_rate;
+ enum AVSampleFormat sample_format;
+
int capture_cursor;
int capture_mouse_clicks;
int capture_raw_data;
@@ -115,17 +120,6 @@
int num_video_devices;
- int audio_channels;
- int audio_bits_per_sample;
- int audio_float;
- int audio_be;
- int audio_signed_integer;
- int audio_packed;
- int audio_non_interleaved;
-
- int32_t *audio_buffer;
- int audio_buffer_size;
-
enum AVPixelFormat pixel_format;
AVCaptureSession *capture_session;
@@ -304,7 +298,6 @@ static void destroy_context(AVFContext* ctx)
ctx->avf_audio_delegate = NULL;
av_freep(&ctx->url);
- av_freep(&ctx->audio_buffer);
pthread_mutex_destroy(&ctx->frame_lock);
@@ -680,88 +673,61 @@ static int get_video_config(AVFormatContext *s)
static int get_audio_config(AVFormatContext *s)
{
AVFContext *ctx = (AVFContext*)s->priv_data;
- CMFormatDescriptionRef format_desc;
- AVStream* stream = avformat_new_stream(s, NULL);
+ AVStream* stream;
+ int bits_per_sample, is_float;
- if (!stream) {
- return 1;
- }
+ enum AVCodecID codec_id = av_get_pcm_codec(ctx->sample_format, ctx->big_endian);
- // Take stream info from the first frame.
- while (ctx->audio_frames_captured < 1) {
- CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, YES);
+ if (codec_id == AV_CODEC_ID_NONE) {
+ av_log(ctx, AV_LOG_ERROR, "Error: invalid sample format!\n");
+ return AVERROR(EINVAL);
}
- lock_frames(ctx);
-
- ctx->audio_stream_index = stream->index;
-
- avpriv_set_pts_info(stream, 64, 1, avf_time_base);
-
- format_desc = CMSampleBufferGetFormatDescription(ctx->current_audio_frame);
- const AudioStreamBasicDescription *basic_desc = CMAudioFormatDescriptionGetStreamBasicDescription(format_desc);
-
- if (!basic_desc) {
- unlock_frames(ctx);
- av_log(s, AV_LOG_ERROR, "audio format not available\n");
- return 1;
+ switch (ctx->sample_format) {
+ case AV_SAMPLE_FMT_S16:
+ bits_per_sample = 16;
+ is_float = 0;
+ break;
+ case AV_SAMPLE_FMT_S32:
+ bits_per_sample = 32;
+ is_float = 0;
+ break;
+ case AV_SAMPLE_FMT_FLT:
+ bits_per_sample = 32;
+ is_float = 1;
+ break;
+ default:
+ av_log(ctx, AV_LOG_ERROR, "Error: invalid sample format!\n");
+ unlock_frames(ctx);
+ return AVERROR(EINVAL);
}
- stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
- stream->codecpar->sample_rate = basic_desc->mSampleRate;
- stream->codecpar->channels = basic_desc->mChannelsPerFrame;
- stream->codecpar->channel_layout = av_get_default_channel_layout(stream->codecpar->channels);
-
- ctx->audio_channels = basic_desc->mChannelsPerFrame;
- ctx->audio_bits_per_sample = basic_desc->mBitsPerChannel;
- ctx->audio_float = basic_desc->mFormatFlags & kAudioFormatFlagIsFloat;
- ctx->audio_be = basic_desc->mFormatFlags & kAudioFormatFlagIsBigEndian;
- ctx->audio_signed_integer = basic_desc->mFormatFlags & kAudioFormatFlagIsSignedInteger;
- ctx->audio_packed = basic_desc->mFormatFlags & kAudioFormatFlagIsPacked;
- ctx->audio_non_interleaved = basic_desc->mFormatFlags & kAudioFormatFlagIsNonInterleaved;
-
- if (basic_desc->mFormatID == kAudioFormatLinearPCM &&
- ctx->audio_float &&
- ctx->audio_bits_per_sample == 32 &&
- ctx->audio_packed) {
- stream->codecpar->codec_id = ctx->audio_be ? AV_CODEC_ID_PCM_F32BE : AV_CODEC_ID_PCM_F32LE;
- } else if (basic_desc->mFormatID == kAudioFormatLinearPCM &&
- ctx->audio_signed_integer &&
- ctx->audio_bits_per_sample == 16 &&
- ctx->audio_packed) {
- stream->codecpar->codec_id = ctx->audio_be ? AV_CODEC_ID_PCM_S16BE : AV_CODEC_ID_PCM_S16LE;
- } else if (basic_desc->mFormatID == kAudioFormatLinearPCM &&
- ctx->audio_signed_integer &&
- ctx->audio_bits_per_sample == 24 &&
- ctx->audio_packed) {
- stream->codecpar->codec_id = ctx->audio_be ? AV_CODEC_ID_PCM_S24BE : AV_CODEC_ID_PCM_S24LE;
- } else if (basic_desc->mFormatID == kAudioFormatLinearPCM &&
- ctx->audio_signed_integer &&
- ctx->audio_bits_per_sample == 32 &&
- ctx->audio_packed) {
- stream->codecpar->codec_id = ctx->audio_be ? AV_CODEC_ID_PCM_S32BE : AV_CODEC_ID_PCM_S32LE;
- } else {
+ [ctx->audio_output setAudioSettings:@{
+ AVFormatIDKey: @(kAudioFormatLinearPCM),
+ AVLinearPCMBitDepthKey: @(bits_per_sample),
+ AVLinearPCMIsFloatKey: @(is_float),
+ AVLinearPCMIsBigEndianKey: @(ctx->big_endian),
+ AVNumberOfChannelsKey: @(ctx->channels),
+ AVLinearPCMIsNonInterleaved: @NO,
+ AVSampleRateKey: @(ctx->sample_rate)
+ }];
+
+ stream = avformat_new_stream(s, NULL);
+ if (!stream) {
unlock_frames(ctx);
- av_log(s, AV_LOG_ERROR, "audio format is not supported\n");
- return 1;
+ return -1;
}
- if (ctx->audio_non_interleaved) {
- CMBlockBufferRef block_buffer = CMSampleBufferGetDataBuffer(ctx->current_audio_frame);
- ctx->audio_buffer_size = CMBlockBufferGetDataLength(block_buffer);
- ctx->audio_buffer = av_malloc(ctx->audio_buffer_size);
- if (!ctx->audio_buffer) {
- unlock_frames(ctx);
- av_log(s, AV_LOG_ERROR, "error allocating audio buffer\n");
- return 1;
- }
- }
+ avpriv_set_pts_info(stream, 64, 1, avf_time_base);
- CFRelease(ctx->current_audio_frame);
- ctx->current_audio_frame = nil;
+ stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
+ stream->codecpar->sample_rate = ctx->sample_rate;
+ av_channel_layout_default(&stream->codecpar->ch_layout, ctx->channels);
+ stream->codecpar->codec_id = codec_id;
- unlock_frames(ctx);
+ ctx->audio_stream_index = stream->index;
+ unlock_frames(ctx);
return 0;
}
@@ -1065,6 +1031,7 @@ static int avf_read_header(AVFormatContext *s)
goto fail;
}
if (audio_device && add_audio_device(s, audio_device)) {
+ goto fail;
}
[ctx->capture_session startRunning];
@@ -1140,6 +1107,7 @@ static int copy_cvpixelbuffer(AVFormatContext *s,
static int avf_read_packet(AVFormatContext *s, AVPacket *pkt)
{
+ OSStatus ret;
AVFContext* ctx = (AVFContext*)s->priv_data;
do {
@@ -1183,7 +1151,7 @@ static int avf_read_packet(AVFormatContext *s, AVPacket *pkt)
status = copy_cvpixelbuffer(s, image_buffer, pkt);
} else {
status = 0;
- OSStatus ret = CMBlockBufferCopyDataBytes(block_buffer, 0, pkt->size, pkt->data);
+ ret = CMBlockBufferCopyDataBytes(block_buffer, 0, pkt->size, pkt->data);
if (ret != kCMBlockBufferNoErr) {
status = AVERROR(EIO);
}
@@ -1197,19 +1165,17 @@ static int avf_read_packet(AVFormatContext *s, AVPacket *pkt)
}
} else if (ctx->current_audio_frame != nil) {
CMBlockBufferRef block_buffer = CMSampleBufferGetDataBuffer(ctx->current_audio_frame);
- int block_buffer_size = CMBlockBufferGetDataLength(block_buffer);
- if (!block_buffer || !block_buffer_size) {
- unlock_frames(ctx);
- return AVERROR(EIO);
- }
+ size_t buffer_size = CMBlockBufferGetDataLength(block_buffer);
- if (ctx->audio_non_interleaved && block_buffer_size > ctx->audio_buffer_size) {
+ int status = av_new_packet(pkt, buffer_size);
+ if (status < 0) {
unlock_frames(ctx);
- return AVERROR_BUFFER_TOO_SMALL;
+ return status;
}
- if (av_new_packet(pkt, block_buffer_size) < 0) {
+ ret = CMBlockBufferCopyDataBytes(block_buffer, 0, pkt->size, pkt->data);
+ if (ret != kCMBlockBufferNoErr) {
unlock_frames(ctx);
return AVERROR(EIO);
}
@@ -1225,54 +1191,10 @@ static int avf_read_packet(AVFormatContext *s, AVPacket *pkt)
pkt->stream_index = ctx->audio_stream_index;
pkt->flags |= AV_PKT_FLAG_KEY;
- if (ctx->audio_non_interleaved) {
- int sample, c, shift, num_samples;
-
- OSStatus ret = CMBlockBufferCopyDataBytes(block_buffer, 0, pkt->size, ctx->audio_buffer);
- if (ret != kCMBlockBufferNoErr) {
- unlock_frames(ctx);
- return AVERROR(EIO);
- }
-
- num_samples = pkt->size / (ctx->audio_channels * (ctx->audio_bits_per_sample >> 3));
-
- // transform decoded frame into output format
- #define INTERLEAVE_OUTPUT(bps) \
- { \
- int##bps##_t **src; \
- int##bps##_t *dest; \
- src = av_malloc(ctx->audio_channels * sizeof(int##bps##_t*)); \
- if (!src) { \
- unlock_frames(ctx); \
- return AVERROR(EIO); \
- } \
- \
- for (c = 0; c < ctx->audio_channels; c++) { \
- src[c] = ((int##bps##_t*)ctx->audio_buffer) + c * num_samples; \
- } \
- dest = (int##bps##_t*)pkt->data; \
- shift = bps - ctx->audio_bits_per_sample; \
- for (sample = 0; sample < num_samples; sample++) \
- for (c = 0; c < ctx->audio_channels; c++) \
- *dest++ = src[c][sample] << shift; \
- av_freep(&src); \
- }
-
- if (ctx->audio_bits_per_sample <= 16) {
- INTERLEAVE_OUTPUT(16)
- } else {
- INTERLEAVE_OUTPUT(32)
- }
- } else {
- OSStatus ret = CMBlockBufferCopyDataBytes(block_buffer, 0, pkt->size, pkt->data);
- if (ret != kCMBlockBufferNoErr) {
- unlock_frames(ctx);
- return AVERROR(EIO);
- }
- }
-
CFRelease(ctx->current_audio_frame);
ctx->current_audio_frame = nil;
+
+ unlock_frames(ctx);
} else {
pkt->data = NULL;
unlock_frames(ctx);
@@ -1297,6 +1219,10 @@ static int avf_close(AVFormatContext *s)
}
static const AVOption options[] = {
+ { "channels", "number of audio channels", offsetof(AVFContext, channels), AV_OPT_TYPE_INT, {.i64=2}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
+ { "sample_rate", "audio sample rate", offsetof(AVFContext, sample_rate), AV_OPT_TYPE_INT, {.i64=44100}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
+ { "big_endian", "return big endian samples for audio data", offsetof(AVFContext, big_endian), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
+ { "sample_format", "audio sample format", offsetof(AVFContext, sample_format), AV_OPT_TYPE_INT, {.i64=AV_SAMPLE_FMT_S16}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
{ "list_devices", "list available devices", offsetof(AVFContext, list_devices), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, AV_OPT_FLAG_DECODING_PARAM },
{ "video_device_index", "select video device by index for devices with same name (starts at 0)", offsetof(AVFContext, video_device_index), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, AV_OPT_FLAG_DECODING_PARAM },
{ "audio_device_index", "select audio device by index for devices with same name (starts at 0)", offsetof(AVFContext, audio_device_index), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, AV_OPT_FLAG_DECODING_PARAM },
--
2.32.0 (Apple Git-132)
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 11+ messages in thread
* [FFmpeg-devel] [PATCH 5/6] libavdevice/avfoundation.m: Replace mutex-based concurrency handling in avfoundation.m by a thread-safe fifo queue with maximum length
2022-03-22 13:39 [FFmpeg-devel] [PATCH 0/6] Various libavdevice cleanup & enhancements toots
` (3 preceding siblings ...)
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 4/6] libavdevice/avfoundation.m: use setAudioSettings, extend supported formats toots
@ 2022-03-22 13:39 ` toots
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 6/6] Add AudioToolbox audio input device toots
2022-03-22 14:12 ` [FFmpeg-devel] [PATCH 0/6] Various libavdevice cleanup & enhancements Roger Pack
6 siblings, 0 replies; 11+ messages in thread
From: toots @ 2022-03-22 13:39 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Romain Beauxis
From: Romain Beauxis <toots@rastageeks.org>
This issue was particularly noticeable when working with audio input.
---
libavdevice/avfoundation.m | 232 ++++++++++++++++---------------------
1 file changed, 100 insertions(+), 132 deletions(-)
diff --git a/libavdevice/avfoundation.m b/libavdevice/avfoundation.m
index 055e8f62e4..1825b9787a 100644
--- a/libavdevice/avfoundation.m
+++ b/libavdevice/avfoundation.m
@@ -26,8 +26,8 @@
*/
#import <AVFoundation/AVFoundation.h>
-#include <pthread.h>
#include <Availability.h>
+#import <CoreMedia/CoreMedia.h>
#include "libavutil/channel_layout.h"
#include "libavutil/pixdesc.h"
@@ -42,6 +42,13 @@
#define CLEANUP_DEVICE_ID(s) [[s stringByReplacingOccurrencesOfString:@":" withString:@"."] UTF8String]
+static void av_log_avfoundation(void *s, int lvl, const char *str, OSStatus err) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ av_log(s, lvl, "AVFoundation: %s, %s\n", str,
+ [[[NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil] localizedDescription] UTF8String]);
+ [pool release];
+}
+
static const int avf_time_base = 1000000;
static const AVRational avf_time_base_q = {
@@ -87,9 +94,6 @@
{
AVClass* class;
- int frames_captured;
- int audio_frames_captured;
- pthread_mutex_t frame_lock;
id avf_delegate;
id avf_audio_delegate;
@@ -125,8 +129,9 @@
AVCaptureSession *capture_session;
AVCaptureVideoDataOutput *video_output;
AVCaptureAudioDataOutput *audio_output;
- CMSampleBufferRef current_frame;
- CMSampleBufferRef current_audio_frame;
+
+ CMSimpleQueueRef frames_queue;
+ int max_frames;
AVCaptureDevice *observed_device;
#if !TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
@@ -135,16 +140,6 @@
int observed_quit;
} AVFContext;
-static void lock_frames(AVFContext* ctx)
-{
- pthread_mutex_lock(&ctx->frame_lock);
-}
-
-static void unlock_frames(AVFContext* ctx)
-{
- pthread_mutex_unlock(&ctx->frame_lock);
-}
-
/** FrameReciever class - delegate for AVCaptureSession
*/
@interface AVFFrameReceiver : NSObject
@@ -222,17 +217,13 @@ - (void) captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)videoFrame
fromConnection:(AVCaptureConnection *)connection
{
- lock_frames(_context);
+ OSStatus ret = CMSimpleQueueEnqueue(_context->frames_queue, videoFrame);
- if (_context->current_frame != nil) {
- CFRelease(_context->current_frame);
+ if (ret != noErr) {
+ av_log_avfoundation(_context, AV_LOG_DEBUG, "Error while queueing video frame", ret);
}
- _context->current_frame = (CMSampleBufferRef)CFRetain(videoFrame);
-
- unlock_frames(_context);
-
- ++_context->frames_captured;
+ CFRetain(videoFrame);
}
@end
@@ -266,17 +257,13 @@ - (void) captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)audioFrame
fromConnection:(AVCaptureConnection *)connection
{
- lock_frames(_context);
+ OSStatus ret = CMSimpleQueueEnqueue(_context->frames_queue, audioFrame);
- if (_context->current_audio_frame != nil) {
- CFRelease(_context->current_audio_frame);
+ if (ret != noErr) {
+ av_log_avfoundation(_context, AV_LOG_DEBUG, "Error while queueing audio frame", ret);
}
- _context->current_audio_frame = (CMSampleBufferRef)CFRetain(audioFrame);
-
- unlock_frames(_context);
-
- ++_context->audio_frames_captured;
+ CFRetain(audioFrame);
}
@end
@@ -291,6 +278,19 @@ static void destroy_context(AVFContext* ctx)
[ctx->avf_delegate release];
[ctx->avf_audio_delegate release];
+ CMSampleBufferRef frame;
+
+ if (ctx->frames_queue) {
+ frame = (CMSampleBufferRef)CMSimpleQueueDequeue(ctx->frames_queue);
+ while (frame) {
+ CFRelease(frame);
+ frame = (CMSampleBufferRef)CMSimpleQueueDequeue(ctx->frames_queue);
+ }
+
+ CFRelease(ctx->frames_queue);
+ ctx->frames_queue = NULL;
+ }
+
ctx->capture_session = NULL;
ctx->video_output = NULL;
ctx->audio_output = NULL;
@@ -298,12 +298,6 @@ static void destroy_context(AVFContext* ctx)
ctx->avf_audio_delegate = NULL;
av_freep(&ctx->url);
-
- pthread_mutex_destroy(&ctx->frame_lock);
-
- if (ctx->current_frame) {
- CFRelease(ctx->current_frame);
- }
}
static int parse_device_name(AVFormatContext *s)
@@ -343,15 +337,14 @@ static int configure_video_device(AVFormatContext *s, AVCaptureDevice *video_dev
NSObject *format = nil;
NSObject *selected_range = nil;
NSObject *selected_format = nil;
+ CMFormatDescriptionRef formatDescription;
+ CMVideoDimensions dimensions;
// try to configure format by formats list
// might raise an exception if no format list is given
// (then fallback to default, no configuration)
@try {
for (format in [video_device valueForKey:@"formats"]) {
- CMFormatDescriptionRef formatDescription;
- CMVideoDimensions dimensions;
-
formatDescription = (CMFormatDescriptionRef) [format performSelector:@selector(formatDescription)];
dimensions = CMVideoFormatDescriptionGetDimensions(formatDescription);
@@ -378,6 +371,9 @@ static int configure_video_device(AVFormatContext *s, AVCaptureDevice *video_dev
goto unsupported_format;
}
+ ctx->width = dimensions.width;
+ ctx->height = dimensions.height;
+
if (!selected_range) {
av_log(s, AV_LOG_ERROR, "Selected framerate (%f) is not supported by the device.\n",
framerate);
@@ -625,47 +621,21 @@ static int add_audio_device(AVFormatContext *s, AVCaptureDevice *audio_device)
static int get_video_config(AVFormatContext *s)
{
AVFContext *ctx = (AVFContext*)s->priv_data;
- CVImageBufferRef image_buffer;
- CMBlockBufferRef block_buffer;
- CGSize image_buffer_size;
AVStream* stream = avformat_new_stream(s, NULL);
if (!stream) {
return 1;
}
- // Take stream info from the first frame.
- while (ctx->frames_captured < 1) {
- CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, YES);
- }
-
- lock_frames(ctx);
-
ctx->video_stream_index = stream->index;
avpriv_set_pts_info(stream, 64, 1, avf_time_base);
- image_buffer = CMSampleBufferGetImageBuffer(ctx->current_frame);
- block_buffer = CMSampleBufferGetDataBuffer(ctx->current_frame);
-
- if (image_buffer) {
- image_buffer_size = CVImageBufferGetEncodedSize(image_buffer);
-
- stream->codecpar->codec_id = AV_CODEC_ID_RAWVIDEO;
- stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
- stream->codecpar->width = (int)image_buffer_size.width;
- stream->codecpar->height = (int)image_buffer_size.height;
- stream->codecpar->format = ctx->pixel_format;
- } else {
- stream->codecpar->codec_id = AV_CODEC_ID_DVVIDEO;
- stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
- stream->codecpar->format = ctx->pixel_format;
- }
-
- CFRelease(ctx->current_frame);
- ctx->current_frame = nil;
-
- unlock_frames(ctx);
+ stream->codecpar->codec_id = AV_CODEC_ID_RAWVIDEO;
+ stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
+ stream->codecpar->width = ctx->width;
+ stream->codecpar->height = ctx->height;
+ stream->codecpar->format = ctx->pixel_format;
return 0;
}
@@ -698,7 +668,6 @@ static int get_audio_config(AVFormatContext *s)
break;
default:
av_log(ctx, AV_LOG_ERROR, "Error: invalid sample format!\n");
- unlock_frames(ctx);
return AVERROR(EINVAL);
}
@@ -713,10 +682,8 @@ static int get_audio_config(AVFormatContext *s)
}];
stream = avformat_new_stream(s, NULL);
- if (!stream) {
- unlock_frames(ctx);
+ if (!stream)
return -1;
- }
avpriv_set_pts_info(stream, 64, 1, avf_time_base);
@@ -727,7 +694,6 @@ static int get_audio_config(AVFormatContext *s)
ctx->audio_stream_index = stream->index;
- unlock_frames(ctx);
return 0;
}
@@ -771,8 +737,6 @@ static int avf_read_header(AVFormatContext *s)
ctx->num_video_devices = [devices count] + [devices_muxed count];
- pthread_mutex_init(&ctx->frame_lock, NULL);
-
#if !TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
CGGetActiveDisplayList(0, NULL, &num_screens);
#endif
@@ -1027,6 +991,14 @@ static int avf_read_header(AVFormatContext *s)
// Initialize capture session
ctx->capture_session = [[AVCaptureSession alloc] init];
+ OSStatus err;
+ err = CMSimpleQueueCreate(kCFAllocatorDefault, ctx->max_frames, &ctx->frames_queue);
+
+ if (err != noErr) {
+ av_log_avfoundation(s, AV_LOG_ERROR, "error while creating frame queue", err);
+ goto fail;
+ }
+
if (video_device && add_video_device(s, video_device)) {
goto fail;
}
@@ -1059,7 +1031,7 @@ static int avf_read_header(AVFormatContext *s)
destroy_context(ctx);
if (ret)
return ret;
- return AVERROR(EIO);
+ return AVERROR_EXTERNAL;
}
static int copy_cvpixelbuffer(AVFormatContext *s,
@@ -1108,39 +1080,46 @@ static int copy_cvpixelbuffer(AVFormatContext *s,
static int avf_read_packet(AVFormatContext *s, AVPacket *pkt)
{
OSStatus ret;
+ int status, length;
+ CMBlockBufferRef block_buffer;
+ CMSampleBufferRef frame;
+ CMFormatDescriptionRef format;
AVFContext* ctx = (AVFContext*)s->priv_data;
+ CMItemCount count;
+ CMSampleTimingInfo timing_info;
+ CVImageBufferRef image_buffer;
+ size_t buffer_size;
+ AVRational timebase_q;
- do {
- CVImageBufferRef image_buffer;
- CMBlockBufferRef block_buffer;
- lock_frames(ctx);
+ if (CMSimpleQueueGetCount(ctx->frames_queue) < 1)
+ return AVERROR(EAGAIN);
- if (ctx->current_frame != nil) {
- int status;
- int length = 0;
+ frame = (CMSampleBufferRef)CMSimpleQueueDequeue(ctx->frames_queue);
+ format = CMSampleBufferGetFormatDescription(frame);
- image_buffer = CMSampleBufferGetImageBuffer(ctx->current_frame);
- block_buffer = CMSampleBufferGetDataBuffer(ctx->current_frame);
+ switch (CMFormatDescriptionGetMediaType(format)) {
+ case kCMMediaType_Video:
+ length = 0;
+ image_buffer = CMSampleBufferGetImageBuffer(frame);
+ block_buffer = CMSampleBufferGetDataBuffer(frame);
if (image_buffer != nil) {
length = (int)CVPixelBufferGetDataSize(image_buffer);
} else if (block_buffer != nil) {
length = (int)CMBlockBufferGetDataLength(block_buffer);
} else {
- unlock_frames(ctx);
+ CFRelease(frame);
return AVERROR(EINVAL);
}
- if (av_new_packet(pkt, length) < 0) {
- unlock_frames(ctx);
- return AVERROR(EIO);
+ status = av_new_packet(pkt, length);
+ if (status < 0) {
+ CFRelease(frame);
+ return status;
}
- CMItemCount count;
- CMSampleTimingInfo timing_info;
-
- if (CMSampleBufferGetOutputSampleTimingInfoArray(ctx->current_frame, 1, &timing_info, &count) == noErr) {
- AVRational timebase_q = av_make_q(1, timing_info.presentationTimeStamp.timescale);
+ if (CMSampleBufferGetOutputSampleTimingInfoArray(frame, 1, &timing_info, &count) == noErr) {
+ timebase_q = av_make_q(1, timing_info.presentationTimeStamp.timescale);
pkt->pts = pkt->dts = av_rescale_q(timing_info.presentationTimeStamp.value, timebase_q, avf_time_base_q);
}
@@ -1153,62 +1132,50 @@ static int avf_read_packet(AVFormatContext *s, AVPacket *pkt)
status = 0;
ret = CMBlockBufferCopyDataBytes(block_buffer, 0, pkt->size, pkt->data);
if (ret != kCMBlockBufferNoErr) {
- status = AVERROR(EIO);
+ av_log_avfoundation(s, AV_LOG_ERROR, "error while copying buffer data", ret);
+ status = AVERROR_EXTERNAL;
}
- }
- CFRelease(ctx->current_frame);
- ctx->current_frame = nil;
-
- if (status < 0) {
- unlock_frames(ctx);
- return status;
}
- } else if (ctx->current_audio_frame != nil) {
- CMBlockBufferRef block_buffer = CMSampleBufferGetDataBuffer(ctx->current_audio_frame);
+ CFRelease(frame);
+
+ return status;
- size_t buffer_size = CMBlockBufferGetDataLength(block_buffer);
+ case kCMMediaType_Audio:
+ block_buffer = CMSampleBufferGetDataBuffer(frame);
+ buffer_size = CMBlockBufferGetDataLength(block_buffer);
- int status = av_new_packet(pkt, buffer_size);
+ status = av_new_packet(pkt, buffer_size);
if (status < 0) {
- unlock_frames(ctx);
+ CFRelease(frame);
return status;
}
ret = CMBlockBufferCopyDataBytes(block_buffer, 0, pkt->size, pkt->data);
if (ret != kCMBlockBufferNoErr) {
- unlock_frames(ctx);
- return AVERROR(EIO);
+ CFRelease(frame);
+ av_log_avfoundation(s, AV_LOG_ERROR, "error while copying audio data", ret);
+ return AVERROR_EXTERNAL;
}
- CMItemCount count;
- CMSampleTimingInfo timing_info;
-
- if (CMSampleBufferGetOutputSampleTimingInfoArray(ctx->current_audio_frame, 1, &timing_info, &count) == noErr) {
- AVRational timebase_q = av_make_q(1, timing_info.presentationTimeStamp.timescale);
+ if (CMSampleBufferGetOutputSampleTimingInfoArray(frame, 1, &timing_info, &count) == noErr) {
+ timebase_q = av_make_q(1, timing_info.presentationTimeStamp.timescale);
pkt->pts = pkt->dts = av_rescale_q(timing_info.presentationTimeStamp.value, timebase_q, avf_time_base_q);
}
pkt->stream_index = ctx->audio_stream_index;
pkt->flags |= AV_PKT_FLAG_KEY;
- CFRelease(ctx->current_audio_frame);
- ctx->current_audio_frame = nil;
-
- unlock_frames(ctx);
- } else {
+ CFRelease(frame);
+ return 0;
+ default:
pkt->data = NULL;
- unlock_frames(ctx);
- if (ctx->observed_quit) {
+ if (ctx->observed_quit)
return AVERROR_EOF;
- } else {
- return AVERROR(EAGAIN);
- }
- }
- unlock_frames(ctx);
- } while (!pkt->data);
+ return AVERROR(EAGAIN);
+ }
- return 0;
+ return AVERROR_BUG;
}
static int avf_close(AVFormatContext *s)
@@ -1233,6 +1200,7 @@ static int avf_close(AVFormatContext *s)
{ "capture_mouse_clicks", "capture the screen mouse clicks", offsetof(AVFContext, capture_mouse_clicks), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, AV_OPT_FLAG_DECODING_PARAM },
{ "capture_raw_data", "capture the raw data from device connection", offsetof(AVFContext, capture_raw_data), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, AV_OPT_FLAG_DECODING_PARAM },
{ "drop_late_frames", "drop frames that are available later than expected", offsetof(AVFContext, drop_late_frames), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1, AV_OPT_FLAG_DECODING_PARAM },
+ { "max_frames", "Maximun length of the queue of pending frames", offsetof(AVFContext, max_frames), AV_OPT_TYPE_INT, {.i64=10}, 0, INT_MAX, AV_OPT_FLAG_DECODING_PARAM },
{ NULL },
};
--
2.32.0 (Apple Git-132)
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 11+ messages in thread
* [FFmpeg-devel] [PATCH 6/6] Add AudioToolbox audio input device.
2022-03-22 13:39 [FFmpeg-devel] [PATCH 0/6] Various libavdevice cleanup & enhancements toots
` (4 preceding siblings ...)
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 5/6] libavdevice/avfoundation.m: Replace mutex-based concurrency handling in avfoundation.m by a thread-safe fifo queue with maximum length toots
@ 2022-03-22 13:39 ` toots
2022-03-22 14:12 ` [FFmpeg-devel] [PATCH 0/6] Various libavdevice cleanup & enhancements Roger Pack
6 siblings, 0 replies; 11+ messages in thread
From: toots @ 2022-03-22 13:39 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Romain Beauxis
From: Romain Beauxis <toots@rastageeks.org>
diff --git a/configure b/configure
index a7953ffc16..37f9f7b80a 100755
--- a/configure
+++ b/configure
@@ -203,6 +203,7 @@ External library support:
--disable-avfoundation disable Apple AVFoundation framework [autodetect]
--enable-avisynth enable reading of AviSynth script files [no]
--disable-bzlib disable bzlib [autodetect]
+ --disable-coremedia disable Apple CoreMedia framework [autodetect]
--disable-coreimage disable Apple CoreImage framework [autodetect]
--enable-chromaprint enable audio fingerprinting with chromaprint [no]
--enable-frei0r enable frei0r video filtering [no]
@@ -1751,6 +1752,7 @@ EXTERNAL_AUTODETECT_LIBRARY_LIST="
appkit
avfoundation
bzlib
+ coremedia
coreimage
iconv
libxcb
@@ -3488,6 +3490,8 @@ alsa_outdev_deps="alsa"
avfoundation_indev_deps="avfoundation corevideo coremedia pthreads"
avfoundation_indev_suggest="coregraphics applicationservices"
avfoundation_indev_extralibs="-framework Foundation"
+audiotoolbox_indev_deps="coremedia audiotoolbox"
+audiotoolbox_indev_extralibs="-framework CoreMedia -framework AudioToolbox"
audiotoolbox_outdev_deps="audiotoolbox pthreads"
audiotoolbox_outdev_extralibs="-framework AudioToolbox -framework CoreAudio"
bktr_indev_deps_any="dev_bktr_ioctl_bt848_h machine_ioctl_bt848_h dev_video_bktr_ioctl_bt848_h dev_ic_bt8xx_h"
@@ -6330,6 +6334,7 @@ check_lib camera2ndk "stdbool.h stdint.h camera/NdkCameraManager.h" ACameraManag
enabled appkit && check_apple_framework AppKit
enabled audiotoolbox && check_apple_framework AudioToolbox
enabled avfoundation && check_apple_framework AVFoundation
+enabled coremedia && check_apple_framework CoreMedia
enabled coreimage && check_apple_framework CoreImage
enabled metal && check_apple_framework Metal
enabled videotoolbox && check_apple_framework VideoToolbox
diff --git a/doc/indevs.texi b/doc/indevs.texi
index 858c0fa4e4..8d57a26a5f 100644
--- a/doc/indevs.texi
+++ b/doc/indevs.texi
@@ -103,6 +103,41 @@ Set the maximum number of frames to buffer. Default is 5.
@end table
+@section AudioToolbox
+
+AudioToolbox input device.
+
+Allows native input from CoreAudio devices on OSX.
+
+@subsection Options
+
+AudioToolbox supports the following options:
+
+@table @option
+
+@item channels
+Set the number of channels. Default is device's default.
+
+@item frames_queue_length
+Maximum of buffers in the input queue
+
+@item buffer_frame_size
+Buffer frame size, gouverning internal latency
+
+@item big_endian
+Return big endian samples
+
+@item sample_format
+Sample format
+
+@end table
+
+@subsection Examples
+
+@itemize
+
+@end itemize
+
@section avfoundation
AVFoundation input device.
diff --git a/libavdevice/Makefile b/libavdevice/Makefile
index 99fea7133a..78d4168521 100644
--- a/libavdevice/Makefile
+++ b/libavdevice/Makefile
@@ -15,6 +15,7 @@ OBJS-$(HAVE_LIBC_MSVCRT) += file_open.o
OBJS-$(CONFIG_ALSA_INDEV) += alsa_dec.o alsa.o timefilter.o
OBJS-$(CONFIG_ALSA_OUTDEV) += alsa_enc.o alsa.o
OBJS-$(CONFIG_ANDROID_CAMERA_INDEV) += android_camera.o
+OBJS-$(CONFIG_AUDIOTOOLBOX_INDEV) += audiotoolbox_dec.o
OBJS-$(CONFIG_AUDIOTOOLBOX_OUTDEV) += audiotoolbox.o
OBJS-$(CONFIG_AVFOUNDATION_INDEV) += avfoundation.o
OBJS-$(CONFIG_BKTR_INDEV) += bktr.o
diff --git a/libavdevice/alldevices.c b/libavdevice/alldevices.c
index 22323a0a44..fbecdbb0b2 100644
--- a/libavdevice/alldevices.c
+++ b/libavdevice/alldevices.c
@@ -26,6 +26,7 @@ extern const AVInputFormat ff_alsa_demuxer;
extern const AVOutputFormat ff_alsa_muxer;
extern const AVInputFormat ff_android_camera_demuxer;
extern const AVOutputFormat ff_audiotoolbox_muxer;
+extern const AVInputFormat ff_audiotoolbox_demuxer;
extern const AVInputFormat ff_avfoundation_demuxer;
extern const AVInputFormat ff_bktr_demuxer;
extern const AVOutputFormat ff_caca_muxer;
diff --git a/libavdevice/audiotoolbox.m b/libavdevice/audiotoolbox.m
index 0cb97b5e46..3216f14607 100644
--- a/libavdevice/audiotoolbox.m
+++ b/libavdevice/audiotoolbox.m
@@ -84,7 +84,7 @@ static av_cold int at_write_header(AVFormatContext *avctx)
AudioObjectPropertyAddress prop;
prop.mSelector = kAudioHardwarePropertyDevices;
prop.mScope = kAudioObjectPropertyScopeGlobal;
- prop.mElement = kAudioObjectPropertyElementMaster;
+ prop.mElement = 0;
err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &prop, 0, NULL, &data_size);
if (check_status(avctx, &err, "AudioObjectGetPropertyDataSize devices"))
return AVERROR(EINVAL);
@@ -173,7 +173,7 @@ static av_cold int at_write_header(AVFormatContext *avctx)
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->channels;
+ 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;
@@ -193,9 +193,9 @@ static av_cold int at_write_header(AVFormatContext *avctx)
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->channels);
+ 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->channels);
+ 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);
diff --git a/libavdevice/audiotoolbox_dec.m b/libavdevice/audiotoolbox_dec.m
new file mode 100644
index 0000000000..ae80c0e54a
--- /dev/null
+++ b/libavdevice/audiotoolbox_dec.m
@@ -0,0 +1,530 @@
+/*
+ * AudioToolbox input device
+ * Copyright (c) 2022 Romain Beauxis <toots@rastageeks.org>
+ *
+ * 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 input device
+ * @author Romain Beauxis <toots@rastageeks.org>
+ */
+
+#import <AudioToolbox/AudioToolbox.h>
+#import <CoreMedia/CoreMedia.h>
+
+#include "libavutil/channel_layout.h"
+#include "libavformat/internal.h"
+#include "avdevice.h"
+
+typedef struct {
+ void *data;
+ int size;
+} buffer_t;
+
+typedef struct {
+ AVClass *class;
+ CMSimpleQueueRef frames_queue;
+ AudioUnit audio_unit;
+ AudioStreamBasicDescription record_format;
+ uint64_t position;
+ int frames_queue_length;
+ int buffer_frame_size;
+ int stream_index;
+ int big_endian;
+ enum AVSampleFormat sample_format;
+ int channels;
+} ATContext;
+
+static int check_status(void *ctx, OSStatus status, const char *msg) {
+ if (status == noErr) {
+ av_log(ctx, AV_LOG_DEBUG, "OK: %s\n", msg);
+ return 0;
+ }
+
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
+ av_log(ctx, AV_LOG_ERROR, "Error: %s (%s)\n", msg, [[error localizedDescription] UTF8String]);
+ [pool release];
+ return 1;
+}
+
+static OSStatus input_callback(void *priv,
+ AudioUnitRenderActionFlags *ioActionFlags,
+ const AudioTimeStamp *inTimeStamp,
+ UInt32 inBusNumber,
+ UInt32 inNumberFrames,
+ AudioBufferList *ioData) {
+ ATContext *ctx = (ATContext *)priv;
+ OSStatus err;
+
+ AudioBuffer audio_buffer;
+
+ audio_buffer.mNumberChannels = ctx->channels;
+ audio_buffer.mDataByteSize = inNumberFrames * ctx->record_format.mBytesPerFrame;
+
+ audio_buffer.mData = av_malloc(audio_buffer.mDataByteSize);
+ memset(audio_buffer.mData, 0, audio_buffer.mDataByteSize);
+
+ AudioBufferList bufferList;
+ bufferList.mNumberBuffers = 1;
+ bufferList.mBuffers[0] = audio_buffer;
+
+ err = AudioUnitRender(ctx->audio_unit,
+ ioActionFlags,
+ inTimeStamp,
+ inBusNumber,
+ inNumberFrames,
+ &bufferList);
+ if (check_status(ctx, err, "AudioUnitRender")) {
+ av_freep(&audio_buffer.mData);
+ return err;
+ }
+
+ buffer_t *buffer = av_malloc(sizeof(buffer_t));
+ buffer->data = audio_buffer.mData;
+ buffer->size = audio_buffer.mDataByteSize;
+ err = CMSimpleQueueEnqueue(ctx->frames_queue, buffer);
+
+ if (err != noErr) {
+ av_log(ctx, AV_LOG_DEBUG, "Could not enqueue audio frame!\n");
+ return err;
+ }
+
+ return noErr;
+}
+
+static av_cold int audiotoolbox_read_header(AVFormatContext *avctx) {
+ ATContext *ctx = (ATContext*)avctx->priv_data;
+ OSStatus err = noErr;
+ AudioChannelLayout *channel_layout = NULL;
+ AudioDeviceID device = kAudioObjectUnknown;
+ AudioObjectPropertyAddress prop;
+ UInt32 data_size;
+
+ enum AVCodecID codec_id = av_get_pcm_codec(ctx->sample_format, ctx->big_endian);
+
+ if (codec_id == AV_CODEC_ID_NONE) {
+ av_log(ctx, AV_LOG_ERROR, "Error: invalid sample format!\n");
+ return AVERROR(EINVAL);
+ }
+
+ prop.mScope = kAudioObjectPropertyScopeGlobal;
+ prop.mElement = 0;
+
+ if (!strcmp(avctx->url, "default")) {
+ prop.mSelector = kAudioHardwarePropertyDefaultInputDevice;
+ data_size = sizeof(AudioDeviceID);
+ err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, NULL, &data_size, &device);
+ if (check_status(avctx, err, "AudioObjectGetPropertyData DefaultInputDevice"))
+ goto fail;
+ } else {
+ prop.mSelector = kAudioHardwarePropertyDeviceForUID;
+ CFStringRef deviceUID = CFStringCreateWithCStringNoCopy(NULL, avctx->url, kCFStringEncodingUTF8, kCFAllocatorNull);
+ AudioValueTranslation value;
+ value.mInputData = &deviceUID;
+ value.mInputDataSize = sizeof(deviceUID);;
+ value.mOutputData = &device;
+ value.mOutputDataSize = sizeof(device);
+ data_size = sizeof(value);
+
+ err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, NULL, &data_size, &value);
+ CFRelease(deviceUID);
+
+ if (check_status(avctx, err, "AudioObjectGetPropertyData DeviceForUID"))
+ goto fail;
+ }
+
+ Float64 sample_rate;
+ prop.mSelector = kAudioDevicePropertyNominalSampleRate;
+ prop.mScope = kAudioObjectPropertyScopeInput;
+ data_size = sizeof(sample_rate);
+ err = AudioObjectGetPropertyData(device, &prop, 0, NULL, &data_size, &sample_rate);
+ if (check_status(avctx, err, "AudioObjectGetPropertyData SampleRate"))
+ goto fail;
+
+ if (!ctx->channels) {
+ prop.mSelector = kAudioDevicePropertyPreferredChannelLayout;
+ prop.mScope = kAudioObjectPropertyScopeInput;
+ UInt32 channel_layout_size;
+
+ err = AudioObjectGetPropertyDataSize(device, &prop, 0, NULL, &channel_layout_size);
+ if (check_status(avctx, err, "AudioObjectGetPropertyDataSize PreferredChannelLayout"))
+ goto fail;
+
+ channel_layout = av_malloc(channel_layout_size);
+ err = AudioObjectGetPropertyData(device, &prop, 0, NULL, &channel_layout_size, channel_layout);
+ if (check_status(avctx, err, "AudioObjectGetPropertyData PreferredChannelLayout"))
+ goto fail;
+
+ data_size = sizeof(ctx->channels);
+ err = AudioFormatGetProperty(kAudioFormatProperty_NumberOfChannelsForLayout, channel_layout_size, channel_layout, &data_size, &ctx->channels);
+ if (check_status(avctx, err, "AudioFormatGetProperty NumberOfChannelsForLayout"))
+ goto fail;
+ }
+
+ ctx->record_format.mFormatID = kAudioFormatLinearPCM;
+ ctx->record_format.mChannelsPerFrame = ctx->channels;
+ ctx->record_format.mFormatFlags = kAudioFormatFlagIsPacked;
+ ctx->record_format.mBitsPerChannel = av_get_bytes_per_sample(ctx->sample_format) << 3;
+
+ if (ctx->big_endian)
+ ctx->record_format.mFormatFlags |= kAudioFormatFlagIsBigEndian;
+
+ switch (ctx->sample_format) {
+ case AV_SAMPLE_FMT_S16:
+ case AV_SAMPLE_FMT_S32:
+ ctx->record_format.mFormatFlags |= kAudioFormatFlagIsSignedInteger;
+ break;
+ case AV_SAMPLE_FMT_FLT:
+ ctx->record_format.mFormatFlags |= kAudioFormatFlagIsFloat;
+ break;
+ default:
+ av_log(ctx, AV_LOG_ERROR, "Error: invalid sample format!\n");
+ goto fail;
+ }
+
+ av_log(ctx, AV_LOG_DEBUG, "Audio Input: %s\n", avctx->url);
+ av_log(ctx, AV_LOG_DEBUG, "samplerate: %d\n", (int)sample_rate);
+ av_log(ctx, AV_LOG_DEBUG, "channels: %d\n", ctx->channels);
+ av_log(ctx, AV_LOG_DEBUG, "Input format: %s\n", avcodec_get_name(codec_id));
+
+ data_size = sizeof(ctx->record_format);
+ err = AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &data_size, &ctx->record_format);
+ if (check_status(avctx, err, "AudioFormatGetProperty FormatInfo"))
+ goto fail;
+
+ AudioComponentDescription desc;
+ AudioComponent comp;
+
+ desc.componentType = kAudioUnitType_Output;
+ desc.componentSubType = kAudioUnitSubType_HALOutput;
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+
+ comp = AudioComponentFindNext(NULL, &desc);
+ if (comp == NULL) {
+ av_log(ctx, AV_LOG_ERROR, "Error: AudioComponentFindNext\n");
+ goto fail;
+ }
+
+ err = AudioComponentInstanceNew(comp, &ctx->audio_unit);
+ if (check_status(avctx, err, "AudioComponentInstanceNew"))
+ goto fail;
+
+ UInt32 enableIO = 1;
+ err = AudioUnitSetProperty(ctx->audio_unit,
+ kAudioOutputUnitProperty_EnableIO,
+ kAudioUnitScope_Input,
+ 1,
+ &enableIO,
+ sizeof(enableIO));
+ if (check_status(avctx, err, "AudioUnitSetProperty EnableIO"))
+ goto fail;
+
+ enableIO = 0;
+ err = AudioUnitSetProperty(ctx->audio_unit,
+ kAudioOutputUnitProperty_EnableIO,
+ kAudioUnitScope_Output,
+ 0,
+ &enableIO,
+ sizeof(enableIO));
+ if (check_status(avctx, err, "AudioUnitSetProperty EnableIO"))
+ goto fail;
+
+ err = AudioUnitSetProperty(ctx->audio_unit,
+ kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global,
+ 0,
+ &device,
+ sizeof(device));
+ if (check_status(avctx, err, "AudioUnitSetProperty CurrentDevice"))
+ goto fail;
+
+ err = AudioUnitSetProperty(ctx->audio_unit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input,
+ 0,
+ &ctx->record_format,
+ sizeof(ctx->record_format));
+ if (check_status(avctx, err, "AudioUnitSetProperty StreamFormat"))
+ goto fail;
+
+ err = AudioUnitSetProperty(ctx->audio_unit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Output,
+ 1,
+ &ctx->record_format,
+ sizeof(ctx->record_format));
+ if (check_status(avctx, err, "AudioUnitSetProperty StreamFormat"))
+ goto fail;
+
+ err = AudioUnitSetProperty(ctx->audio_unit,
+ kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Global,
+ 0,
+ &ctx->buffer_frame_size,
+ sizeof(ctx->buffer_frame_size));
+ if (check_status(avctx, err, "AudioUnitSetProperty BufferFrameSize"))
+ goto fail;
+
+ AURenderCallbackStruct callback = {0};
+ callback.inputProc = input_callback;
+ callback.inputProcRefCon = ctx;
+ err = AudioUnitSetProperty(ctx->audio_unit,
+ kAudioOutputUnitProperty_SetInputCallback,
+ kAudioUnitScope_Global,
+ 0,
+ &callback,
+ sizeof(callback));
+ if (check_status(avctx, err, "AudioUnitSetProperty SetInputCallback"))
+ goto fail;
+
+ err = AudioUnitInitialize(ctx->audio_unit);
+ if (check_status(avctx, err, "AudioUnitInitialize"))
+ goto fail;
+
+ err = CMSimpleQueueCreate(kCFAllocatorDefault, ctx->frames_queue_length, &ctx->frames_queue);
+ if (check_status(avctx, err, "CMSimpleQueueCreate"))
+ goto fail;
+
+ CFRetain(ctx->frames_queue);
+
+ err = AudioUnitInitialize(ctx->audio_unit);
+ if (check_status(avctx, err, "AudioUnitInitialize"))
+ goto fail;
+
+ err = AudioOutputUnitStart(ctx->audio_unit);
+ if (check_status(avctx, err, "AudioOutputUnitStart"))
+ goto fail;
+
+ AVStream* stream = avformat_new_stream(avctx, NULL);
+ stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
+ stream->codecpar->sample_rate = sample_rate;
+ stream->codecpar->channels = ctx->channels;
+ stream->codecpar->channel_layout = av_get_default_channel_layout(stream->codecpar->channels);
+ stream->codecpar->codec_id = codec_id;
+
+ avpriv_set_pts_info(stream, 64, 1, sample_rate);
+
+ ctx->stream_index = stream->index;
+ ctx->position = 0;
+
+ av_freep(&channel_layout);
+ return 0;
+
+fail:
+ av_freep(&channel_layout);
+ return AVERROR(EINVAL);
+}
+
+static int audiotoolbox_read_packet(AVFormatContext *avctx, AVPacket *pkt) {
+ ATContext *ctx = (ATContext*)avctx->priv_data;
+
+ if (CMSimpleQueueGetCount(ctx->frames_queue) < 1)
+ return AVERROR(EAGAIN);
+
+ buffer_t *buffer = (buffer_t *)CMSimpleQueueDequeue(ctx->frames_queue);
+
+ int status = av_packet_from_data(pkt, buffer->data, buffer->size);
+ if (status < 0) {
+ av_freep(&buffer->data);
+ av_freep(&buffer);
+ return status;
+ }
+
+ pkt->stream_index = ctx->stream_index;
+ pkt->flags |= AV_PKT_FLAG_KEY;
+ pkt->pts = pkt->dts = ctx->position;
+
+ ctx->position += pkt->size / (ctx->channels * av_get_bytes_per_sample(ctx->sample_format));
+
+ av_freep(&buffer);
+ return 0;
+}
+
+static av_cold int audiotoolbox_close(AVFormatContext *avctx) {
+ ATContext *ctx = (ATContext*)avctx->priv_data;
+
+ if (ctx->audio_unit) {
+ AudioOutputUnitStop(ctx->audio_unit);
+ AudioComponentInstanceDispose(ctx->audio_unit);
+ ctx->audio_unit = NULL;
+ }
+
+ if (ctx->frames_queue) {
+ buffer_t *buffer = (buffer_t *)CMSimpleQueueDequeue(ctx->frames_queue);
+
+ while (buffer) {
+ av_freep(&buffer->data);
+ av_freep(&buffer);
+ buffer = (buffer_t *)CMSimpleQueueDequeue(ctx->frames_queue);
+ }
+
+ CFRelease(ctx->frames_queue);
+ ctx->frames_queue = NULL;
+ }
+
+ return 0;
+}
+
+static int audiotoolbox_get_device_list(AVFormatContext *avctx, AVDeviceInfoList *device_list)
+{
+ OSStatus err = noErr;
+ CFStringRef device_UID = NULL;
+ CFStringRef device_name = NULL;
+ AudioDeviceID *devices = NULL;
+ AVDeviceInfo *avdevice_info = NULL;
+ int num_devices, ret;
+ UInt32 i;
+
+ avdevice_info = av_mallocz(sizeof(AVDeviceInfo));
+
+ if (!avdevice_info) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ avdevice_info->device_name = av_strdup("default");
+ avdevice_info->device_description = av_strdup("Default audio input device");
+ if (!avdevice_info->device_name || !avdevice_info->device_description) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+ avdevice_info->media_types = av_malloc_array(1, sizeof(enum AVMediaType));
+ if (avdevice_info->media_types) {
+ avdevice_info->nb_media_types = 1;
+ avdevice_info->media_types[0] = AVMEDIA_TYPE_AUDIO;
+ }
+
+ if ((ret = av_dynarray_add_nofree(&device_list->devices,
+ &device_list->nb_devices, avdevice_info)) < 0)
+ goto fail;
+
+ // get devices
+ UInt32 data_size = 0;
+ AudioObjectPropertyAddress prop;
+ prop.mSelector = kAudioHardwarePropertyDevices;
+ prop.mScope = kAudioObjectPropertyScopeGlobal;
+ prop.mElement = 0;
+ 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 = av_malloc(data_size);
+
+ err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, NULL, &data_size, devices);
+ if (check_status(avctx, err, "AudioObjectGetPropertyData devices")) {
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
+
+ for(i = 0; i < num_devices; ++i) {
+ prop.mSelector = kAudioDevicePropertyStreams;
+ prop.mScope = kAudioDevicePropertyScopeInput;
+ data_size = 0;
+
+ err = AudioObjectGetPropertyDataSize(devices[i], &prop, 0, NULL, &data_size);
+ if (check_status(avctx, err, "AudioObjectGetPropertyData Streams"))
+ continue;
+
+ UInt32 streamCount = data_size / sizeof(AudioStreamID);
+
+ if (streamCount <= 0)
+ continue;
+
+ // 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;
+
+ avdevice_info = av_mallocz(sizeof(AVDeviceInfo));
+ if (!avdevice_info) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+ avdevice_info->device_name = av_strdup(CFStringGetCStringPtr(device_UID, kCFStringEncodingUTF8));
+ avdevice_info->device_description = av_strdup(CFStringGetCStringPtr(device_name, kCFStringEncodingUTF8));
+ if (!avdevice_info->device_name || !avdevice_info->device_description) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+ avdevice_info->media_types = av_malloc_array(1, sizeof(enum AVMediaType));
+ if (avdevice_info->media_types) {
+ avdevice_info->nb_media_types = 1;
+ avdevice_info->media_types[0] = AVMEDIA_TYPE_AUDIO;
+ }
+
+ if ((ret = av_dynarray_add_nofree(&device_list->devices,
+ &device_list->nb_devices, avdevice_info)) < 0)
+ goto fail;
+ }
+
+ av_freep(&devices);
+ return 0;
+
+fail:
+ av_freep(&devices);
+ if (avdevice_info) {
+ av_freep(&avdevice_info->device_name);
+ av_freep(&avdevice_info->device_description);
+ av_freep(&avdevice_info);
+ }
+
+ return ret;
+}
+
+static const AVOption options[] = {
+ { "channels", "number of audio channels", offsetof(ATContext, channels), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
+ { "frames_queue_length", "maximum of buffers in the input queue", offsetof(ATContext, frames_queue_length), AV_OPT_TYPE_INT, {.i64=10}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
+ { "buffer_frame_size", "buffer frame size, gouverning internal latency", offsetof(ATContext, buffer_frame_size), AV_OPT_TYPE_INT, {.i64=1024}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
+ { "big_endian", "return big endian samples", offsetof(ATContext, big_endian), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
+ { "sample_format", "sample format", offsetof(ATContext, sample_format), AV_OPT_TYPE_INT, {.i64=AV_SAMPLE_FMT_S16}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
+ { NULL },
+};
+
+static const AVClass audiotoolbox_class = {
+ .class_name = "AudioToolbox",
+ .item_name = av_default_item_name,
+ .option = options,
+ .version = LIBAVUTIL_VERSION_INT,
+ .category = AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT,
+};
+
+const AVInputFormat ff_audiotoolbox_demuxer = {
+ .name = "audiotoolbox",
+ .long_name = NULL_IF_CONFIG_SMALL("AudioToolbox input device"),
+ .priv_data_size = sizeof(ATContext),
+ .read_header = audiotoolbox_read_header,
+ .read_packet = audiotoolbox_read_packet,
+ .read_close = audiotoolbox_close,
+ .get_device_list = audiotoolbox_get_device_list,
+ .flags = AVFMT_NOFILE,
+ .priv_class = &audiotoolbox_class,
+};
--
2.32.0 (Apple Git-132)
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [FFmpeg-devel] [PATCH 1/6] Fix dshow device name/description
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 1/6] Fix dshow device name/description toots
@ 2022-03-22 14:10 ` Roger Pack
2022-03-22 19:07 ` Diederick C. Niehorster
0 siblings, 1 reply; 11+ messages in thread
From: Roger Pack @ 2022-03-22 14:10 UTC (permalink / raw)
To: FFmpeg development discussions and patches; +Cc: Romain Beauxis
On Tue, Mar 22, 2022 at 7:40 AM <toots@rastageeks.org> wrote:
>
> From: Romain Beauxis <romain.beauxis@nextstep.com>
>
> diff --git a/libavdevice/dshow.c b/libavdevice/dshow.c
> index 6039578ff9..4ee3f6e194 100644
> --- a/libavdevice/dshow.c
> +++ b/libavdevice/dshow.c
> @@ -552,8 +552,8 @@ dshow_cycle_devices(AVFormatContext *avctx, ICreateDevEnum *devenum,
> if (!device)
> goto fail;
>
> - device->device_name = av_strdup(friendly_name);
> - device->device_description = av_strdup(unique_name);
> + device->device_name = av_strdup(unique_name);
> + device->device_description = av_strdup(friendly_name);
> if (!device->device_name || !device->device_description)
> goto fail;
>
LGTM.
The device enumeration API was added only recently to dshow, guess a
bug crept in.
Thanks!
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [FFmpeg-devel] [PATCH 0/6] Various libavdevice cleanup & enhancements
2022-03-22 13:39 [FFmpeg-devel] [PATCH 0/6] Various libavdevice cleanup & enhancements toots
` (5 preceding siblings ...)
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 6/6] Add AudioToolbox audio input device toots
@ 2022-03-22 14:12 ` Roger Pack
6 siblings, 0 replies; 11+ messages in thread
From: Roger Pack @ 2022-03-22 14:12 UTC (permalink / raw)
To: FFmpeg development discussions and patches; +Cc: Romain Beauxis
On Tue, Mar 22, 2022 at 7:40 AM <toots@rastageeks.org> wrote:
>
> From: Romain Beauxis <toots@rastageeks.org>
>
> This is a series of patches that fix and enhances libavdevice & have been submitted multiple times in the past 6 months. In the series, the most straightforward patches have been placed first in the list.
>
> While I have a lot of appreciation for all the solid work done in this project, seeing the difficulty in getting them included makes one wonder what is the status of libavdevice and if it is still actively maintained.
dshow gets used in windows land. Maintained, well... :)
>
> Typically, in the first patch, we fix the name/machine readable ID of dshow devices. If the API has any significant use or maintenance, this should have been caught and fixed much sooner.
>
> Next, the patches fixing the avfoundation device audio format and concurrency models are absolutely required to get any use of this operator as audio input and, quite likely, as video input as well.
>
> This is a lot of changes to make the library usable for a cross-platform project and, with the lack of traction with the changes, this makes the developer wonder if the library is ready for production use and if it wouldn't be easier to implement hardware handling separately and plug into FFmpeg after retreiving media data.
>
> There are more that I have noticed with the API, in particular the fact that audio/video devices are labelled as video. I would love to submit changes to enhance that aspect but, not with all these patches already pending..
>
> Thanks for y'all consideration.
> -- Romain
>
> Romain Beauxis (6):
> Fix dshow device name/description
> Use appropriate method for device discovery, fix crash with bogus
> device index.
> libavdevice/avfoundation.m: Allow to select devices by unique ID
> libavdevice/avfoundation.m: use setAudioSettings, extend supported
> formats
> libavdevice/avfoundation.m: Replace mutex-based concurrency handling
> in avfoundation.m by a thread-safe fifo queue with maximum length
> Add AudioToolbox audio input device.
>
> configure | 5 +
> doc/indevs.texi | 41 ++-
> libavdevice/Makefile | 1 +
> libavdevice/alldevices.c | 1 +
> libavdevice/audiotoolbox.m | 8 +-
> libavdevice/audiotoolbox_dec.m | 530 ++++++++++++++++++++++++++++++++
> libavdevice/avfoundation.m | 547 ++++++++++++++++-----------------
> libavdevice/dshow.c | 4 +-
> 8 files changed, 843 insertions(+), 294 deletions(-)
> create mode 100644 libavdevice/audiotoolbox_dec.m
>
> --
> 2.32.0 (Apple Git-132)
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [FFmpeg-devel] [PATCH 1/6] Fix dshow device name/description
2022-03-22 14:10 ` Roger Pack
@ 2022-03-22 19:07 ` Diederick C. Niehorster
2022-04-09 19:39 ` Marton Balint
0 siblings, 1 reply; 11+ messages in thread
From: Diederick C. Niehorster @ 2022-03-22 19:07 UTC (permalink / raw)
To: FFmpeg development discussions and patches; +Cc: Romain Beauxis
On Tue, Mar 22, 2022 at 3:10 PM Roger Pack <rogerdpack2@gmail.com> wrote:
>
> On Tue, Mar 22, 2022 at 7:40 AM <toots@rastageeks.org> wrote:
> >
> > From: Romain Beauxis <romain.beauxis@nextstep.com>
> >
> > diff --git a/libavdevice/dshow.c b/libavdevice/dshow.c
> > index 6039578ff9..4ee3f6e194 100644
> > --- a/libavdevice/dshow.c
> > +++ b/libavdevice/dshow.c
> > @@ -552,8 +552,8 @@ dshow_cycle_devices(AVFormatContext *avctx, ICreateDevEnum *devenum,
> > if (!device)
> > goto fail;
> >
> > - device->device_name = av_strdup(friendly_name);
> > - device->device_description = av_strdup(unique_name);
> > + device->device_name = av_strdup(unique_name);
> > + device->device_description = av_strdup(friendly_name);
> > if (!device->device_name || !device->device_description)
> > goto fail;
> >
>
> LGTM.
> The device enumeration API was added only recently to dshow, guess a
> bug crept in.
> Thanks!
LGTM.
Indeed, this was code introduced by my patch, committed to the ffmpeg
repo only 91 days ago. Indeed, this should have been the other way
around given the documentation of struct AVDeviceInfo.
All the best,
Dee
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [FFmpeg-devel] [PATCH 1/6] Fix dshow device name/description
2022-03-22 19:07 ` Diederick C. Niehorster
@ 2022-04-09 19:39 ` Marton Balint
0 siblings, 0 replies; 11+ messages in thread
From: Marton Balint @ 2022-04-09 19:39 UTC (permalink / raw)
To: FFmpeg development discussions and patches
On Tue, 22 Mar 2022, Diederick C. Niehorster wrote:
> On Tue, Mar 22, 2022 at 3:10 PM Roger Pack <rogerdpack2@gmail.com> wrote:
>>
>> On Tue, Mar 22, 2022 at 7:40 AM <toots@rastageeks.org> wrote:
>>>
>>> From: Romain Beauxis <romain.beauxis@nextstep.com>
>>>
>>> diff --git a/libavdevice/dshow.c b/libavdevice/dshow.c
>>> index 6039578ff9..4ee3f6e194 100644
>>> --- a/libavdevice/dshow.c
>>> +++ b/libavdevice/dshow.c
>>> @@ -552,8 +552,8 @@ dshow_cycle_devices(AVFormatContext *avctx, ICreateDevEnum *devenum,
>>> if (!device)
>>> goto fail;
>>>
>>> - device->device_name = av_strdup(friendly_name);
>>> - device->device_description = av_strdup(unique_name);
>>> + device->device_name = av_strdup(unique_name);
>>> + device->device_description = av_strdup(friendly_name);
>>> if (!device->device_name || !device->device_description)
>>> goto fail;
>>>
>>
>> LGTM.
>> The device enumeration API was added only recently to dshow, guess a
>> bug crept in.
>> Thanks!
>
> LGTM.
>
> Indeed, this was code introduced by my patch, committed to the ffmpeg
> repo only 91 days ago. Indeed, this should have been the other way
> around given the documentation of struct AVDeviceInfo.
Applied, thanks.
Marton
>
> All the best,
> Dee
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2022-04-09 19:40 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-03-22 13:39 [FFmpeg-devel] [PATCH 0/6] Various libavdevice cleanup & enhancements toots
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 1/6] Fix dshow device name/description toots
2022-03-22 14:10 ` Roger Pack
2022-03-22 19:07 ` Diederick C. Niehorster
2022-04-09 19:39 ` Marton Balint
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 2/6] Use appropriate method for device discovery, fix crash with bogus device index toots
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 3/6] libavdevice/avfoundation.m: Allow to select devices by unique ID toots
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 4/6] libavdevice/avfoundation.m: use setAudioSettings, extend supported formats toots
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 5/6] libavdevice/avfoundation.m: Replace mutex-based concurrency handling in avfoundation.m by a thread-safe fifo queue with maximum length toots
2022-03-22 13:39 ` [FFmpeg-devel] [PATCH 6/6] Add AudioToolbox audio input device toots
2022-03-22 14:12 ` [FFmpeg-devel] [PATCH 0/6] Various libavdevice cleanup & enhancements Roger Pack
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
This inbox may be cloned and mirrored by anyone:
git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git
# If you have public-inbox 1.1+ installed, you may
# initialize and index your mirror using the following commands:
public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
ffmpegdev@gitmailbox.com
public-inbox-index ffmpegdev
Example config snippet for mirrors.
AGPL code for this site: git clone https://public-inbox.org/public-inbox.git