* [FFmpeg-devel] [PATCH] avfoundation: buffer multiple audio frames to prevent sample overwrite (PR #20871)
@ 2025-11-08 20:28 drobinator via ffmpeg-devel
0 siblings, 0 replies; only message in thread
From: drobinator via ffmpeg-devel @ 2025-11-08 20:28 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: drobinator
PR #20871 opened by drobinator
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20871
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20871.patch
The AVFoundation audio callback may outpace avf_read_packet, causing the
previous single-buffer storage (current_audio_frame) to be overwritten
before it is read, which results in missing samples and dropouts.
Replace the single audio buffer with a small ring buffer, allowing
multiple audio frames to be queued.
Fixes #11398
>From e611e1f988b269fc90d8d2ea95ccd578814ffd02 Mon Sep 17 00:00:00 2001
From: Dylan Robinson <richard.dylan@gmail.com>
Date: Sat, 8 Nov 2025 15:06:39 -0500
Subject: [PATCH] avfoundation: buffer multiple audio frames to prevent sample
overwrite
The AVFoundation audio callback may outpace avf_read_packet, causing the
previous single-buffer storage (current_audio_frame) to be overwritten
before it is read, which results in missing samples and dropouts.
Replace the single audio buffer with a small ring buffer, allowing
multiple audio frames to be queued.
Fixes #11398
---
libavdevice/avfoundation.m | 65 +++++++++++++++++++++++++++++---------
1 file changed, 50 insertions(+), 15 deletions(-)
diff --git a/libavdevice/avfoundation.m b/libavdevice/avfoundation.m
index ebec1ac4f2..7090dfaad3 100644
--- a/libavdevice/avfoundation.m
+++ b/libavdevice/avfoundation.m
@@ -82,6 +82,44 @@ static const struct AVFPixelFormatSpec avf_pixel_formats[] = {
{ AV_PIX_FMT_NONE, 0 }
};
+#define AVF_FRAME_LIST_LENGTH 4
+
+typedef struct
+{
+ size_t write_index;
+ size_t read_index;
+ CMSampleBufferRef frames[AVF_FRAME_LIST_LENGTH];
+} AVFFrameList;
+
+static void push_frame(AVFFrameList* list, CMSampleBufferRef frame)
+{
+ list->frames[list->write_index] = (CMSampleBufferRef)CFRetain(frame);
+ list->write_index = (list->write_index + 1) % AVF_FRAME_LIST_LENGTH;
+
+ if (list->write_index == list->read_index) {
+ for (int i = 0; i < (AVF_FRAME_LIST_LENGTH / 2); ++i) {
+ CFRelease(list->frames[list->read_index]);
+ list->read_index = (list->read_index + 1) % AVF_FRAME_LIST_LENGTH;
+ }
+ }
+}
+
+static CMSampleBufferRef peek_frame(AVFFrameList* list)
+{
+ if (list->write_index != list->read_index)
+ return list->frames[list->read_index];
+
+ return nil;
+}
+
+static void pop_frame(AVFFrameList* list)
+{
+ if (list->write_index != list->read_index) {
+ CFRelease(list->frames[list->read_index]);
+ list->read_index = (list->read_index + 1) % AVF_FRAME_LIST_LENGTH;
+ }
+}
+
typedef struct
{
AVClass* class;
@@ -131,7 +169,7 @@ typedef struct
AVCaptureVideoDataOutput *video_output;
AVCaptureAudioDataOutput *audio_output;
CMSampleBufferRef current_frame;
- CMSampleBufferRef current_audio_frame;
+ AVFFrameList audio_frames;
AVCaptureDevice *observed_device;
#if !TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
@@ -263,6 +301,8 @@ static void unlock_frames(AVFContext* ctx)
{
if (self = [super init]) {
_context = context;
+ _context->audio_frames.write_index = 0;
+ _context->audio_frames.read_index = 0;
}
return self;
}
@@ -273,11 +313,7 @@ static void unlock_frames(AVFContext* ctx)
{
lock_frames(_context);
- if (_context->current_audio_frame != nil) {
- CFRelease(_context->current_audio_frame);
- }
-
- _context->current_audio_frame = (CMSampleBufferRef)CFRetain(audioFrame);
+ push_frame(&_context->audio_frames, audioFrame);
unlock_frames(_context);
@@ -695,7 +731,7 @@ static int get_audio_config(AVFormatContext *s)
avpriv_set_pts_info(stream, 64, 1, avf_time_base);
- format_desc = CMSampleBufferGetFormatDescription(ctx->current_audio_frame);
+ format_desc = CMSampleBufferGetFormatDescription(peek_frame(&ctx->audio_frames));
const AudioStreamBasicDescription *basic_desc = CMAudioFormatDescriptionGetStreamBasicDescription(format_desc);
if (!basic_desc) {
@@ -743,7 +779,7 @@ static int get_audio_config(AVFormatContext *s)
}
if (ctx->audio_non_interleaved) {
- CMBlockBufferRef block_buffer = CMSampleBufferGetDataBuffer(ctx->current_audio_frame);
+ CMBlockBufferRef block_buffer = CMSampleBufferGetDataBuffer(peek_frame(&ctx->audio_frames));
ctx->audio_buffer_size = CMBlockBufferGetDataLength(block_buffer);
ctx->audio_buffer = av_malloc(ctx->audio_buffer_size);
if (!ctx->audio_buffer) {
@@ -753,8 +789,7 @@ static int get_audio_config(AVFormatContext *s)
}
}
- CFRelease(ctx->current_audio_frame);
- ctx->current_audio_frame = nil;
+ pop_frame(&ctx->audio_frames);
unlock_frames(ctx);
@@ -1173,8 +1208,9 @@ static int avf_read_packet(AVFormatContext *s, AVPacket *pkt)
unlock_frames(ctx);
return status;
}
- } else if (ctx->current_audio_frame != nil) {
- CMBlockBufferRef block_buffer = CMSampleBufferGetDataBuffer(ctx->current_audio_frame);
+ } else if (peek_frame(&ctx->audio_frames) != nil) {
+ CMSampleBufferRef frame = peek_frame(&ctx->audio_frames);
+ CMBlockBufferRef block_buffer = CMSampleBufferGetDataBuffer(frame);
int block_buffer_size = CMBlockBufferGetDataLength(block_buffer);
if (!block_buffer || !block_buffer_size) {
@@ -1195,7 +1231,7 @@ static int avf_read_packet(AVFormatContext *s, AVPacket *pkt)
CMItemCount count;
CMSampleTimingInfo timing_info;
- if (CMSampleBufferGetOutputSampleTimingInfoArray(ctx->current_audio_frame, 1, &timing_info, &count) == noErr) {
+ if (CMSampleBufferGetOutputSampleTimingInfoArray(frame, 1, &timing_info, &count) == noErr) {
AVRational 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);
}
@@ -1249,8 +1285,7 @@ static int avf_read_packet(AVFormatContext *s, AVPacket *pkt)
}
}
- CFRelease(ctx->current_audio_frame);
- ctx->current_audio_frame = nil;
+ pop_frame(&ctx->audio_frames);
} else {
pkt->data = NULL;
unlock_frames(ctx);
--
2.49.1
_______________________________________________
ffmpeg-devel mailing list -- ffmpeg-devel@ffmpeg.org
To unsubscribe send an email to ffmpeg-devel-leave@ffmpeg.org
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2025-11-08 20:29 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-11-08 20:28 [FFmpeg-devel] [PATCH] avfoundation: buffer multiple audio frames to prevent sample overwrite (PR #20871) drobinator via ffmpeg-devel
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