Commit 7f2e5cdd7c for ffmpeg
commit 7f2e5cdd7c3ad502141c918bac13e2a31b4829c0
Author: Vincent Herbst <superuser404@tuta.com>
Date: Sun Jul 19 20:09:29 2026 +0200
avfilter/vf_yadif_videotoolbox: fix over-release of autoreleased Metal objects
call_kernel() obtains the command buffer and compute encoder from
property getters (mtlQueue.commandBuffer, buffer.computeCommandEncoder),
which return autoreleased references under the non-ARC build, and then
releases them via ff_objc_release(). That is an over-release: when the
enclosing autorelease pool is drained, the objects are released a second
time and the process crashes with EXC_BAD_ACCESS in objc_release.
On current macOS this is reproducible with the ffmpeg CLI itself. The
filter thread's thread-local autorelease pool is popped during pthread
TSD cleanup at thread exit, so any run that used yadif_videotoolbox
crashes when the filter thread terminates:
ffmpeg -init_hw_device videotoolbox=vt -filter_hw_device vt \
-f lavfi -i testsrc2=size=640x480:rate=30 \
-vf setfield=tff,format=nv12,hwupload,yadif_videotoolbox,\
hwdownload,format=nv12 -frames:v 20 -f null -
Thread 11 Crashed:: vf#0:0
0 libobjc.A.dylib objc_release + 16
1 libobjc.A.dylib AutoreleasePoolPage::releaseUntil(objc_object**) + 204
2 libobjc.A.dylib objc_autoreleasePoolPop + 244
3 libobjc.A.dylib objc_tls_direct_base<...>::dtor_(void*) + 156
4 libsystem_pthread.dylib _pthread_tsd_cleanup + 620
5 libsystem_pthread.dylib _pthread_exit + 84
Host applications hit the same crash earlier, for example when a
dispatch queue drains its last-resort pool after a work item that ran
the filter.
Wrap the kernel invocation in @autoreleasepool and drop the manual
releases, making the pool pop the single balanced release and draining
the Metal transients once per frame. The ff_objc_release() calls on the
s->mtl* members in do_uninit() stay: those hold owned references
obtained from new* methods.
diff --git a/libavfilter/vf_yadif_videotoolbox.m b/libavfilter/vf_yadif_videotoolbox.m
index 4fb2a9d96a..4c7dc261d8 100644
--- a/libavfilter/vf_yadif_videotoolbox.m
+++ b/libavfilter/vf_yadif_videotoolbox.m
@@ -73,31 +73,30 @@ static void call_kernel(AVFilterContext *ctx,
int tff) API_AVAILABLE(macos(10.11), ios(8.0))
{
YADIFVTContext *s = ctx->priv;
- id<MTLCommandBuffer> buffer = s->mtlQueue.commandBuffer;
- id<MTLComputeCommandEncoder> encoder = buffer.computeCommandEncoder;
- struct mtlYadifParams *params = (struct mtlYadifParams *)s->mtlParamsBuffer.contents;
- *params = (struct mtlYadifParams){
- .channels = channels,
- .parity = parity,
- .tff = tff,
- .is_second_field = !(parity ^ tff),
- .skip_spatial_check = s->yadif.mode&2,
- .field_mode = s->yadif.current_field
- };
-
- [encoder setTexture:dst atIndex:0];
- [encoder setTexture:prev atIndex:1];
- [encoder setTexture:cur atIndex:2];
- [encoder setTexture:next atIndex:3];
- [encoder setBuffer:s->mtlParamsBuffer offset:0 atIndex:4];
- ff_metal_compute_encoder_dispatch(s->mtlDevice, s->mtlPipeline, encoder, dst.width, dst.height);
- [encoder endEncoding];
-
- [buffer commit];
- [buffer waitUntilCompleted];
-
- ff_objc_release(&encoder);
- ff_objc_release(&buffer);
+ @autoreleasepool {
+ id<MTLCommandBuffer> buffer = s->mtlQueue.commandBuffer;
+ id<MTLComputeCommandEncoder> encoder = buffer.computeCommandEncoder;
+ struct mtlYadifParams *params = (struct mtlYadifParams *)s->mtlParamsBuffer.contents;
+ *params = (struct mtlYadifParams){
+ .channels = channels,
+ .parity = parity,
+ .tff = tff,
+ .is_second_field = !(parity ^ tff),
+ .skip_spatial_check = s->yadif.mode&2,
+ .field_mode = s->yadif.current_field
+ };
+
+ [encoder setTexture:dst atIndex:0];
+ [encoder setTexture:prev atIndex:1];
+ [encoder setTexture:cur atIndex:2];
+ [encoder setTexture:next atIndex:3];
+ [encoder setBuffer:s->mtlParamsBuffer offset:0 atIndex:4];
+ ff_metal_compute_encoder_dispatch(s->mtlDevice, s->mtlPipeline, encoder, dst.width, dst.height);
+ [encoder endEncoding];
+
+ [buffer commit];
+ [buffer waitUntilCompleted];
+ }
}
static int filter(AVFilterContext *ctx, AVFrame *dst,