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

使用FFmpeg读取视频帧到OpenCV Mat时触发libswscale断言错误

问题:FFmpeg转OpenCV Mat触发swscale断言错误

我尝试用C++的FFmpeg库读取视频所有帧并转换为OpenCV的Mat对象,运行时触发如下断言错误:

Assertion desc failed at libswscale/swscale_internal.h:727

经GDB调试,错误发生在创建sws上下文的代码行:

struct SwsContext *frameScaler = sws_getContext(width, height, videoCodecContext->pix_fmt, width, height, AV_PIX_FMT_BGR24, SWS_BICUBIC, nullptr, nullptr, nullptr);

完整代码如下:

#include <iostream>
#include <string>
#include <vector>

extern "C" {
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <libavcodec/avcodec.h>
}

#include <opencv2/opencv.hpp>

// FFmpeg错误检查辅助函数
inline void checkError(int error, const std::string &message) {
    if (error < 0) {
        std::cerr << message << ": " << av_err2str(error) << std::endl;
        exit(EXIT_FAILURE);
    }
}

int main() {
    // 初始化FFmpeg
    av_log_set_level(AV_LOG_ERROR);
    avformat_network_init();

    // 打开输入文件
    std::string fileName = "input.mp4";
    AVFormatContext *formatContext = nullptr;
    int error = avformat_open_input(&formatContext, fileName.c_str(), nullptr, nullptr);
    checkError(error, "打开输入文件失败");

    // 查找视频流
    AVStream *videoStream = nullptr;
    for (unsigned int i = 0; i < formatContext->nb_streams; i++) {
        if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && !videoStream) {
            videoStream = formatContext->streams[i];
        }
    }
    if (!videoStream) {
        std::cerr << "错误:输入文件不含视频流" << std::endl;
        exit(EXIT_FAILURE);
    }

    // 创建视频解码器上下文
    const AVCodec *videoCodec = avcodec_find_decoder(videoStream->codecpar->codec_id);
    AVCodecContext *videoCodecContext = avcodec_alloc_context3(videoCodec);
    if (!videoCodecContext) {
        std::cerr << "分配视频解码器上下文失败" << std::endl;
        exit(EXIT_FAILURE);
    }
    error = avcodec_parameters_to_context(videoCodecContext, videoStream->codecpar);
    checkError(error, "设置解码器上下文参数失败");
    error = avcodec_open2(videoCodecContext, videoCodec, nullptr);
    checkError(error, "打开视频解码器失败");

    // 创建帧缩放器
    int width = videoCodecContext->width;
    int height = videoCodecContext->height;
    struct SwsContext *frameScaler = sws_getContext(width, height, videoCodecContext->pix_fmt, width, height, AV_PIX_FMT_BGR24, SWS_BICUBIC, nullptr, nullptr, nullptr);

    // 读取数据包并解码视频帧
    std::vector<cv::Mat> videoFrames;
    AVPacket packet;
    while (av_read_frame(formatContext, &packet) == 0) {
        if (packet.stream_index == videoStream->index) {
            // 解码视频帧
            AVFrame *frame = av_frame_alloc();
            int gotFrame = 0;
            error = avcodec_send_packet(videoCodecContext, &packet);
            checkError(error, "向解码器发送数据包失败");
            error = avcodec_receive_frame(videoCodecContext, frame);
            checkError(error, "从解码器接收帧失败");
            if (error == 0) {
                gotFrame = 1;
            }
            if (gotFrame) {
                // 将帧缩转为目标格式
                AVFrame *scaledFrame = av_frame_alloc();
                av_image_alloc(scaledFrame->data, scaledFrame->linesize, width, height, AV_PIX_FMT_BGR24, 32);
                sws_scale(frameScaler, frame->data, frame->linesize, 0, height, scaledFrame->data, scaledFrame->linesize);

                // 将帧数据复制到cv::Mat对象
                cv::Mat mat(height, width, CV_8UC3, scaledFrame->data[0], scaledFrame->linesize[0]);
                videoFrames.push_back(mat.clone());

                // 清理资源
                av_freep(&scaledFrame->data[0]);
                av_frame_free(&scaledFrame);
            }
            av_frame_free(&frame);
        }
        av_packet_unref(&packet);
    }

    // 清理资源
    sws_freeContext(frameScaler);
    avcodec_free_context(&videoCodecContext);
    avformat_close_input(&formatContext);

    return 0;
}

问题分析与解决

这个断言错误本质是swscale无法处理输入的像素格式,或者输入格式参数无效。以下是具体原因和修复方案:

1. 检查像素格式有效性

打开解码器后,videoCodecContext->pix_fmt可能未被正确初始化(比如某些视频的codec参数未填充该字段),导致sws_getContext接收到无效格式。添加检查:

if (videoCodecContext->pix_fmt == AV_PIX_FMT_NONE) {
    std::cerr << "错误:无效的像素格式" << std::endl;
    exit(EXIT_FAILURE);
}

2. 处理硬件解码/非标准格式

如果视频使用硬件加速解码,返回的帧格式可能是硬件专用格式(如AV_PIX_FMT_CUDA),swscale无法直接处理。强制使用软件解码:

// 在avcodec_open2之前添加
videoCodecContext->hw_device_ctx = nullptr; // 禁用硬件解码

3. 检查sws上下文创建结果

sws_getContext创建失败时会返回nullptr,原代码未做检查,后续调用sws_scale会直接崩溃。添加判断:

struct SwsContext *frameScaler = sws_getContext(width, height, videoCodecContext->pix_fmt, width, height, AV_PIX_FMT_BGR24, SWS_BICUBIC, nullptr, nullptr, nullptr);
if (!frameScaler) {
    std::cerr << "创建sws缩放上下文失败" << std::endl;
    exit(EXIT_FAILURE);
}

4. 修正解码循环的错误处理

原代码中avcodec_receive_frame返回EOF或EAGAIN时直接调用checkError,导致程序异常退出。正确处理逻辑:

// 替换原解码循环内的接收帧代码
error = avcodec_send_packet(videoCodecContext, &packet);
checkError(error, "向解码器发送数据包失败");

while (true) {
    error = avcodec_receive_frame(videoCodecContext, frame);
    if (error == AVERROR_EOF) {
        break; // 解码完成
    } else if (error == AVERROR(EAGAIN)) {
        break; // 需要更多数据包
    } else if (error < 0) {
        checkError(error, "从解码器接收帧失败");
    } else {
        // 这里处理缩放和Mat转换逻辑
        // ...
        av_frame_unref(frame); // 重置帧,准备接收下一个
    }
}

内容的提问来源于stack exchange,提问作者Alex

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 13:55:36