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

2024-05-12 08:36

本文主要是介绍音视频-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;}// 分配一个AVPacketpkt = 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);// 释放AVPacketif(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,表示成功接收到数据包;否则返回负值错误代码。

这篇关于音视频-H264编码封装- MP4格式转Annex B格式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/982129

相关文章

Golang如何对cron进行二次封装实现指定时间执行定时任务

《Golang如何对cron进行二次封装实现指定时间执行定时任务》:本文主要介绍Golang如何对cron进行二次封装实现指定时间执行定时任务问题,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录背景cron库下载代码示例【1】结构体定义【2】定时任务开启【3】使用示例【4】控制台输出总结背景

Mysql常见的SQL语句格式及实用技巧

《Mysql常见的SQL语句格式及实用技巧》本文系统梳理MySQL常见SQL语句格式,涵盖数据库与表的创建、删除、修改、查询操作,以及记录增删改查和多表关联等高级查询,同时提供索引优化、事务处理、临时... 目录一、常用语法汇总二、示例1.数据库操作2.表操作3.记录操作 4.高级查询三、实用技巧一、常用语

利用Python脚本实现批量将图片转换为WebP格式

《利用Python脚本实现批量将图片转换为WebP格式》Python语言的简洁语法和库支持使其成为图像处理的理想选择,本文将介绍如何利用Python实现批量将图片转换为WebP格式的脚本,WebP作为... 目录简介1. python在图像处理中的应用2. WebP格式的原理和优势2.1 WebP格式与传统

Python中对FFmpeg封装开发库FFmpy详解

《Python中对FFmpeg封装开发库FFmpy详解》:本文主要介绍Python中对FFmpeg封装开发库FFmpy,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、FFmpy简介与安装1.1 FFmpy概述1.2 安装方法二、FFmpy核心类与方法2.1 FF

C++ 函数 strftime 和时间格式示例详解

《C++函数strftime和时间格式示例详解》strftime是C/C++标准库中用于格式化日期和时间的函数,定义在ctime头文件中,它将tm结构体中的时间信息转换为指定格式的字符串,是处理... 目录C++ 函数 strftipythonme 详解一、函数原型二、功能描述三、格式字符串说明四、返回值五

C#实现将Office文档(Word/Excel/PDF/PPT)转为Markdown格式

《C#实现将Office文档(Word/Excel/PDF/PPT)转为Markdown格式》Markdown凭借简洁的语法、优良的可读性,以及对版本控制系统的高度兼容性,逐渐成为最受欢迎的文档格式... 目录为什么要将文档转换为 Markdown 格式使用工具将 Word 文档转换为 Markdown(.

Java中JSON格式反序列化为Map且保证存取顺序一致的问题

《Java中JSON格式反序列化为Map且保证存取顺序一致的问题》:本文主要介绍Java中JSON格式反序列化为Map且保证存取顺序一致的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未... 目录背景问题解决方法总结背景做项目涉及两个微服务之间传数据时,需要提供方将Map类型的数据序列化为co

Ubuntu上手动安装Go环境并解决“可执行文件格式错误”问题

《Ubuntu上手动安装Go环境并解决“可执行文件格式错误”问题》:本文主要介绍Ubuntu上手动安装Go环境并解决“可执行文件格式错误”问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未... 目录一、前言二、系统架构检测三、卸载旧版 Go四、下载并安装正确版本五、配置环境变量六、验证安装七、常见

基于Go语言实现Base62编码的三种方式以及对比分析

《基于Go语言实现Base62编码的三种方式以及对比分析》Base62编码是一种在字符编码中使用62个字符的编码方式,在计算机科学中,,Go语言是一种静态类型、编译型语言,它由Google开发并开源,... 目录一、标准库现状与解决方案1. 标准库对比表2. 解决方案完整实现代码(含边界处理)二、关键实现细

一文详解如何在Vue3中封装API请求

《一文详解如何在Vue3中封装API请求》在现代前端开发中,API请求是不可避免的一部分,尤其是与后端交互时,下面我们来看看如何在Vue3项目中封装API请求,让你在实现功能时更加高效吧... 目录为什么要封装API请求1. vue 3项目结构2. 安装axIOS3. 创建API封装模块4. 封装API请求