PR #23850 opened by superuser404 URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23850 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23850.patch
`call_kernel()` obtains the command buffer and compute encoder from property getters (`mtlQueue.commandBuffer`, `buffer.computeCommandEncoder`). Under the non-ARC build these return autoreleased (+0) references, yet the function releases them manually via `ff_objc_release()`. That is an over-release: when the enclosing autorelease pool is drained, both objects are released a second time. On current macOS this crashes the plain ffmpeg CLI. The ObjC runtime pops the thread-local autorelease pool during pthread TSD cleanup when the filter thread exits, so every run that used the filter crashes at teardown: ``` 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 - ``` ``` Termination: EXC_BAD_ACCESS (SIGSEGV), KERN_INVALID_ADDRESS 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<AutoreleasePoolPage*, ...>::dtor_(void*) + 156 4 libsystem_pthread.dylib _pthread_tsd_cleanup + 620 5 libsystem_pthread.dylib _pthread_exit + 84 6 libsystem_pthread.dylib _pthread_start + 148 ``` Host applications that use libavfilter directly hit the same crash earlier, for example when a GCD queue drains its last-resort pool after a work item that ran the filter. The fix wraps the kernel invocation in `@autoreleasepool` and drops the two manual releases, so the pool pop is the single balanced release and the Metal transients drain once per frame instead of accumulating. The `ff_objc_release()` calls on the `s->mtl*` members in `do_uninit()` are left alone; those hold owned (+1) references obtained from `new*` methods. Verified on macOS 26 (Apple Silicon): the command above crashes with exit 139 before the patch and exits 0 with identical output after it, and the filter output is unchanged. Since this is a plain crash fix it may be worth backporting to the active release branches; the bug has been present since the filter was introduced. >From 732e3afeb3f4a35389edeccc4329256265519a56 Mon Sep 17 00:00:00 2001 From: Vincent Herbst <[email protected]> Date: Sun, 19 Jul 2026 20:09:29 +0200 Subject: [PATCH] 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. --- libavfilter/vf_yadif_videotoolbox.m | 45 ++++++++++++++--------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/libavfilter/vf_yadif_videotoolbox.m b/libavfilter/vf_yadif_videotoolbox.m index f8eb0c6bfa..b1211ceed1 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 - }; + @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]; + [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); + [buffer commit]; + [buffer waitUntilCompleted]; + } } static void filter(AVFilterContext *ctx, AVFrame *dst, -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
