音视频-H264编码封装- MP4格式转Annex B格式

目录

1:H264语法结构回顾

2:H264编码补充介绍

3:MP4模式转Annex B模式输出到文件示例


1:H264语法结构回顾

在之前文章里介绍过H264的语法结构。

传送门: 视音频-H264 编码NALU语法结构简介 

2:H264编码补充介绍

   H.264视频编码标准中两种常见的封装方式:annexb模式和mp4模式。

  1. annexb模式

    • 这是传统的封装模式,其中每个视频帧的数据(NALUnit)在传输时以0x000001或0x0000001作为分隔符。
    • SPS(Sequence Parameter Set)和PPS(Picture Parameter Set)等关键信息通常会周期性地在视频流内部重复,特别是在关键帧(I帧)之前。
    • 这种模式下,视频帧的数据相对独立,不依赖于容器格式。
  2. mp4模式

    • 在这种模式下,SPS、PPS和其他相关信息被封装到了容器(如MP4、MKV)中,而不是直接在视频流内部传输。
    • 每个视频帧之前通常有一个4字节的长度字段,用于指示下一个视频帧的长度。
    • 在这种模式下,视频帧的数据是按照容器格式进行封装和传输的。

因为一些解码器只支持annexb模式,所以有时候需要将mp4格式的视频数据转换为annexb模式。在FFmpeg中,可以使用h264_mp4toannexb_filter过滤器来进行这种转换。

实现大概流程如下:

3:MP4模式转Annex B模式输出到文件示例

(填充sps pps 到H264头部信息 并输出H264文件)

        这段代码的主要功能是从MP4文件中提取H264视频流,使用bitstream过滤器将H264从MP4格式转换为AnnexB格式,并将转换后的数据写入到一个新的文件中。代码使用了FFmpeg库中的函数来处理媒体文件和Packet。程序首先查找输入媒体文件中的视频流,然后读取每个Packet,如果Packet属于视频流,则使用bitstream过滤器进行处理,并将处理后的数据包写入到输出文件中。如果出错或到达文件末尾,则退出循环并释放所有资源。

#include <stdio.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libavutil/log.h>

// 定义一个全局的缓冲区,用于存储错误信息
static char err_buff[128] = {0};

// 函数:获取错误信息
// 参数:errnum - 错误码
// 返回值:指向错误信息字符串的指针
static char* av_get_error(int errnum)
{
    // 使用av_strerror函数将错误码转换为可读的错误信息,并存储在err_buff中
    av_strerror(errnum, err_buff, 128);
    return err_buff;
}

int main(int argc, char **argv)
{
    // 定义FFmpeg的格式上下文,用于存储输入文件的多媒体流信息
    AVFormatContext *ifmt_ctx = NULL;
    // 存储视频流的索引
    int             videoindex = -1;
    // 定义AVPacket,用于存储编解码数据包
    AVPacket        *pkt = NULL;
    // 用于存储函数调用的返回值
    int             ret = -1;
    // 标记文件是否读取结束
    int             file_end = 0;

    // 检查命令行参数数量,至少需要输入文件和输出文件两个参数
    if(argc < 3)
    {
        printf("usage: %s input outfile\n", argv[0]);
        return -1;
    }
    // 打开输出文件
    FILE *outfp=fopen(argv[2],"wb");
    printf("in:%s out:%s\n", argv[1], argv[2]);

    // 分配格式上下文
    ifmt_ctx = avformat_alloc_context();
    if (!ifmt_ctx)
    {
        printf("Could not allocate context.\n");
        return -1;
    }

    // 打开输入文件,准备读取多媒体流
    ret = avformat_open_input(&ifmt_ctx,argv[1], NULL, NULL);
    if(ret != 0)
    {
        printf("avformat_open_input: %s\n", av_get_error(ret));
        return -1;
    }

    // 扫描文件,直到找到所有流的信息
    ret = avformat_find_stream_info(ifmt_ctx, NULL);
    if(ret < 0)
    {
        printf("avformat_find_stream_info: %s\n", av_get_error(ret));
        avformat_close_input(&ifmt_ctx);
        return -1;
    }

    // 查找最佳的视频流
    videoindex = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if(videoindex == -1)
    {
        avformat_close_input(&ifmt_ctx);
        return -1;
    }

    // 分配一个AVPacket
    pkt = av_packet_alloc();
    av_init_packet(pkt);

    // 获取H.264 MP4到AnnexB比特流过滤器
    const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb");
    // 分配一个比特流过滤器上下文
    AVBSFContext *bsf_ctx = NULL;
    av_bsf_alloc(bsfilter, &bsf_ctx);
    // 将视频流的编解码参数复制到过滤器的输入参数中
    avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar);
    // 初始化比特流过滤器
    av_bsf_init(bsf_ctx);

    // 循环读取数据包,直到文件结束
    file_end = 0;
    while (0 == file_end)
    {
        // 从格式上下文中读取一个数据包
        if((ret = av_read_frame(ifmt_ctx, pkt)) < 0)
        {
            // 如果没有更多的数据包,则标记文件结束
            file_end = 1;
            printf("read end: ret:%d\n", ret);
        }
        // 如果读取到的数据包属于视频流
        if(ret == 0 && pkt->stream_index == videoindex)
        {
            // 存储原始数据包的大小
            int input_size = pkt->size;
            // 计数器,用于统计输出的数据包数量
            int out_pkt_count = 0;
            // 通过比特流过滤器发送数据包
            if (av_bsf_send_packet(bsf_ctx, pkt) != 0)
            {
                av_packet_unref(pkt);
                continue;
            }
            av_packet_unref(pkt); // 释放pkt,因为过滤器会使用它
            // 从过滤器接收转换后的数据包,并写入输出文件
            while(av_bsf_receive_packet(bsf_ctx, pkt) == 0)
            {
                out_pkt_count++;
                // 将数据包的内容写入输出文件
                size_t size = fwrite(pkt->data, 1, pkt->size, outfp);
                if(size != pkt->size)
                {
                    printf("fwrite failed-> write:%u, pkt_size:%u\n", size, pkt->size);
                }
                av_packet_unref(pkt); // 释放数据包
            }
            // 如果原始数据包较大,但是只得到了一个输出数据包,打印警告信息
            if(out_pkt_count >= 2)
            {
                printf("curent pkt(size:%d), only get 1 out pkt, get %d pkts\n",
                       input_size, out_pkt_count);
            }
        }
        else
        {
            // 如果读取到的数据包不是视频流,则释放它
            if(ret == 0)
                av_packet_unref(pkt);
        }
    }
    // 关闭输出文件
    if(outfp)
        fclose(outfp);
    // 释放比特流过滤器上下文
    if(bsf_ctx)
        av_bsf_free(&bsf_ctx);
    // 释放AVPacket
    if(pkt)
        av_packet_free(&pkt);
    // 关闭输入的格式上下文
    if(ifmt_ctx)
        avformat_close_input(&ifmt_ctx);
    // 打印完成信息
    printf("finish\n");

    return 0;
}

相关函数如下:

  1. av_bsf_get_by_name("h264_mp4toannexb")

    • 功能:获取指定名称的比特流过滤器。
    • 参数:比特流过滤器的名称。
    • 返回值:指向 AVBitStreamFilter 结构的指针,如果找不到指定名称的过滤器,则返回 NULL。
  2. av_bsf_alloc(bsfilter, &bsf_ctx)

    • 功能:为指定的比特流过滤器分配上下文内存。
    • 参数:
      • bsfilter:要分配上下文的比特流过滤器。
      • bsf_ctx:用于存储分配的过滤器上下文的指针。
    • 返回值:成功返回 0,失败返回负值错误代码。
  3. avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar)

    • 功能:复制给定的编解码器参数到过滤器上下文的输入参数中。
    • 参数:
      • bsf_ctx->par_in:过滤器上下文的输入参数。
      • ifmt_ctx->streams[videoindex]->codecpar:要复制的编解码器参数。
    • 返回值:无。
  4. av_bsf_init(bsf_ctx)

    • 功能:初始化指定的比特流过滤器上下文。
    • 参数:要初始化的过滤器上下文。
    • 返回值:成功返回 0,失败返回负值错误代码。
  5. av_bsf_send_packet(bsf_ctx, pkt)

    • 功能:发送数据包给比特流过滤器进行处理。
    • 参数:
      • bsf_ctx:要发送数据包的比特流过滤器上下文。
      • pkt:要发送的数据包。
    • 返回值:成功返回 0,表示数据包被成功发送给过滤器;否则返回负值错误代码。
  6. av_bsf_receive_packet(bsf_ctx, pkt)

    • 功能:从比特流过滤器接收处理后的数据包。
    • 参数:
      • bsf_ctx:要从中接收数据包的比特流过滤器上下文。
      • pkt:用于存储接收到的数据包。
    • 返回值:成功返回 0,表示成功接收到数据包;否则返回负值错误代码。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

镓未来助力联想笔记本GaN适配器标配化,赋能高效用户体验

镓未来赋能笔记本电脑GaN适配器标配化 据悉&#xff0c;Lenovo 2024年推出搭配的多款新型笔记本原装适配器电源ADL100UDGC3A&#xff0c;采用了镓未来集成型Cascode技术氮化镓功率器件G1N65R150PB。新款方案相较上一代工艺&#xff0c;体积减小23%&#xff0c;重量降低18%&…

JavaScript之数据类型(3)——object进阶

前言&#xff1a; 利用基础知识来构建对象会发现十分复杂&#xff0c;我们可以结合其他的知识点来为我们object的构建进行优化。 <1>工厂法&#xff1a; 基本格式&#xff1a; function creatObject(属性值1,属性值2,属性值3,...,属性值n) {var 对象名 new Object();对…

parallels desktop19最新免费Mac电脑虚拟机软件

Parallels Desktop是一款运行在Mac电脑上的虚拟机软件&#xff0c;它允许用户在Mac系统上同时运行多个操作系统&#xff0c;比如Windows、Linux等。通过这款软件&#xff0c;Mac用户可以轻松地在同一台电脑上体验不同操作系统的功能和应用程序&#xff0c;而无需额外的硬件设备…

https免费证书获取

获取免费证书的网址&#xff1a; Certbot 1. 进入你的linux系统&#xff0c;先安装snapd&#xff0c; yum install snapd 2. 启动snapd service snapd start 3.安装 Certbot snap install --classic certbot 注意如下出现此错误时&#xff0c;需要先建立snap 软连接后&am…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-15.5讲 GPIO中断实验-通用中断驱动编写

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

MYSQL中的DQL

语法&#xff1a; select 字段列表 from 表名列表 where 条件列表 group by 分组字段列表 having 分组后条件列表 order by 排序字段 limit 分页参数 条件查询 语法&#xff1a; 查询多个字段&#xff1a;select 字段1&#xff0c;字段2 from表名 查询所有字段&#xff1a…

计算机视觉——OpenCV实现Lucas-Kanade 光流追踪

1.光流 光流法是计算机视觉中用于估计图像序列中物体运动的关键技术。它类似于观察夜空中的彗星&#xff0c;通过其在天空中的运动轨迹来追踪它的路径。在图像处理中&#xff0c;光流帮助我们理解像素点如何在连续的帧之间移动。 1.1 稀疏光流法 稀疏光流法关注于图像中的关…

【算法】最短路问题 bfs 到 dijkstra

1976、到达目的地的方案数 你在一个城市里&#xff0c;城市由 n 个路口组成&#xff0c;路口编号为 0 到 n - 1 &#xff0c;某些路口之间有 双向 道路。输入保证你可以从任意路口出发到达其他任意路口&#xff0c;且任意两个路口之间最多有一条路。 给你一个整数 n 和二维整…

Coze扣子开发指南:AI零代码编程创建插件

在Coze扣子中创建插件&#xff0c;有两种方式&#xff0c;一是用API&#xff0c;具体方式参照上一篇文章《Coze扣子开发指南&#xff1a;用免费API自己创建插件》&#xff0c;还有一种方式就是编程&#xff0c;不过有了AI的帮助&#xff0c;即使不会编程的人&#xff0c;也可以…

AI语音模型PaddleSpeech踩坑(安装)指南

PaddleSpeech简介 PaddleSpeech 是基于飞桨 PaddlePaddle 的语音方向的开源模型库&#xff0c;用于语音和音频中的各种关键任务的开发&#xff0c;包含大量基于深度学习前沿和有影响力的模型。 PaddleSpeech安装步骤 提示&#xff1a;要找到一个合适的PaddleSpeech版本与pad…

软件全套资料梳理(需求、开发、实施、运维、安全、测试、交付、认证、评审、投标等)

软件全套精华资料包清单部分文件列表&#xff1a; 工作安排任务书&#xff0c;可行性分析报告&#xff0c;立项申请审批表&#xff0c;产品需求规格说明书&#xff0c;需求调研计划&#xff0c;用户需求调查单&#xff0c;用户需求说明书&#xff0c;概要设计说明书&#xff0c…

C++ | Leetcode C++题解之第84题柱状图中最大的矩形

题目&#xff1a; 题解&#xff1a; class Solution { public:int largestRectangleArea(vector<int>& heights) {int n heights.size();vector<int> left(n), right(n, n);stack<int> mono_stack;for (int i 0; i < n; i) {while (!mono_stack.em…

批量生成大量附件(如:excel,txt,pdf)压缩包等文件时前端超时,采用mq+redis异步处理和多线程优化提升性能

一.首先分析一下场景&#xff1a;项目中我需要从财务模块去取单证模块的数据来生成一个个excel文件 在单证那个一个提单号就是一个excel文件&#xff0c;我们这边一个财务发票可能会查出几千个提单&#xff0c;也就是会生成几百个excel&#xff0c;然后压缩为一个压缩包&#x…

企业微信创建应用(一)

登录到企业微信后台管理(https://work.weixin.qq.com/)进入自建应用(应用管理-应用-创建应用) 3.查看参数AgentId和 Secret 4.企业微信查看效果

Python | Leetcode Python题解之第84题柱状图中最大的矩形

题目&#xff1a; 题解&#xff1a; class Solution:def largestRectangleArea(self, heights: List[int]) -> int:n len(heights)left, right [0] * n, [n] * nmono_stack list()for i in range(n):while mono_stack and heights[mono_stack[-1]] > heights[i]:righ…

音频运放LPF使用

1、用于差分ADC输入的单端到差分缓冲器和抗混叠LPF 2、用于单端ADC输入的缓冲和抗混叠LPF 3、用于DAC输出的后置LPF和差分到单端缓冲器&#xff08;AC耦合&#xff09; 4、后LPF和差分到DAC输出的单端缓冲器&#xff08;直流耦合&#xff09; 5、用于差分ADC输入的具有抗混叠LP…

【无标获取S4与ECC的具体差异的方法题】

首先我们需要对ECC vs S4的差异这个课题要有一个深刻的理解&#xff0c;这不是一个简单并能准确说清楚的课题。 我们需要结合实际项目的具体情况去回答这个问题&#xff0c;因为这个问题本身是没有标准答案的。 首先要了解SAP本身ERP产品线的发展概况&#xff0c;其次我们要…

微信小程序踩坑,skyline模式下,scroll-view下面的一级元素设置margin中的auto无效,具体数据有效

开发工具版本 基础库 开启skyline渲染调试 问题描述 skyline模式下,scroll-view下面的一级元素的margin写auto的值是没有效果的(二级元素margin写auto是有效果的),关闭这个模式就正常显示 演示效果图 父元素的宽度和高度效果(宽度是750rpx,宽度占满的) 一级元素宽度和css效果…

在Linux上安装并启动Redis

目录 安装gcc环境 上传redis文件方法一&#xff1a;sftp 上传redis文件方法二&#xff1a;wget 启动redis-server ctrlc关闭redis-server 参考文章&#xff1a;Linux 安装 Redis 及踩坑 - 敲代码的阿磊 - 博客园 (cnblogs.com) 准备&#xff1a;打开VMware Workstation&am…

20240511,谓词,内建函数对象

拜托铠甲勇士真的帅好不好&#xff01;&#xff01;&#xff01; STL案例2-员工分组 10个员工&#xff0c;指派部门&#xff0c;员工信息&#xff08;姓名&#xff0c;工资组成&#xff0c;部门&#xff1a;策划&#xff0c;美术&#xff0c;研发&#xff09;&#xff0c;随机…