FFmpeg源代码简单分析:avcodec_close()

本文详细分析了FFmpeg库函数avcodec_close()的用途、调用关系及内部实现,揭示了如何正确关闭编码器的过程,对于理解FFmpeg内部机制具有重要意义。

=====================================================

FFmpeg的库函数源代码分析文章列表:

【架构图】

FFmpeg源代码结构图 - 解码

FFmpeg源代码结构图 - 编码

【通用】

FFmpeg 源代码简单分析:av_register_all()

FFmpeg 源代码简单分析:avcodec_register_all()

FFmpeg 源代码简单分析:内存的分配和释放(av_malloc()av_free()等)

FFmpeg 源代码简单分析:常见结构体的初始化和销毁(AVFormatContextAVFrame等)

FFmpeg 源代码简单分析:avio_open2()

FFmpeg 源代码简单分析:av_find_decoder()av_find_encoder()

FFmpeg 源代码简单分析:avcodec_open2()

FFmpeg 源代码简单分析:avcodec_close()

【解码】

图解FFMPEG打开媒体的函数avformat_open_input

FFmpeg 源代码简单分析:avformat_open_input()

FFmpeg 源代码简单分析:avformat_find_stream_info()

FFmpeg 源代码简单分析:av_read_frame()

FFmpeg 源代码简单分析:avcodec_decode_video2()

FFmpeg 源代码简单分析:avformat_close_input()

【编码】

FFmpeg 源代码简单分析:avformat_alloc_output_context2()

FFmpeg 源代码简单分析:avformat_write_header()

FFmpeg 源代码简单分析:avcodec_encode_video()

FFmpeg 源代码简单分析:av_write_frame()

FFmpeg 源代码简单分析:av_write_trailer()

【其它】

FFmpeg源代码简单分析:日志输出系统(av_log()等)

FFmpeg源代码简单分析:结构体成员管理系统-AVClass

FFmpeg源代码简单分析:结构体成员管理系统-AVOption

FFmpeg源代码简单分析:libswscalesws_getContext()

FFmpeg源代码简单分析:libswscalesws_scale()

FFmpeg源代码简单分析:libavdeviceavdevice_register_all()

FFmpeg源代码简单分析:libavdevicegdigrab

【脚本】

FFmpeg源代码简单分析:makefile

FFmpeg源代码简单分析:configure

【H.264】

FFmpegH.264解码器源代码简单分析:概述

=====================================================


本文简单分析FFmpeg的avcodec_close()函数。该函数用于关闭编码器。avcodec_close()函数的声明位于libavcodec\avcodec.h,如下所示。


/**
 * Close a given AVCodecContext and free all the data associated with it
 * (but not the AVCodecContext itself).
 *
 * Calling this function on an AVCodecContext that hasn't been opened will free
 * the codec-specific data allocated in avcodec_alloc_context3() /
 * avcodec_get_context_defaults3() with a non-NULL codec. Subsequent calls will
 * do nothing.
 */
int avcodec_close(AVCodecContext *avctx);

该函数只有一个参数,就是需要关闭的编码器的AVCodecContext。

函数调用关系图

函数的调用关系图如下所示。

avcodec_close()

avcodec_close()的定义位于libavcodec\utils.c,如下所示。
av_cold int avcodec_close(AVCodecContext *avctx)
{
    if (!avctx)
        return 0;

    if (avcodec_is_open(avctx)) {
        FramePool *pool = avctx->internal->pool;
        int i;
        if (CONFIG_FRAME_THREAD_ENCODER &&
            avctx->internal->frame_thread_encoder && avctx->thread_count > 1) {
            ff_frame_thread_encoder_free(avctx);
        }
        if (HAVE_THREADS && avctx->internal->thread_ctx)
            ff_thread_free(avctx);
        //关闭编解码器
        if (avctx->codec && avctx->codec->close)
            avctx->codec->close(avctx);
        avctx->coded_frame = NULL;
        avctx->internal->byte_buffer_size = 0;
        av_freep(&avctx->internal->byte_buffer);
        av_frame_free(&avctx->internal->to_free);
        for (i = 0; i < FF_ARRAY_ELEMS(pool->pools); i++)
            av_buffer_pool_uninit(&pool->pools[i]);
        av_freep(&avctx->internal->pool);

        if (avctx->hwaccel && avctx->hwaccel->uninit)
            avctx->hwaccel->uninit(avctx);
        av_freep(&avctx->internal->hwaccel_priv_data);

        av_freep(&avctx->internal);
    }

    if (avctx->priv_data && avctx->codec && avctx->codec->priv_class)
        av_opt_free(avctx->priv_data);
    av_opt_free(avctx);
    av_freep(&avctx->priv_data);
    if (av_codec_is_encoder(avctx->codec))
        av_freep(&avctx->extradata);
    avctx->codec = NULL;
    avctx->active_thread_type = 0;

    return 0;
}

从avcodec_close()的定义可以看出,该函数释放AVCodecContext中有关的变量,并且调用了AVCodec的close()关闭了解码器。


AVCodec->close()

AVCodec的close()是一个函数指针,指向了特定编码器的关闭函数。在这里我们以libx264为例,看一下它对应的AVCodec的结构体的定义,如下所示。
AVCodec ff_libx264_encoder = {
    .name             = "libx264",
    .long_name        = NULL_IF_CONFIG_SMALL("libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
    .type             = AVMEDIA_TYPE_VIDEO,
    .id               = AV_CODEC_ID_H264,
    .priv_data_size   = sizeof(X264Context),
    .init             = X264_init,
    .encode2          = X264_frame,
    .close            = X264_close,
    .capabilities     = CODEC_CAP_DELAY | CODEC_CAP_AUTO_THREADS,
    .priv_class       = &x264_class,
    .defaults         = x264_defaults,
    .init_static_data = X264_init_static,
};

从ff_libx264_encoder的定义可以看出:close()函数对应的是X264_close()函数。继续看一下X264_close()函数的定义,如下所示。
static av_cold int X264_close(AVCodecContext *avctx)
{
    X264Context *x4 = avctx->priv_data;

    av_freep(&avctx->extradata);
    av_freep(&x4->sei);
    //关闭编码器
    if (x4->enc)
        x264_encoder_close(x4->enc);

    av_frame_free(&avctx->coded_frame);

    return 0;
}

从X264_close()的定义可以看出,该函数调用了libx264的x264_encoder_close()关闭了libx264编码器。


雷霄骅
leixiaohua1020@126.com
http://blog.csdn.net/leixiaohua1020

#include "ffmpegdecoder.h" #include "VideoFrame.h" #include <QElapsedTimer> #include <QDebug> #include "../GuiUtil.h" FFmpegDecoder::FFmpegDecoder(QObject *parent) : QThread(parent) , m_running(false), m_initialized(false) , m_formatCtx(nullptr), m_codecCtx(nullptr), m_frame(nullptr) , m_swsCtx(nullptr), m_videoStreamIndex(-1) , m_targetFormat(AV_PIX_FMT_RGB24) { avformat_network_init(); } FFmpegDecoder::~FFmpegDecoder() { stop(); close(); } bool FFmpegDecoder::open(const QString &url) { QMutexLocker locker(&m_mutex); m_url = url; return true; } void FFmpegDecoder::close() { cleanupFFmpeg(); } void FFmpegDecoder::stop() { m_running = false; while (isRunning()) { m_running = false; QThread::msleep(20); } } void FFmpegDecoder::run() { if (!initFFmpeg()) { cleanupFFmpeg(); return; } m_running = true; m_initialized = true; AVPacket *packet = av_packet_alloc(); QElapsedTimer frameTimer; frameTimer.start(); while (m_running) { int ret = av_read_frame(m_formatCtx, packet); if (ret < 0) { emit info("RTSP视频流故障, 播放已停止,请检查网络状况后在开始", Gui::Ctl::rtsp::RTSP_FAULT); break; } if (packet->stream_index == m_videoStreamIndex) { processPacket(packet); } av_packet_unref(packet); } av_packet_free(&packet); cleanupFFmpeg(); m_running = false; } bool FFmpegDecoder::initFFmpeg() { // 打开视频流 AVDictionary *options = nullptr; av_dict_set(&options, "rtsp_transport", "tcp", 0); // 强制使用TCP av_dict_set(&options, "timeout", "5000000", 0); // 5秒超时 int ret = avformat_open_input(&m_formatCtx, m_url.toUtf8().constData(), nullptr, &options); if (ret != 0) { emit info("无法打开RTSP流", Gui::Ctl::rtsp::RTSP_NETWORK_ERROR); return false; } if (avformat_find_stream_info(m_formatCtx, nullptr) < 0) { emit info("无法获取流信息", Gui::Ctl::rtsp::RTSP_NOT_GET_INFO); return false; } // 查找视频流 for (unsigned int i = 0; i < m_formatCtx->nb_streams; i++) { if (m_formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { m_videoStreamIndex = i; break; } } if (m_videoStreamIndex == -1) { emit info("未找到视频流", Gui::Ctl::rtsp::RTSP_NOT_FOUND); return false; } // 获取解码器 AVCodecParameters *codecParams = m_formatCtx->streams[m_videoStreamIndex]->codecpar; const AVCodec *codec = avcodec_find_decoder(codecParams->codec_id); if (!codec) { emit info("没有找到支持的解码器", Gui::Ctl::rtsp::RTSP_NOT_FOUND_DECODER); return false; } // 创建解码器上下文 m_codecCtx = avcodec_alloc_context3(codec); if (avcodec_parameters_to_context(m_codecCtx, codecParams) < 0) { emit info("无法创建解码器上下文", Gui::Ctl::rtsp::RTSP_NOT_CREATE_DECODER_CONTEXT); return false; } if (avcodec_open2(m_codecCtx, codec, nullptr) < 0) { emit info("无法打开解码器", Gui::Ctl::rtsp::RTSP_NOT_OPEN_DECODER); return false; } // 分配帧 m_frame = av_frame_alloc(); if (!m_frame) { emit info("无法分配帧", Gui::Ctl::rtsp::RTSP_NOT_ALLOCK_FRAME); return false; } emit info("即将播放视频", Gui::Ctl::rtsp::RTSP_OPENED); return true; } void FFmpegDecoder::cleanupFFmpeg() { if (m_swsCtx) { sws_freeContext(m_swsCtx); m_swsCtx = nullptr; } if (m_frame) { av_frame_free(&m_frame); m_frame = nullptr; } if (m_codecCtx) { //avcodec_close(m_codecCtx); avcodec_free_context(&m_codecCtx); m_codecCtx = nullptr; } if (m_formatCtx) { avformat_close_input(&m_formatCtx); m_formatCtx = nullptr; } m_initialized = false; } void FFmpegDecoder::processPacket(AVPacket *packet) { int ret = avcodec_send_packet(m_codecCtx, packet); if (ret < 0) { qDebug() << "发送数据包到解码器失败"; return; } while (ret >= 0) { ret = avcodec_receive_frame(m_codecCtx, m_frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { qDebug() << "解码错误"; break; } // 初始化转换上下文 if (!m_swsCtx) { m_swsCtx = sws_getContext( m_frame->width, m_frame->height, static_cast<AVPixelFormat>(m_frame->format), m_frame->width, m_frame->height, m_targetFormat, SWS_BILINEAR, nullptr, nullptr, nullptr); if (!m_swsCtx) { qDebug() << "无法创建转换上下文"; break; } } // 创建视频帧 auto videoFrame = std::make_shared<VideoFrame>(m_frame->width, m_frame->height); videoFrame->pts = m_frame->pts; // 设置目标帧参数 uint8_t *dstData[1] = { videoFrame->data }; int dstLinesize[1] = { videoFrame->linesize }; // 执行格式转换 sws_scale(m_swsCtx, m_frame->data, m_frame->linesize, 0, m_frame->height, dstData, dstLinesize); // 发送解码后的帧 emit frameDecoded(videoFrame); av_frame_unref(m_frame); } } 注释上述代码
最新发布
09-12
以下是完整注释后的代码,包含关键功能说明和实现细节: ```cpp #include "ffmpegdecoder.h" #include "VideoFrame.h" #include <QElapsedTimer> #include <QDebug> #include "../GuiUtil.h" // 构造函数:初始化FFmpeg解码器 FFmpegDecoder::FFmpegDecoder(QObject *parent) : QThread(parent) // 继承QThread实现多线程解码 , m_running(false) // 运行状态标志 , m_initialized(false) // 初始化标志 , m_formatCtx(nullptr) // FFmpeg格式上下文 , m_codecCtx(nullptr) // 解码器上下文 , m_frame(nullptr) // 原始帧存储 , m_swsCtx(nullptr) // 图像转换上下文 , m_videoStreamIndex(-1) // 视频流索引 , m_targetFormat(AV_PIX_FMT_RGB24) // 目标像素格式(RGB24) { avformat_network_init(); // 初始化FFmpeg网络模块 } // 析构函数:确保资源释放 FFmpegDecoder::~FFmpegDecoder() { stop(); // 停止解码线程 close(); // 释放资源 } // 打开视频流(非实际连接,仅设置URL) bool FFmpegDecoder::open(const QString &url) { QMutexLocker locker(&m_mutex); // 线程安全锁 m_url = url; // 保存视频流URL return true; } // 关闭并释放所有FFmpeg资源 void FFmpegDecoder::close() { cleanupFFmpeg(); // 清理FFmpeg相关资源 } // 停止解码线程 void FFmpegDecoder::stop() { m_running = false; // 设置停止标志 while (isRunning()) // 等待线程退出 { m_running = false; QThread::msleep(20); // 20ms等待间隔 } } // 主线程函数(核心解码逻辑) void FFmpegDecoder::run() { // 初始化FFmpeg环境 if (!initFFmpeg()) { cleanupFFmpeg(); return; } m_running = true; m_initialized = true; AVPacket *packet = av_packet_alloc(); // 分配数据包内存 QElapsedTimer frameTimer; // 帧计时器 frameTimer.start(); // 主解码循环 while (m_running) { // 读取视频帧数据包 int ret = av_read_frame(m_formatCtx, packet); if (ret < 0) { emit info("RTSP视频流故障, 播放已停止,请检查网络状况后在开始", Gui::Ctl::rtsp::RTSP_FAULT); break; } // 只处理视频流数据包 if (packet->stream_index == m_videoStreamIndex) { processPacket(packet); // 解码处理数据包 } av_packet_unref(packet); // 释放数据包引用 } // 清理资源 av_packet_free(&packet); cleanupFFmpeg(); m_running = false; } // 初始化FFmpeg解码环境 bool FFmpegDecoder::initFFmpeg() { // 设置RTSP连接参数 AVDictionary *options = nullptr; av_dict_set(&options, "rtsp_transport", "tcp", 0); // 强制使用TCP传输 av_dict_set(&options, "timeout", "5000000", 0); // 5秒超时(微秒) // 打开视频流 int ret = avformat_open_input(&m_formatCtx, m_url.toUtf8().constData(), nullptr, &options); if (ret != 0) { emit info("无法打开RTSP流", Gui::Ctl::rtsp::RTSP_NETWORK_ERROR); return false; } // 获取流信息 if (avformat_find_stream_info(m_formatCtx, nullptr) < 0) { emit info("无法获取流信息", Gui::Ctl::rtsp::RTSP_NOT_GET_INFO); return false; } // 查找视频流索引 for (unsigned int i = 0; i < m_formatCtx->nb_streams; i++) { if (m_formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { m_videoStreamIndex = i; break; } } if (m_videoStreamIndex == -1) { emit info("未找到视频流", Gui::Ctl::rtsp::RTSP_NOT_FOUND); return false; } // 获取解码器参数 AVCodecParameters *codecParams = m_formatCtx->streams[m_videoStreamIndex]->codecpar; // 查找合适的解码器 const AVCodec *codec = avcodec_find_decoder(codecParams->codec_id); if (!codec) { emit info("没有找到支持的解码器", Gui::Ctl::rtsp::RTSP_NOT_FOUND_DECODER); return false; } // 创建解码器上下文 m_codecCtx = avcodec_alloc_context3(codec); if (avcodec_parameters_to_context(m_codecCtx, codecParams) < 0) { emit info("无法创建解码器上下文", Gui::Ctl::rtsp::RTSP_NOT_CREATE_DECODER_CONTEXT); return false; } // 打开解码器 if (avcodec_open2(m_codecCtx, codec, nullptr) < 0) { emit info("无法打开解码器", Gui::Ctl::rtsp::RTSP_NOT_OPEN_DECODER); return false; } // 分配帧内存 m_frame = av_frame_alloc(); if (!m_frame) { emit info("无法分配帧", Gui::Ctl::rtsp::RTSP_NOT_ALLOCK_FRAME); return false; } emit info("即将播放视频", Gui::Ctl::rtsp::RTSP_OPENED); return true; } // 清理FFmpeg资源 void FFmpegDecoder::cleanupFFmpeg() { // 释放图像转换上下文 if (m_swsCtx) { sws_freeContext(m_swsCtx); m_swsCtx = nullptr; } // 释放帧内存 if (m_frame) { av_frame_free(&m_frame); m_frame = nullptr; } // 释放解码器上下文 if (m_codecCtx) { avcodec_free_context(&m_codecCtx); m_codecCtx = nullptr; } // 关闭输入流 if (m_formatCtx) { avformat_close_input(&m_formatCtx); m_formatCtx = nullptr; } m_initialized = false; } // 处理视频数据包 void FFmpegDecoder::processPacket(AVPacket *packet) { // 发送数据包到解码器 int ret = avcodec_send_packet(m_codecCtx, packet); if (ret < 0) { qDebug() << "发送数据包到解码器失败"; return; } // 接收解码后的帧 while (ret >= 0) { ret = avcodec_receive_frame(m_codecCtx, m_frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; // 需要更多数据或到达结尾 } else if (ret < 0) { qDebug() << "解码错误"; break; } // 初始化图像转换器(SWSContext) if (!m_swsCtx) { m_swsCtx = sws_getContext( m_frame->width, m_frame->height, static_cast<AVPixelFormat>(m_frame->format), // 源格式 m_frame->width, m_frame->height, m_targetFormat, // 目标格式(RGB24) SWS_BILINEAR, // 缩放算法 nullptr, nullptr, nullptr); if (!m_swsCtx) { qDebug() << "无法创建转换上下文"; break; } } // 创建视频帧对象(QImage封装) auto videoFrame = std::make_shared<VideoFrame>(m_frame->width, m_frame->height); videoFrame->pts = m_frame->pts; // 保存时间戳 // 设置转换参数 uint8_t *dstData[1] = { videoFrame->data }; // 目标数据指针 int dstLinesize[1] = { videoFrame->linesize }; // 目标行大小 // 执行像素格式转换 sws_scale(m_swsCtx, m_frame->data, // 源数据 m_frame->linesize,// 源行大小 0, // 起始Y位置 m_frame->height, // 处理行数 dstData, // 目标数据 dstLinesize); // 目标行大小 // 发送解码完成的帧信号 emit frameDecoded(videoFrame); av_frame_unref(m_frame); // 释放帧引用 } } ``` ### 关键流程说明 1. **线程管理**: - 继承`QThread`实现独立解码线程 - 通过`m_running`标志控制线程生命周期 - 使用互斥锁(`QMutexLocker`)保证线程安全 2. **FFmpeg初始化**: - `avformat_open_input`打开媒体流 - `avcodec_alloc_context3`创建解码器上下文 - `sws_getContext`准备图像格式转换 3. **解码循环**: - `av_read_frame`读取数据包 - `avcodec_send_packet`/`avcodec_receive_frame`完成解码 - `sws_scale`转换像素格式为RGB24 4. **资源管理**: - 使用FFmpeg的`av_xxx_free`系列函数释放资源 - 智能指针管理`VideoFrame`对象 - 错误处理通过信号机制通知上层 5. **性能优化**: - 重复使用`SWSContext`避免重复创建 - 仅处理视频流数据包(m_videoStreamIndex) - TCP传输保证稳定性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值