FFmpeg开发学习:音视频封装

news2025/3/31 9:58:15

1.基本流程

注意:在新版本的ffmpeg中,已经不需要使用av_register_all注册api

1.输入参数

输出文件路径 char *output
视频编码参数 AVCodecParameters *video_par
音频编码参数 AVCodecParameters *audio_par
数据包 AVPacket *packets[]

2.封装流程

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

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的视频流和音频流

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)打开输出的目标文件

//打开输出的文件
    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)写入文件头

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

(5)将所有的AVPacket写入AVStream

//写入所有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

//根据类型设置正确的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)转换时间戳

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

(7)写入文件尾

    av_write_trailer(out_fmt_ctx);

(8)清理指针

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文件

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;


}
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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2323259.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

hackmyvm-reversteg

arp-scan -l nmap -sS -v 192.168.222.45 在源码中可以看到 根据下面的提示可以猜测117db0148dc179a2c2245c5a30e63ab0是一个图像文件 将图片下载到本地 隐写术 在两张图片上使用strings,发现有一些可打印的字符串 strings 117db0148dc179a2c2245c5a30e63ab0.jpg base64解码…

UE4学习笔记 FPS游戏制作17 让机器人持枪 销毁机器人时也销毁机器人的枪 让机器人射击

添加武器插槽 打开机器人的Idle动画&#xff0c;方便查看武器位置 在动画面板里打开骨骼树&#xff0c;找到右手的武器节点&#xff0c;右键添加一个插槽&#xff0c;重命名为RightWeapon&#xff0c;右键插槽&#xff0c;添加一个预览资产&#xff0c;选择Rifle&#xff0c;根…

设计模式(创建型)-建造者模式

定义 建造者模式&#xff08;Builder Pattern&#xff09;是一种创建型设计模式&#xff0c;它将一个复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。该模式允许通过多个简单的步骤逐步构建出一个复杂的对象&#xff0c;用户只需指定复杂对象…

Git 之配置ssh

1、打开 Git Bash 终端 2、设置用户名 git config --global user.name tom3、生成公钥 ssh-keygen -t rsa4、查看公钥 cat ~/.ssh/id_rsa.pub5、将查看到的公钥添加到不同Git平台 6、验证ssh远程连接git仓库 ssh -T gitgitee.com ssh -T gitcodeup.aliyun.com

黑马点评项目

遇到问题&#xff1a; 登录流程 session->JWT->SpringSession->tokenRedis &#xff08;不需要改进为SpringSession&#xff0c;token更广泛&#xff0c;移动端或者前后端分离都可以用&#xff09; SpringSession配置为redis模式后&#xff0c;redis相当于分布式se…

【AVRCP】AVRCP核心术语解析

目录 一、协议核心术语&#xff1a;架构的基石 1.1 音视频控制协议簇&#xff08;AVRCP 生态链&#xff09; 1.2 数据传输协议&#xff08;L2CAP 核心术语&#xff09; 二、设备架构术语&#xff1a;角色与交互 2.1 设备角色模型&#xff08;CT/TG 二元架构&#xff09; …

【弹性计算】异构计算云服务和 AI 加速器(四):FPGA 虚拟化技术

异构计算云服务和 AI 加速器&#xff08;四&#xff09;&#xff1a;FPGA 虚拟化技术 &#x1f680; FPGA&#xff08;Field-Programmable Gate Array&#xff0c;现场可编程门阵列&#xff09;是一种可重构的半导体芯片&#xff0c;允许用户根据需要动态配置硬件逻辑&#xff…

编译原理——自底向上语法优先分析

文章目录 自底向上优先分析概述一、自底向上优先分析概述二、简单优先分析法&#xff08;一&#xff09;优先关系定义&#xff08;二&#xff09;简单优先文法的定义&#xff08;三&#xff09;简单优先分析法的操作步骤 三、算法优先分析法&#xff08;一&#xff09;直观算符…

nuxt3网站文章分享微信 ,QQ功能

1.安装 npm install qrcode --save-dev 2.组件使用 <div class"share"><div style"line-height: 69px; color: #fff;width: 100px;"><p style"text-align: center;">分享:</p></div><div click"shareToMi…

STM32F103_LL库+寄存器学习笔记07 - 串口接收缓冲区非空中断

导言 上一章节《STM32F103_LL库寄存器学习笔记06 - 梳理串口与串行发送“Hello,World"》梳理完USART的基本设置与发送字符串“Hello,World"&#xff0c;接着梳理接收缓冲区非空中断。 实用的串口接收程序都会使用中断方式&#xff0c;不会使用轮询方式。最主要的原因…

生物中心论

Robert Lanza的“生物中心论”&#xff08;Biocentrism&#xff09;是一种以生命和意识为核心的宇宙观&#xff0c;试图颠覆传统科学对时间、空间和物质的理解。 一、核心观点 意识创造宇宙 生物中心论认为&#xff0c;宇宙的存在依赖于观察者的意识。传统科学将宇宙视为独立实…

Spring AOP:面向切面编程的探索之旅

目录 1. AOP 2. Spring AOP 快速入门 2.1 引入 Spring AOP 依赖 2.2 Spring AOP 简单使用 3. Spring AOP 核心概念 3.1 切点 3.1.1 Pointcut 定义切点 3.1.2 切点表达式 3.1.2.1 execution 表达式 3.1.2.2 annotation 表达式 3.2 连接点 3.3 通知(Advice) 3.3.1 通…

使用QT画带有透明效果的图

分辨率&#xff1a;24X24 最大圆 代码: #include <QApplication> #include <QImage> #include <QPainter>int main(int argc, char *argv[]) {QImage image(QSize(24,24),QImage::Format_ARGB32);image.fill(QColor(0,0,0,0));QPainter paint(&image);…

RocketMQ可视化工具使用 - Dashboard(保姆级教程)

1、github拉取代码&#xff0c;地址&#xff1a; https://github.com/apache/rocketmq-dashboard 2、指定Program arguments&#xff0c;本地启动工程 勾上这个Program arguments&#xff0c;会出现多一个对应的框 写入参数 --server.port1280 --rocketmq.config.namesrvAddr…

用Unity实现UDP客户端同步通信

制作UDPNetMgr网络管理模块 这段代码定义了一个名为UDPNetMgr的 Unity 脚本类&#xff0c;用于管理 UDP 网络通信&#xff0c;它作为单例存在&#xff0c;在Awake方法中创建收发消息的线程&#xff0c;Update方法处理接收到的消息&#xff1b;StartClient方法启动客户端连接&a…

pandoc安装及基础使用

pandoc安装 访问pandoc tags,切换至想要安装的版本&#xff0c;本次安装3.6.4 下载windows版本 下载texlive镜像&#xff0c;将文件转换成pdf需要用到 点开后会进入最近的镜像网站 下载完成后解压iso文件&#xff0c;以管理员身份运行install-tl-windows.bat&#xff…

3.27学习总结 算法题

自己用c语言做的&#xff0c;不尽如意 后面看了题解&#xff0c;用的是c&#xff0c;其中string 变量和字符串拼接感觉比c方便好多&#xff0c;可以用更少的代码实现更好的效果&#xff0c;打算之后去学习c&#xff0c;用c写算法。 递归&#xff0c;不断输入字符&#xff0c;…

案例分享|树莓派媒体播放器,重构商场广告的“黄金三秒”

研究显示&#xff0c;与传统户外广告相比&#xff0c;数字户外广告在消费者心中的记忆率提高了17%&#xff0c;而动态户外广告更是能提升16%的销售业绩&#xff0c;整体广告效率提升了17%。这一显著优势&#xff0c;使得越来越多资源和技术流入数字广告行业。 户外裸眼3D广告 无…

Redisson - 分布式锁和同步器

文章目录 锁&#xff08;Lock&#xff09;公平锁&#xff08;Fair Lock&#xff09;联锁&#xff08;MultiLock&#xff09;红锁&#xff08;RedLock&#xff09; 【已废弃】读写锁&#xff08;ReadWriteLock&#xff09;信号量&#xff08;Semaphore&#xff09;可过期许可信号…

Zustand 状态管理:从入门到实践

Zustand 状态管理&#xff1a;从入门到实践 Zustand 是一个轻量、快速且灵活的 React 状态管理库。它基于 Hooks API&#xff0c;提供了简洁的接口来创建和使用状态&#xff0c;同时易于扩展和优化。本文将通过一个 TODO 应用实例带你快速入门 Zustand&#xff0c;并探讨其核心…