目录
1:H264语法结构回顾
2:H264编码补充介绍
3:MP4模式转Annex B模式输出到文件示例
1:H264语法结构回顾
在之前文章里介绍过H264的语法结构。
传送门: 视音频-H264 编码NALU语法结构简介
2:H264编码补充介绍
H.264视频编码标准中两种常见的封装方式:annexb模式和mp4模式。
-
annexb模式:
- 这是传统的封装模式,其中每个视频帧的数据(NALUnit)在传输时以0x000001或0x0000001作为分隔符。
- SPS(Sequence Parameter Set)和PPS(Picture Parameter Set)等关键信息通常会周期性地在视频流内部重复,特别是在关键帧(I帧)之前。
- 这种模式下,视频帧的数据相对独立,不依赖于容器格式。
-
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;
}
相关函数如下:
-
av_bsf_get_by_name("h264_mp4toannexb")
:- 功能:获取指定名称的比特流过滤器。
- 参数:比特流过滤器的名称。
- 返回值:指向
AVBitStreamFilter
结构的指针,如果找不到指定名称的过滤器,则返回 NULL。
-
av_bsf_alloc(bsfilter, &bsf_ctx)
:- 功能:为指定的比特流过滤器分配上下文内存。
- 参数:
bsfilter
:要分配上下文的比特流过滤器。bsf_ctx
:用于存储分配的过滤器上下文的指针。
- 返回值:成功返回 0,失败返回负值错误代码。
-
avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar)
:- 功能:复制给定的编解码器参数到过滤器上下文的输入参数中。
- 参数:
bsf_ctx->par_in
:过滤器上下文的输入参数。ifmt_ctx->streams[videoindex]->codecpar
:要复制的编解码器参数。
- 返回值:无。
-
av_bsf_init(bsf_ctx)
:- 功能:初始化指定的比特流过滤器上下文。
- 参数:要初始化的过滤器上下文。
- 返回值:成功返回 0,失败返回负值错误代码。
-
av_bsf_send_packet(bsf_ctx, pkt)
:- 功能:发送数据包给比特流过滤器进行处理。
- 参数:
bsf_ctx
:要发送数据包的比特流过滤器上下文。pkt
:要发送的数据包。
- 返回值:成功返回 0,表示数据包被成功发送给过滤器;否则返回负值错误代码。
-
av_bsf_receive_packet(bsf_ctx, pkt)
:- 功能:从比特流过滤器接收处理后的数据包。
- 参数:
bsf_ctx
:要从中接收数据包的比特流过滤器上下文。pkt
:用于存储接收到的数据包。
- 返回值:成功返回 0,表示成功接收到数据包;否则返回负值错误代码。