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;
}
相关推荐
励志码农6 分钟前
JavaWeb 30 天入门:第二十三天 —— 监听器(Listener)
java·开发语言·spring boot·学习·servlet
DisonTangor12 分钟前
字节开源 OneReward: 通过多任务人类偏好学习实现统一掩模引导的图像生成
学习·ai作画·开源·aigc
黎宇幻生1 小时前
Java全栈学习笔记33
java·笔记·学习
2501_926227942 小时前
.Net程序员就业现状以及学习路线图(五)
学习·.net
siy23335 小时前
[c语言日记] 数组的一种死法和两种用法
c语言·开发语言·笔记·学习·链表
Zender Han7 小时前
Flutter 视频播放器——flick_video_player 介绍与使用
android·flutter·ios·音视频
max5006007 小时前
实时多模态电力交易决策系统:设计与实现
图像处理·人工智能·深度学习·算法·音视频
在路上`7 小时前
前端学习之后端java小白(三)-sql外键约束一对多
java·前端·学习
尚久龙8 小时前
安卓学习 之 用户登录界面的简单实现
android·运维·服务器·学习·手机·android studio·安卓
yb0os18 小时前
RPC实战和核心原理学习(一)----基础
java·开发语言·网络·数据结构·学习·计算机·rpc