H264码流介绍 及 FFmpeg解封装得到H264码流方法

参考文章

音视频 H264 编解码协议-知乎
视频H264编码详解(上)-知乎
H.264媒体流AnnexB和AVCC格式分析-CSDN
H264之NALU解析-知乎
H264帧,SPS,PPS概念-知乎
H.264流媒体协议格式中的Annex B格式和AVCC格式深度解析-CSDN

H264简介

H.264从1999年开始,到2003年形成草案,最后在2007年定稿有待核实。在ITU的标准⾥称为H.264,在MPEG的标准⾥是MPEG-4的⼀个组成部分--MPEG-4 Part 10,⼜叫Advanced Video Codec,因此常常称为MPEG-4 AVC或直接叫AVC。

带宽

Mbps(Million bits per second)即"传输速率",也叫"带宽"。Mbps和MB/s是怎么换算的呢?

8Mbps=1MB/s;8Mbps换算成下载速度就是1MB/s,不过由于种种限制,实际情况中8M的宽带往往达不到1MB/s的下载速度,能达到800KB/s以上算是正常情况。

NALU(Network Abstract Layer Unit)网络抽象层单元

H.264原始码流(裸流)由⼀个接⼀个NALU组成.

SPS :序列参数集,包含解码配置,如profile level分辨率和帧率等。编码后的第一帧。
PPS :图像参数集,包含有关熵编码模式、分片组、运动预测和去块滤波器等信息。编码后的第二帧。
I帧 :帧内编码,可独立解码生成完整的图片。
P帧 :前向预测编码帧,需要参考其前⾯的⼀个I 或者B 来⽣成⼀张完整的图⽚。
B帧 : 双向预测内插编码帧,则要参考其前⼀个I或者P帧及其后⾯的⼀个P帧来⽣成⼀张完整的图⽚。
NALU = StartCode + ⼀组对应于视频编码的NALU头部信息 + ⼀个原始字节序列负荷(RBSP, Raw Byte Sequence Payload)

其中 Start Code⽤于标示这是⼀个NALU单元的开始,必须是"00 00 00 01" 或"00 00 01",除此之外基本相当于⼀个NAL header + RBSP:

值得注意的是,一个NALU即使是VCL NALU 也并不一定表示一个视频帧。因为一个帧的数据可能比较多,可以分片为多个NALU来储存。一个或者多个NALU组成一个访问单元AU,一个AU包含一个完整的帧。

H264编码分层

  • NAL层:(Network Abstraction Layer,视频数据网络抽象层)
    它的作用是H264只要在网络上传输,在传输的过程每个包以太网是1500字节. 而H264的帧往往会大于1500字节的.所以就要进行拆包. 将一个帧拆成多个包进行传输.所有的拆包或者组包都是通过NAL层去处理的.
  • VCL层:(Video Coding Layer,视频数据编码层)
    它的作用就是对视频原始数据进行压缩.

H264 码流格式

H264有两种封装:

  • ⼀种是AnnexB模式
    [start code]NALU | [start code] NALU |...

    这种格式比较常见,也就是我们熟悉的每个帧前面都有0x00 00 00 01或者0x00 00 01作为起始码。.h264文件就是采用的这种格式,每个帧前面都要有个起始码。SPS PPS等也作为一类NALU存储在这个码流中,一般在码流最前面。也就是说这种格式包含VCL 和 非VCL 类型的NALU。

  • ⼀种是AVCC模式
    ([extradata]) | ([length] NALU) | ([length] NALU) | ...

    FLV/MP4/MKV中H264采用的是AVCC模式,也叫AVC1格式。

    没有startcode,每⼀个frame前⾯4个字节是frame⻓度。SPS和PPS等参数信息被封装在extradata中。

    比如ffmpeg中解析mp4文件后sps pps存在streams[index]->codecpar->extradata中。也就是说这种码流通常只包含VCL类型NALU。

    此时将前4个字节替换为0x00000001才能得到AnnexB模式的NALU数据。

    很多解码器只⽀持AnnexB这种模式,因此需要将MP4做转换。在ffmpeg中⽤ h264_mp4toannexb_filter接口可以做转换。

FFmpeg解析mp4中H264码流

MP4文件中编码信息是存储在文件开始或者文件末尾的,详细结构这里不详述了。就知道不是和图像数据放在一起的就可以了。

FFmpeg使用av_read_frame(AVFormatContext *s, AVPacket *pkt)函数读mp4文件,读到packet里面仅仅是VCL编码数据NAL,并且这个编码数据是AVCC格式组织的码流,直接保存成.264文件没法播放。

  1. avFmtCtx->streams[_videoStreamIndex]->codecpar->extradata中解析SPS和PPS数据,解析出SPS PPS数据加上4字节的0001的起始码拼装成AnnexB格式的NALU,先写入文件.
  2. 通过av_read_frame(AVFormatContext *s, AVPacket *pkt)读取到数据存放在pkt->data中,长度为pkt->size
    注意:这1个pkt->data中的数据可能是多个NALU的数据!!!这些数据按([length] NALU) | ([length] NALU) | ...规则排列。先取前4字节作为长度,读取指定长度的数据加上起始码拼NALU。然后同样的方式读取后面的数据,直到总长度等于pkt->size。

FFmpeg 解析mp4中H264码流 代码示例

参考链接:

H.264媒体流AnnexB和AVCC格式分析-CSDN

	//...
    AVPacket spsPacket, ppsPacket, tmpPacket;
    uint8_t startCode[4] = {0x00, 0x00, 0x00, 0x01};
    bool sendSpsPps = false;

    while (av_read_frame(_avFmtCtx, _avPacket) == 0) { // 能读到数据返回0,循环读取
    	// 根据pkt->stream_index判断是不是视频流
        if (_avPacket->stream_index == _videoStreamIndex) {
        	// 仅1次处理sps pps,也可以拿在while外面
            if (!sendSpsPps) { 
                int spsLength = 0;
                int ppsLength = 0;
                // extradata 数据指针,方便操作取其指针
                uint8_t *ex = _avFmtCtx->streams[_videoStreamIndex]->codecpar->extradata;
				
				// extradata;第6字节后5位表示SPS个数,通常为1,这里就省略判断处理,严谨期间还是要判断
				// 直接 取第7 8 俩字节作为SPS长度
                spsLength = (ex[6] << 8) | ex[7];
				
				// x[8+spsLength]表示PPS个数,通常为1,这里就省略判断处理
				// 取接下来两位作为PPS长度
                ppsLength = (ex[8 + spsLength + 1] << 8) | ex[8 + spsLength + 2];

				// 为spsPacket ppsPacket的data分配内存,类似malloc
				// 如果只是为了保存文件,可以不使用pkt结构,直接malloc就行
				// 分配的空间为sps或pps长度加上4字节的起始码
                av_new_packet(&spsPacket, spsLength + 4);
                av_new_packet(&ppsPacket, ppsLength + 4);
				
				// 给SPS拼前4字节起始码
                memcpy(spsPacket.data, startCode, 4);
                // 把SPS数据拼在起始码后面
                memcpy(spsPacket.data + 4, ex + 8, spsLength);
				
				// TODO: 这里可以把spsPacket.data数据写入文件 
				
				// 给PPS拼前4字节起始码
                memcpy(ppsPacket.data, startCode, 4);
                // 把PPS数据拼在起始码后面
                memcpy(ppsPacket.data + 4, ex + 8 + spsLength + 2 + 1, ppsLength);
                
				// TODO: 这里可以把ppsPacket.data数据写入文件 
				
                sendSpsPps = true;
            }

			// 下面处理读到pkt中的数据
            int nalLength = 0;
            uint8_t *data = _avPacket->data;
            // _avPacket->data中可能有多个NALU,循环处理
            while (data < _avPacket->data + _avPacket->size) {
            	// 取前4字节作为nal的长度
                nalLength = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
                if (nalLength > 0) {
                    memcpy(data, startCode, 4);  // 拼起始码
                    tmpPacket = *_avPacket;      // 仅为了复制packet的其他信息,保存文件可忽略
                    tmpPacket.data = data;		 // 把tmpPkt指针偏移到实际数据位置
                    tmpPacket.size = nalLength + 4; // 长度为nal长度+起始码4
					
					//TODO: 处理这个NALU的数据,可以直接把tmpPacket.data写入文件
                }
                data = data + 4 + nalLength; // 处理data中下一个NALU数据
            }
        }

        av_packet_unref(_avPacket);
    }

通过FFMPEG的h264_mp4toannexb进行格式转换

参考链接:

FFmpeg简单使用:过滤器 ---- h264_mp4toannexb-CSDN
ffmpeg 新接口 :从flv文件中提取h264码流-3ms

相关推荐
简鹿办公5 小时前
快速上手:利用 FFmpeg 合并音频文件的实用教程
ffmpeg·音频合并命令·ffmpeg合并音频
冰冰的coco5 小时前
音视频基础 -(编码、封装、推拉流)格式/协议 与 ffmpeg处理流程
ffmpeg·音视频
yerennuo16 小时前
FFmpeg 音视频基础
qt·ffmpeg·音视频
少年少年少年奋斗奋斗奋斗21 小时前
使用 ffmpeg 给视频批量加图片水印
ffmpeg·音视频
cuijiecheng20181 天前
音视频入门基础:MPEG2-TS专题(13)——FFmpeg源码中,解析Section Header的实现
ffmpeg·音视频
我才是一卓1 天前
linux 编译、交叉编译 opencv+ffmpeg 为动态库
linux·opencv·ffmpeg
飞飞-躺着更舒服2 天前
FFmpeg 解复用过程
ffmpeg·音视频
韩曙亮2 天前
【FFmpeg】FFmpeg 内存结构 ⑥ ( 搭建开发环境 | AVPacket 创建与释放代码分析 | AVPacket 内存使用注意事项 )
ffmpeg·音视频·avpacket·内存结构·avframe·avbuffer
Rox_Lee2 天前
ffmpeg使用自定义字体添加字幕
ffmpeg