FFmpeg处理流程

结构体

AVFormatContext

作用:管理媒体文件的封装格式上下文,存储文件格式、流信息、I/O 操作等元数据。

关键字段

cpp 复制代码
AVInputFormat *iformat;   // 输入格式(如MP4、FLV)
AVStream **streams;       // 音视频流数组
int nb_streams;           // 流数量
int64_t duration;         // 总时长(微秒)

初始化:avformat_alloc_context()avformat_open_input()

AVStream

作用:表示单个音视频流,包含编解码参数和时间基准。

关键字段:

cpp 复制代码
AVCodecParameters *codecpar;  // 编解码参数(如分辨率、采样率)
AVRational time_base;         // 时间基(如1/30表示30fps)

初始化:avformat_new_stream()

AVCodec

AVCodecContext

作用:编解码器上下文,存储编解码参数(如码率、帧率、像素格式)。

关键字段:

cpp 复制代码
enum AVCodecID codec_id;      // 编解码器ID(如H.264、AAC)
int width, height;            // 视频分辨率
enum AVPixelFormat pix_fmt;   // 像素格式(如YUV420P)
AVRational time_base;         // 编码器时间基

初始化:avcodec_alloc_context3(),avcodec_parameters_to_context()

AVPacket

作用:存储编码后的压缩数据(如H.264数据包)。

关键字段:

cpp 复制代码
uint8_t *data;       // 压缩数据指针
int size;            // 数据大小
int64_t pts, dts;    // 显示和解码时间戳

初始化:av_packet_alloc()av_packet_unref()

AVFrame

作用:存储解码后的原始数据(如YUV像素数据或PCM音频样本)。

关键字段:

cpp 复制代码
uint8_t *data[AV_NUM_DATA_POINTERS]; // 数据指针(如Y、U、V分量)
int linesize[AV_NUM_DATA_POINTERS];  // 每行字节数
int width, height;                    // 视频分辨率

初始化:av_frame_alloc()av_frame_free()

SwsContext

作用:图像格式转换上下文(如YUV转RGB)。

初始化:sws_getContext(),销毁:sws_freeContext()

SwrContext

作用:音频重采样上下文(如48kHz转44.1kHz)。

初始化:swr_alloc_set_opts(),销毁:swr_free()

API

avformat_open_input

avformat_find_stream_info

av_find_best_stream

avcodec_alloc_context3

avcodec_parameters_to_context

avcodec_open2

avcodec_find_encoder

av_opt_set_int

sws_getContext

avformat_alloc_output_context2

avformat_new_stream

avcodec_parameters_from_context

avio_open

avformat_write_header

av_frame_alloc

av_frame_get_buffer

av_packet_alloc

av_read_frame

avcodec_send_packet

avcodec_receive_frame

sws_scale

av_rescale_q

avcodec_send_frame

avcodec_receive_packet

av_packet_rescale_ts

av_interleaved_write_frame

av_packet_unref

av_write_trailer

例子

cpp 复制代码
#include <iostream>
#include <memory>

// 使用 RAII 管理指针(可选,但推荐)
template<typename T, void(*Deleter)(T*)>
struct FFmpegResource {
    T* ptr = nullptr;
    FFmpegResource(T* p = nullptr) : ptr(p) {}
    ~FFmpegResource() { if (ptr) Deleter(ptr); }
};

using AVFormatContextPtr = FFmpegResource<AVFormatContext, avformat_close_input>;
using AVCodecContextPtr = FFmpegResource<AVCodecContext, avcodec_free_context>;
using SwsContextPtr = FFmpegResource<SwsContext, sws_freeContext>;
using AVFramePtr = FFmpegResource<AVFrame, av_frame_free>;
using AVPacketPtr = FFmpegResource<AVPacket, av_packet_free>;

int main() {
    AVFormatContext *srcCtx = nullptr;
    AVCodecContext *srcDecCtx = nullptr, *encCtx = nullptr;
    SwsContext *swsCtx = nullptr;
    AVFrame *decFrame = nullptr, *encFrame = nullptr;
    AVPacket *pkt = nullptr;
    AVFormatContext *outputCtx = nullptr;
    int ret = 0;

    // 错误处理标签
    #define CHECK_ERROR(cond, msg, cleanup_label) \
        if ((cond)) { \
            std::cerr << (msg) << ": " << av_err2str(ret) << std::endl; \
            goto cleanup_label; \
        }

    // ============ 打开输入文件 ============
    ret = avformat_open_input(&srcCtx, srcPath.toStdString().c_str(), nullptr, nullptr);
    CHECK_ERROR(ret < 0, "打开视频文件失败", cleanup);

    ret = avformat_find_stream_info(srcCtx, nullptr);
    CHECK_ERROR(ret < 0, "获取视频流信息失败", cleanup);

    // ============ 初始化视频解码器 ============
    const AVCodec *srcDec = nullptr;
    int streamIndex = av_find_best_stream(srcCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &srcDec, 0);
    CHECK_ERROR(streamIndex < 0, "查找视频流失败", cleanup);

    srcDecCtx = avcodec_alloc_context3(srcDec);
    CHECK_ERROR(!srcDecCtx, "分配解码器上下文失败", cleanup);

    ret = avcodec_parameters_to_context(srcDecCtx, srcCtx->streams[streamIndex]->codecpar);
    CHECK_ERROR(ret < 0, "拷贝解码器参数失败", cleanup);

    ret = avcodec_open2(srcDecCtx, srcDec, nullptr);
    CHECK_ERROR(ret < 0, "打开解码器失败", cleanup);

    // ============ 初始化视频编码器 ============
    const AVCodec *srcEnc = avcodec_find_encoder(srcCtx->streams[streamIndex]->codecpar->codec_id);
    CHECK_ERROR(!srcEnc, "查找编码器失败", cleanup);

    encCtx = avcodec_alloc_context3(srcEnc);
    CHECK_ERROR(!encCtx, "分配编码器上下文失败", cleanup);

    // 配置编码参数
    encCtx->width = width;
    encCtx->height = height;
    encCtx->pix_fmt = AV_PIX_FMT_YUV420P;
    encCtx->time_base = {1, 30};
    encCtx->gop_size = 12;
//    encCtx->bit_rate = 4000000; 不设置码率

    encCtx->profile = FF_PROFILE_H264_HIGH;
    encCtx->level = 40;
    encCtx->max_b_frames = 2;
    encCtx->color_range = AVCOL_RANGE_MPEG; // 颜色范围(tv)
    encCtx->color_primaries = AVCOL_PRI_BT709; // 颜色标准
    encCtx->color_trc = AVCOL_TRC_BT709;  // 颜色传输特性
    encCtx->colorspace = AVCOL_SPC_BT709; // 颜色空间

    // 设置CRF模式与参数调整
    encCtx->flags |= AV_CODEC_FLAG_QSCALE;// 启用量化参数控制
    av_opt_set_int(encCtx->priv_data, "crf", 18, AV_OPT_SEARCH_CHILDREN);// 0-51,18为视觉无损

    av_opt_set(encCtx->priv_data, "preset", "veryslow", AV_OPT_SEARCH_CHILDREN);  // 牺牲时间换取质量
//    av_opt_set(encCtx->priv_data, "tune", "film", AV_OPT_SEARCH_CHILDREN);  // 电影类用film,动画用animation


    ret = avcodec_open2(encCtx, srcEnc, nullptr);
    CHECK_ERROR(ret < 0, "打开编码器失败", cleanup);

    // ============ 创建缩放上下文 ============
    swsCtx = sws_getContext(/* 参数保持原逻辑 */);
    CHECK_ERROR(!swsCtx, "创建缩放上下文失败", cleanup);

    // ============ 准备输出文件 ============
    ret = avformat_alloc_output_context2(&outputCtx, nullptr, nullptr, destPath.toStdString().c_str());
    CHECK_ERROR(ret < 0, "创建输出上下文失败", cleanup);

    AVStream *outStream = avformat_new_stream(outputCtx, nullptr);
    CHECK_ERROR(!outStream, "创建输出流失败", cleanup);

    ret = avcodec_parameters_from_context(outStream->codecpar, encCtx);
    CHECK_ERROR(ret < 0, "拷贝编码器参数到输出流失败", cleanup);

    // 显式设置输出流时间基与编码器一致 
    outStream->time_base = encCtx->time_base;

    if (!(outputCtx->oformat->flags & AVFMT_NOFILE)) {
        ret = avio_open(&outputCtx->pb, destPath.toStdString().c_str(), AVIO_FLAG_WRITE);
        CHECK_ERROR(ret < 0, "打开输出文件失败", cleanup);
    }

    ret = avformat_write_header(outputCtx, nullptr);
    CHECK_ERROR(ret < 0, "写入文件头失败", cleanup);

    // ============ 帧处理循环 ============
    decFrame = av_frame_alloc();
    encFrame = av_frame_alloc();
    pkt = av_packet_alloc();
    CHECK_ERROR(!decFrame || !encFrame || !pkt, "分配帧/包失败", cleanup);

    while (av_read_frame(srcCtx, pkt) >= 0) {
        if (pkt->stream_index != streamIndex) {
            av_packet_unref(pkt);
            continue;
        }

         // 解码
        if ((ret = avcodec_send_packet(srcDecCtx, pkt)) < 0) {
            cout << "读取包失败: " << av_err2str(ret) << endl;
        }
        while (ret >= 0) {
            ret = avcodec_receive_frame(srcDecCtx, decFrame);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                break;
            } else if (ret < 0) {
                cout << "读取帧失败: " << av_err2str(ret) << endl;
            }
            cout << "解码帧 pts: " << decFrame->pts << endl;

            // 缩放
            sws_scale(swsCtx, decFrame->data, decFrame->linesize,
                      0, srcDecCtx->height, encFrame->data, encFrame->linesize);
            encFrame->pts = av_rescale_q(decFrame->pts, srcCtx->streams[streamIndex]->time_base, encCtx->time_base);

            // 编码
            AVPacket *encPkt = av_packet_alloc();
            if ((ret = avcodec_send_frame(encCtx, encFrame)) < 0) {
                cout << "发送帧到编码器失败: " << av_err2str(ret) << endl;
            }
            while (ret >= 0) {
                ret = avcodec_receive_packet(encCtx, encPkt);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                    break;
                } else if (ret < 0) {
                    cout << "编码器输出包失败: " << av_err2str(ret) << endl;
                }

                // 写入输出文件
                av_packet_rescale_ts(encPkt, encCtx->time_base, outStream->time_base);
                cout << "编码: " << encPkt->pts << endl;
                av_interleaved_write_frame(outputCtx, encPkt);
                av_packet_unref(encPkt);
            }

        }
    }

    // ============ 刷新编码器缓冲区  ============
    avcodec_send_frame(encCtx, nullptr); // 发送空帧刷新
    while (true) {
        AVPacket encPkt;
        av_init_packet(&encPkt);
        ret = avcodec_receive_packet(encCtx, &encPkt);
        if (ret == AVERROR_EOF || ret < 0) break;
        av_packet_rescale_ts(&encPkt, encCtx->time_base, outStream->time_base);
        av_interleaved_write_frame(outputCtx, &encPkt);
        av_packet_unref(&encPkt);
    }

    // ============ 写入文件尾 ============
    ret = av_write_trailer(outputCtx);
    CHECK_ERROR(ret < 0, "写入文件尾失败", cleanup);

// ============ 资源释放 ============
cleanup:
    avformat_close_input(&srcCtx);
    avcodec_free_context(&srcDecCtx);
    avcodec_free_context(&encCtx);
    sws_freeContext(swsCtx);
    av_frame_free(&decFrame);
    av_frame_free(&encFrame);
    av_packet_free(&pkt);
    if (outputCtx && !(outputCtx->oformat->flags & AVFMT_NOFILE)) {
        avio_closep(&outputCtx->pb);
    }
    avformat_free_context(outputCtx);
    return ret;
}
相关推荐
小gpt&4 小时前
02 windows qt配置ffmpeg开发环境搭建
windows·qt·ffmpeg
鹅毛在路上了6 小时前
vlc录制的视频伪时长修复方法
ffmpeg·音视频
CHHC188010 小时前
工业相机视频播放(RTSP)
ffmpeg·推流·工业相机·rtsp
plmm烟酒僧2 天前
编译支持 RKmpp 和 RGA 的 ffmpeg 源码
ffmpeg·视频编解码·rkmpp·硬件加速·vpu·rga
将进酒杯莫停3 天前
ffmpeg(4)-Windows环境下编译ffmpeg
ffmpeg
虾球xz3 天前
游戏引擎学习第148天
学习·ffmpeg·游戏引擎
看到我请叫我去学java吖3 天前
MacOS安装FFmpeg和FFprobe
macos·ffmpeg
小gpt&3 天前
01 音视频知识学习(视频)
c++·qt·学习·ffmpeg·音视频
Yeauty4 天前
三分钟掌握音视频处理 | 在 Rust 中优雅地集成 FFmpeg
rust·ffmpeg·音视频