You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何跨FFI边界处理断言语句?Tauri视频校验程序崩溃求助

解决libswscale断言错误导致的程序崩溃问题

问题背景

实现了一个Tauri命令函数,用于检测指定路径文件是否为有效视频,但传入非视频文件(如.rs文件)时程序直接崩溃。尝试用线程包裹逻辑、panic::catch_unwind捕获异常均无效,控制台报错显示底层libswscale库触发断言失败:

[sunrast @ 0x7f1e44003040] this is not sunras encoded data
[image2 @ 0x7f1e44000900] Could not find codec parameters for stream 0 (Video: sunrast, none): unspecified size
Consider increasing the value for the 'analyzeduration' (0) and 'probesize' (5000000) options
Assertion desc failed at libswscale/swscale_internal.h:725

相关代码:

use std::{panic, thread};

use libvmaf_rs::video::Video;

#[tauri::command]
pub async fn validate_video(path: String) -> Result<(), String> {
    let did_panic = thread::spawn(|| match Video::new(path, 640, 480) {
        Ok(_) => Ok(()),
        Err(e) => Err(e.frames().into_iter().map(|f| format!("{f:?}")).fold(
            String::new(),
            |mut prev, next| {
                prev.push_str(&next);
                prev.push_str("\n");
                prev
            },
        )),
    });

    match did_panic.join() {
        Ok(e) => e,
        Err(e) => Err("Panicked while trying to load video".to_string()),
    }
}

核心原因

这不是Rust层面的panic,而是C语言层的断言失败:libswscale作为FFmpeg的C库,断言失败会直接调用abort()终止进程,完全绕过Rust的panic捕获机制,因此panic::catch_unwind和线程都无法拦截。

解决方案

1. 前置过滤非视频文件

通过文件扩展名或魔术字节初步筛选,避免无效调用触发底层错误:

fn is_likely_video(path: &str) -> bool {
    // 覆盖常见视频扩展名
    let video_extensions = ["mp4", "mov", "avi", "mkv", "flv", "webm", "wmv"];
    path.split('.')
        .last()
        .map(|ext| video_extensions.contains(&ext.to_lowercase().as_str()))
        .unwrap_or(false)
}

#[tauri::command]
pub async fn validate_video(path: String) -> Result<(), String> {
    if !is_likely_video(&path) {
        return Err("Unrecognized video file extension".to_string());
    }

    // 原有逻辑...
    let did_panic = thread::spawn(|| match Video::new(path, 640, 480) {
        Ok(_) => Ok(()),
        Err(e) => Err(e.frames().into_iter().map(|f| format!("{f:?}")).fold(
            String::new(),
            |mut prev, next| {
                prev.push_str(&next);
                prev.push_str("\n");
                prev
            },
        )),
    });

    match did_panic.join() {
        Ok(e) => e,
        Err(e) => Err("Panicked while trying to load video".to_string()),
    }
}

2. 用FFmpeg API提前探测文件

直接通过ffmpeg-next库探测文件流信息,确认是有效视频后再调用libvmaf_rs,从根源避免触发断言:

use ffmpeg_next::{format, media};

fn probe_video_file(path: &str) -> Result<(), String> {
    // 打开文件并读取流信息
    let ictx = format::input(path)
        .map_err(|e| format!("Failed to open file: {e}"))?;
    
    // 查找最佳视频流
    let video_stream = ictx.streams()
        .best(media::Type::Video)
        .ok_or("No video stream found in file")?;
    
    // 校验视频流参数有效性
    let codec_params = video_stream.codec_parameters();
    if codec_params.width() == 0 || codec_params.height() == 0 {
        return Err("Invalid video stream parameters".to_string());
    }

    Ok(())
}

#[tauri::command]
pub async fn validate_video(path: String) -> Result<(), String> {
    // 前置探测,失败直接返回
    probe_video_file(&path)?;

    // 调用libvmaf_rs处理
    let did_panic = thread::spawn(|| match Video::new(path, 640, 480) {
        Ok(_) => Ok(()),
        Err(e) => Err(e.frames().into_iter().map(|f| format!("{f:?}")).fold(
            String::new(),
            |mut prev, next| {
                prev.push_str(&next);
                prev.push_str("\n");
                prev
            },
        )),
    });

    match did_panic.join() {
        Ok(e) => e,
        Err(e) => Err("Panicked while trying to load video".to_string()),
    }
}

3. 禁用FFmpeg断言(不推荐)

若必须处理任意文件,可在编译FFmpeg时添加--disable-assertions参数禁用断言,但这会隐藏潜在bug,降低程序稳定性,仅作为最后手段。

内容的提问来源于stack exchange,提问作者Brandon Piña

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 20:15:55