音视频ffmpeg

完整流程代码框架

步骤 1:初始化 FFmpeg 并打开输入文件(解封装准备)
cpp 复制代码
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>

int main() {
    // 初始化FFmpeg(旧版本需要,新版本可省略)
    av_register_all();
    avcodec_register_all();

    // 输入文件路径
    const char* input_path = "input.mp4";
    AVFormatContext* in_fmt_ctx = NULL;

    // 打开输入文件,获取封装格式上下文
    if (avformat_open_input(&in_fmt_ctx, input_path, NULL, NULL) != 0) {
        fprintf(stderr, "无法打开输入文件\n");
        return -1;
    }

    // 读取流信息(获取音频流、视频流的索引)
    if (avformat_find_stream_info(in_fmt_ctx, NULL) < 0) {
        fprintf(stderr, "无法获取流信息\n");
        avformat_close_input(&in_fmt_ctx);
        return -1;
    }

    // 查找音频流和视频流的索引
    int audio_stream_idx = -1, video_stream_idx = -1;
    for (int i = 0; i < in_fmt_ctx->nb_streams; i++) {
        if (in_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            audio_stream_idx = i;
        } else if (in_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream_idx = i;
        }
    }
    if (audio_stream_idx == -1 || video_stream_idx == -1) {
        fprintf(stderr, "未找到音视频流\n");
        avformat_close_input(&in_fmt_ctx);
        return -1;
    }
步骤 2:初始化解码器(解码准备)

需要分别为音频和视频初始化解码器:

cpp 复制代码
    // 初始化视频解码器
    AVCodecParameters* video_codecpar = in_fmt_ctx->streams[video_stream_idx]->codecpar;
    const AVCodec* video_decoder = avcodec_find_decoder(video_codecpar->codec_id);
    AVCodecContext* video_dec_ctx = avcodec_alloc_context3(video_decoder);
    avcodec_parameters_to_context(video_dec_ctx, video_codecpar); // 复制流参数到解码器上下文
    if (avcodec_open2(video_dec_ctx, video_decoder, NULL) < 0) {
        fprintf(stderr, "无法打开视频解码器\n");
        // 错误处理...
    }

    // 初始化音频解码器(类似视频)
    AVCodecParameters* audio_codecpar = in_fmt_ctx->streams[audio_stream_idx]->codecpar;
    const AVCodec* audio_decoder = avcodec_find_decoder(audio_codecpar->codec_id);
    AVCodecContext* audio_dec_ctx = avcodec_alloc_context3(audio_decoder);
    avcodec_parameters_to_context(audio_dec_ctx, audio_codecpar);
    if (avcodec_open2(audio_dec_ctx, audio_decoder, NULL) < 0) {
        fprintf(stderr, "无法打开音频解码器\n");
        // 错误处理...
    }
步骤 3:初始化编码器和输出上下文(复用准备)

假设输出为output.mkv,重新编码为 H.265(视频)和 OPUS(音频):

cpp 复制代码
    // 输出文件路径
    const char* output_path = "output.mkv";
    AVFormatContext* out_fmt_ctx = NULL;
    avformat_alloc_output_context2(&out_fmt_ctx, NULL, NULL, output_path); // 自动推断输出格式

    // 创建输出视频流(编码为H.265)
    const AVCodec* video_encoder = avcodec_find_encoder(AV_CODEC_ID_HEVC); // H.265编码器
    AVStream* out_video_stream = avformat_new_stream(out_fmt_ctx, video_encoder);
    AVCodecContext* video_enc_ctx = avcodec_alloc_context3(video_encoder);
    // 配置编码器参数(分辨率、帧率、码率等,参考输入视频)
    video_enc_ctx->width = video_dec_ctx->width;
    video_enc_ctx->height = video_dec_ctx->height;
    video_enc_ctx->pix_fmt = video_encoder->pix_fmts[0]; // 编码器支持的像素格式
    video_enc_ctx->time_base = (AVRational){1, 30}; // 30fps
    out_video_stream->time_base = video_enc_ctx->time_base;
    if (avcodec_open2(video_enc_ctx, video_encoder, NULL) < 0) { /* 错误处理 */ }
    avcodec_parameters_from_context(out_video_stream->codecpar, video_enc_ctx);

    // 创建输出音频流(编码为OPUS)
    const AVCodec* audio_encoder = avcodec_find_encoder(AV_CODEC_ID_OPUS);
    AVStream* out_audio_stream = avformat_new_stream(out_fmt_ctx, audio_encoder);
    AVCodecContext* audio_enc_ctx = avcodec_alloc_context3(audio_encoder);
    // 配置编码器参数(采样率、声道数等,参考输入音频)
    audio_enc_ctx->sample_rate = audio_dec_ctx->sample_rate;
    audio_enc_ctx->channels = audio_dec_ctx->channels;
    audio_enc_ctx->sample_fmt = audio_encoder->sample_fmts[0];
    audio_enc_ctx->time_base = (AVRational){1, audio_enc_ctx->sample_rate};
    out_audio_stream->time_base = audio_enc_ctx->time_base;
    if (avcodec_open2(audio_enc_ctx, audio_encoder, NULL) < 0) { /* 错误处理 */ }
    avcodec_parameters_from_context(out_audio_stream->codecpar, audio_enc_ctx);

    // 打开输出文件(写入头部)
    if (!(out_fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
        if (avio_open(&out_fmt_ctx->pb, output_path, AVIO_FLAG_WRITE) < 0) {
            fprintf(stderr, "无法打开输出文件\n");
            // 错误处理...
        }
    }
    avformat_write_header(out_fmt_ctx, NULL); // 写入文件头部
步骤 4:读取数据包→解码→编码→写入输出(核心循环)
cpp 复制代码
    AVPacket* pkt = av_packet_alloc(); // 压缩数据包(输入)
    AVFrame* frame = av_frame_alloc(); // 原始帧(解码后)
    AVPacket* enc_pkt = av_packet_alloc(); // 编码后的数据包(输出)

    // 循环读取输入文件的数据包
    while (av_read_frame(in_fmt_ctx, pkt) >= 0) {
        if (pkt->stream_index == video_stream_idx) {
            // 视频包:解码→编码→写入
            // 1. 解码
            if (avcodec_send_packet(video_dec_ctx, pkt) < 0) { /* 错误处理 */ }
            while (avcodec_receive_frame(video_dec_ctx, frame) >= 0) {
                // 2. 编码(注意:可能需要转换像素格式,这里简化)
                frame->pts = av_frame_get_best_effort_timestamp(frame); // 设置时间戳
                if (avcodec_send_frame(video_enc_ctx, frame) < 0) { /* 错误处理 */ }
                while (avcodec_receive_packet(video_enc_ctx, enc_pkt) >= 0) {
                    // 转换时间戳(从编码器时间基→输出流时间基)
                    av_packet_rescale_ts(enc_pkt, video_enc_ctx->time_base, out_video_stream->time_base);
                    enc_pkt->stream_index = out_video_stream->index;
                    // 3. 写入输出文件
                    av_interleaved_write_frame(out_fmt_ctx, enc_pkt);
                    av_packet_unref(enc_pkt); // 释放编码包
                }
            }
        } else if (pkt->stream_index == audio_stream_idx) {
            // 音频包:解码→编码→写入(逻辑同视频,略)
            // ...
        }
        av_packet_unref(pkt); // 释放输入包
    }

    // 刷新编码器(处理剩余帧)
    avcodec_send_frame(video_enc_ctx, NULL);
    while (avcodec_receive_packet(video_enc_ctx, enc_pkt) >= 0) { /* 写入输出 */ }
    // 音频同理...
步骤 5:释放资源
cpp 复制代码
    // 写入文件尾部
    av_write_trailer(out_fmt_ctx);

    // 释放所有上下文和内存
    avformat_close_input(&in_fmt_ctx);
    avcodec_free_context(&video_dec_ctx);
    avcodec_free_context(&audio_dec_ctx);
    avcodec_free_context(&video_enc_ctx);
    avcodec_free_context(&audio_enc_ctx);
    if (!(out_fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
        avio_closep(&out_fmt_ctx->pb);
    }
    avformat_free_context(out_fmt_ctx);
    av_frame_free(&frame);
    av_packet_free(&pkt);
    av_packet_free(&enc_pkt);

    return 0;
}

关键说明

  1. 格式转换 :解码后的 YUV/PCM 可能与编码器要求的格式不匹配(如像素格式、采样格式),需用sws_scale(视频)或swr_convert(音频)转换。

  2. 时间戳同步 :通过av_packet_rescale_ts转换时间基,确保音视频时序一致(核心是PTS/DTS的正确传递)。

  3. 错误处理:实际代码需更完善的错误检查(如内存分配失败、编解码失败等)。

  4. 编译命令 :需链接 FFmpeg 库,例如:

    bash

    复制代码
    gcc -o av_process av_process.c -lavformat -lavcodec -lavutil -lswscale -lswresample

通过这个框架,就能实现 "输入文件→解封装→解码→重新编码→复用→输出文件" 的完整流程。如需播放(而非输出文件),则在解码后将原始帧(YUV/PCM)交给渲染层(如 SDL 显示视频、播放音频)即可

相关推荐
彷徨而立18 小时前
【FFmpeg】对比 d3d12va 、d3d11va、dxva2 这三种视频硬解方案
ffmpeg
jjjxxxhhh12321 小时前
【学习】USB摄像头 -> FFmpeg -> H264 -> AI模型
人工智能·学习·ffmpeg
给大佬递杯卡布奇诺21 小时前
FFmpeg 基本API avformat_open_input函数内部调用流程分析
c++·ffmpeg·音视频
aqi001 天前
FFmpeg开发笔记(八十三)国产的视频裁剪框架AndroidVideoTrimmer
android·ffmpeg·音视频·流媒体
彷徨而立2 天前
【win32】FFmpeg 硬件解码器
windows·ffmpeg
dddddppppp1232 天前
linux sdl图形编程之helloworld.
linux·运维·ffmpeg
给大佬递杯卡布奇诺2 天前
FFmpeg 基本API avio_read函数内部调用流程分析
c++·ffmpeg·音视频
华仔啊3 天前
别再用在线工具转GIF了!FFmpeg实现高清无损转换,这才是真优雅
ffmpeg