FFmpeg开发学习:音视频封装

1.基本流程

1.输入参数

输出文件路径 char *output

视频编码参数 AVCodecParameters *video_par

音频编码参数 AVCodecParameters *audio_par

数据包 AVPacket *packets[]

2.封装流程

(1)创建输出的上下文AVFormatContext指针

cpp 复制代码
AVFormatContext *out_fmt_ctx = nullptr;
    AVStream *video_stream = nullptr;
    AVStream *audio_stream = nullptr;

    //创建输出的上下文以及添加视频流和音频流
    if(avformat_alloc_output_context2(&out_fmt_ctx, nullptr, nullptr, output_filename) < 0) {
        fprintf(stderr, "falied to create output context\n");
        return;
    }

(2)添加AVStream的视频流和音频流

cpp 复制代码
if(video_par) {
        video_stream = avformat_new_stream(out_fmt_ctx, nullptr);
        avcodec_parameters_copy(video_stream->codecpar, video_par);
        video_stream->time_base = (AVRational){1, 1000};
    }

    if(audio_par) {
        audio_stream = avformat_new_stream(out_fmt_ctx, nullptr);
        avcodec_parameters_copy(audio_stream->codecpar, audio_par);
    }

(3)打开输出的目标文件

cpp 复制代码
//打开输出的文件
    if(!(out_fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
        if(avio_open(&out_fmt_ctx->pb, output_filename, AVIO_FLAG_WRITE) < 0) {
            fprintf(stderr, "can not open output file.\n" );
            return;
        }
    }

(4)写入文件头

cpp 复制代码
//写入头文件
    if(avformat_write_header(out_fmt_ctx, nullptr) < 0) {
        fprintf(stderr, "error occurred when writing header.\n");
    }

(5)将所有的AVPacket写入AVStream

cpp 复制代码
//写入所有packet
    for(int i=0; i<packet_count; i++) {
        AVPacket *pkt = packets[i];

        //根据类型设置正确的time_base和stream_index
        AVStream *out_stream = (pkt->stream_index==0) ? video_stream : audio_stream;
        AVRational in_tb = (pkt->stream_index==0) ? (AVRational){1, 1000} : (AVRational){1, 48000};

        //转换时间戳
        av_packet_rescale_ts(pkt, in_tb, out_stream->time_base);
        pkt->stream_index = out_stream->index;

        if(av_interleaved_write_frame(out_fmt_ctx, pkt) < 0) {
            fprintf(stderr, "failed to write packet.\n");
        }

        av_packet_unref(pkt);
    }

(5.1)设置正确的time_base和stream_index

cpp 复制代码
//根据类型设置正确的time_base和stream_index
        AVStream *out_stream = (pkt->stream_index==0) ? video_stream : audio_stream;
        AVRational in_tb = (pkt->stream_index==0) ? (AVRational){1, 1000} : (AVRational){1, 48000};

(6)转换时间戳

cpp 复制代码
//转换时间戳
        av_packet_rescale_ts(pkt, in_tb, out_stream->time_base);
        pkt->stream_index = out_stream->index;

(7)写入文件尾

cpp 复制代码
    av_write_trailer(out_fmt_ctx);

(8)清理指针

cpp 复制代码
if(!(out_fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
        avio_closep(&out_fmt_ctx->pb);
    }
    avformat_free_context(out_fmt_ctx);

基本上可以理解为,对于音视频封装的输入数据,需要视频编码参数以及音频编码参数两个参数,以及相关的数据帧

使用ffmpeg进行封装时,需要利用api写入相关格式的文件头以及文件尾,然后按顺序处理数据帧。

2.程序示例

模拟256个数据帧的音视频数据,并封装为mp4文件

cpp 复制代码
extern "C"{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <libavutil/avutil.h>
#include <libavutil/mem.h>
}

#include <cstdio>
#include <cstdlib>
#include <cstring>


inline void mux_audio_video(const char *output_filename,
    AVCodecParameters *video_par,
    AVCodecParameters *audio_par,
    AVPacket *packets[], int packet_count) {

    AVFormatContext *out_fmt_ctx = nullptr;
    AVStream *video_stream = nullptr;
    AVStream *audio_stream = nullptr;

    //创建输出的上下文以及添加视频流和音频流
    if(avformat_alloc_output_context2(&out_fmt_ctx, nullptr, nullptr, output_filename) < 0) {
        fprintf(stderr, "falied to create output context\n");
        return;
    }

    if(video_par) {
        video_stream = avformat_new_stream(out_fmt_ctx, nullptr);
        avcodec_parameters_copy(video_stream->codecpar, video_par);
        video_stream->time_base = (AVRational){1, 1000};
    }

    if(audio_par) {
        audio_stream = avformat_new_stream(out_fmt_ctx, nullptr);
        avcodec_parameters_copy(audio_stream->codecpar, audio_par);
    }

    //打开输出的文件
    if(!(out_fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
        if(avio_open(&out_fmt_ctx->pb, output_filename, AVIO_FLAG_WRITE) < 0) {
            fprintf(stderr, "can not open output file.\n" );
            return;
        }
    }

    //写入头文件
    if(avformat_write_header(out_fmt_ctx, nullptr) < 0) {
        fprintf(stderr, "error occurred when writing header.\n");
    }

    //写入所有packet
    for(int i=0; i<packet_count; i++) {
        AVPacket *pkt = packets[i];

        //根据类型设置正确的time_base和stream_index
        AVStream *out_stream = (pkt->stream_index==0) ? video_stream : audio_stream;
        AVRational in_tb = (pkt->stream_index==0) ? (AVRational){1, 1000} : (AVRational){1, 48000};

        //转换时间戳
        av_packet_rescale_ts(pkt, in_tb, out_stream->time_base);
        pkt->stream_index = out_stream->index;

        if(av_interleaved_write_frame(out_fmt_ctx, pkt) < 0) {
            fprintf(stderr, "failed to write packet.\n");
        }

        av_packet_unref(pkt);
    }

    //写入尾部信息
    av_write_trailer(out_fmt_ctx);

    //清理
    if(!(out_fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
        avio_closep(&out_fmt_ctx->pb);
    }
    avformat_free_context(out_fmt_ctx);
    return;


}
cpp 复制代码
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <libavutil/avutil.h>
#include <libavutil/mem.h>
}

#include <cstdio>
#include <cstdlib>
#include <cstring>

#include "function/mux_demux_case.h"
#define FAKE_PACKET_COUNT 10

#include "function/libavformat_case.h"


int main() {
    av_log_set_level(AV_LOG_INFO);

    const char *filename = "D:/Cpp/FFmpegUsingCase/test_output.mp4";

    // 初始化视频参数
    AVCodecParameters *vpar = avcodec_parameters_alloc();
    vpar->codec_type = AVMEDIA_TYPE_VIDEO;
    vpar->codec_id = AV_CODEC_ID_H264;
    vpar->width = 1280;
    vpar->height = 720;
    vpar->format = AV_PIX_FMT_YUV420P;
    vpar->bit_rate = 400000;

    // 初始化音频参数
    AVCodecParameters *apar = avcodec_parameters_alloc();
    apar->codec_type = AVMEDIA_TYPE_AUDIO;
    apar->codec_id = AV_CODEC_ID_AAC;
    apar->sample_rate = 48000;
    apar->ch_layout.nb_channels = 2;
    apar->frame_size = 1024;
    // apar->channel_layout = AV_CH_LAYOUT_STEREO;
    apar->ch_layout.order = AV_CHANNEL_ORDER_NATIVE;
    apar->ch_layout.u.mask = AV_CH_LAYOUT_STEREO;
    apar->format = AV_SAMPLE_FMT_FLTP;
    apar->bit_rate = 128000;

    // 模拟一些 AVPacket
    AVPacket *packet_array[FAKE_PACKET_COUNT];
    for (int i = 0; i < FAKE_PACKET_COUNT; i++) {
        AVPacket *pkt = av_packet_alloc();
        pkt->data = (uint8_t *)av_malloc(100); // 模拟数据内容
        pkt->size = 100;
        pkt->pts = i * 40;
        pkt->dts = i * 40;
        pkt->duration = 40;
        pkt->stream_index = i % 2; // 偶数为视频帧,奇数为音频帧
        if (i % 5 == 0)
            pkt->flags |= AV_PKT_FLAG_KEY;
        packet_array[i] = pkt;
    }

    // 封装并输出到文件
    mux_audio_video(filename, vpar, apar, packet_array, FAKE_PACKET_COUNT);

    printf("finished: %s\n", filename);

    // 清理参数结构体
    avcodec_parameters_free(&vpar);
    avcodec_parameters_free(&apar);

    printAVFormatContextCase(filename);

    return 0;
}
相关推荐
智联视频超融合平台1 小时前
智能巡检机器人:2025年企业安全运维的“数字哨兵“
运维·安全·机器人·音视频·实时音视频·视频编解码
mex_wayne1 小时前
强化学习课程:stanford_cs234 学习笔记(2)introduction to RL
笔记·学习
许野平1 小时前
Manim 输出视频尺寸设置
音视频·动画·视频·manim
hanpfei1 小时前
PipeWire 音频设计与实现分析二——SPA 插件
算法·音视频
2401_872487882 小时前
网络安全之前端学习(css篇2)
前端·css·学习
梁下轻语的秋缘2 小时前
每日c/c++题 备战蓝桥杯(二分答案模版)
c语言·c++·学习·算法·蓝桥杯
修修修也3 小时前
【算法手记7】拼三角
数据结构·学习·算法·刷题
落笔太慌张~3 小时前
[FGPA基础学习]分秒计数器的制作
学习·fpga开发
知识分享小能手3 小时前
CSS3学习教程,从入门到精通, CSS3 变形效果(2D 和 3D)的详细语法知识点及案例代码(22)
前端·javascript·css·学习·3d·css3·html5
白夜易寒4 小时前
Docker学习之服务编排(day9)
学习·docker·eureka