抽取视频数据步骤
1、打开.mp4格式文件
2、创建并打开一个空文件存储 H264 数据
3、找到视频流,并循环读取流中AVPacket,并为每个流添加特征码或sps/pps等数据(只有关键帧前面要添加sps/pps数据,其他的只需要添加特征码)。
4、都处理完后将数据写入文件保存
流程图:
相关API
读取sps和pps 数据
从AVCodecContext的extradata数据域中读取,如下所示,mp4文件的extradata的部分数据
extradata 数据详细分析
- 跳过前 4 个字节
- 第5个字节 ff 的后2位用于指示表示编码数据长度所需字节数
- 第6个字节 e1 后5位(结果是1)是表示接下来的sps或pps的个数为1
- 第7、8两个字节00 18表示接下来的sps或pps数据的长度,结果是接下来sps或pps长度是24个字节。
- 第9个字节是67表示这个是sps数据,也就是说从67到9a这24个字节是sps数据。因为sps只有一个,所以接下来是pps数据。
- 第34个字节也就是9a后面的那个字节 01,表示 pps 的个数为 1 个
- 第 35、36 两个字节 00 06 标识pps 数据长度是 6
- 第 37 个字节 68 表明这确实是pps数据,包括68在内的6个字节是pps的内容
提取到的每个sps/pps数据在写入h264文件时都要在其前面加上4个字节的特征码(0x00000001)。
源码:
/*
*提取视频中的视频数据
* */
#include <stdio.h>
#include <libavformat/avformat.h>
#include <libavutil/log.h>
#include <libavformat/avio.h>
/*
在帧前面添加特征码(一般SPS/PPS的帧的特征码用4字节表示,为0X00000001,其他的帧特征码用3个字节表示,为0X000001。也有都用4字节表示的,我们这里采用前面的方式)
out是要输出的AVPaket
sps_pps是SPS和PPS数据的指针,对于非关键帧就传NULL
sps_pps_size是SPS/PPS数据的大小,对于非关键帧传0
in是指向当前要处理的帧的头信息的指针
in_size是当前要处理的帧大小(nal_size)
*/
static int alloc_and_copy(AVPacket *out,const uint8_t *sps_pps, uint32_t sps_pps_size,const uint8_t *in, uint32_t in_size)
{
uint32_t offset = out->size; // 偏移量,就是out已有数据的大小,后面再写入数据就要从偏移量处开始操作
// 特征码的大小,SPS/PPS占4字节,其余占3字节
uint8_t nal_header_size = sps_pps==NULL ? 3 : 4;
int err;
// 每次处理前都要对out进行扩容,扩容的大小就是此次要写入的内容的大小,也就是特征码大小加上sps/pps大小加上加上本帧数据大小
if ((err = av_grow_packet(out, sps_pps_size + in_size + nal_header_size)) < 0)
return err;
// 1.如果有sps_pps则先将sps_pps拷贝进out(memcpy()函数用于内存拷贝,第一个参数为拷贝要存储的地方,第二个参数是要拷贝的内容,第三个参数是拷贝内容的大小)
if (sps_pps)
{
memcpy(out->data + offset, sps_pps, sps_pps_size);
}
// 2.再设置特征码(sps/pps特征码4位0x00000001,其他的特征码3位0x000001)
for (int i = 0; i < nal_header_size; i++)
{
(out->data+offset+sps_pps_size)[i] = i==nal_header_size-1 ? 1 : 0;
}
// 3.最后再拷贝NALU数据(当前处理的帧数据)
memcpy(out->data + sps_pps_size + nal_header_size + offset, in, in_size);
return 0;
}
/*
读取并拷贝sps/pps数据
codec_extradata是codecpar的扩展数据,sps/pps数据就在这个扩展数据里面
codec_extradata_size是扩展数据大小
out_extradata是输出sps/pps数据的AVPacket包
padding:就是宏AV_INPUT_BUFFER_PADDING_SIZE的值(64),是用于解码的输入流的末尾必要的额外字节个数,需要它主要是因为一些优化的流读取器一次读取32或者64比特,可能会读取超过size大小内存的末尾。
*/
int h264_extradata_to_annexb(const uint8_t *codec_extradata, const int codec_extradata_size, AVPacket *out_extradata, int padding)
{
uint16_t unit_size; // sps/pps数据长度
uint64_t total_size = 0; // 所有sps/pps数据长度加上其特征码长度后的总长度
for (int i = 0; i < codec_extradata_size; ++i)
{
printf("%02x ",*(codec_extradata+i));
}
/*
out:是一个指向一段内存的指针,这段内存用于存放所有拷贝的sps/pps数据和其特征码数据
unit_nb:sps/pps个数
sps_done:sps数据是否已经处理完毕
sps_seen:是否有sps数据
pps_seen:是否有pps数据