编码的流程:采集--> 获取到视频帧--> 对视频帧进行编码 --> 获取到视频帧信息 --> 将编码后的数据以NALU方式写入到文件
1.NAL的封装方式:
- NAL是将每一帧数据写入到一个或者多个NALU单元中,进行传输或存储的
- NALU分为NAL头和NAL体
- NALU头通常为00 00 00 01,作为一个新的NALU的起始标识
2.封装过程:
- I帧、P帧、B帧都是被封装成一个或者多个NALU进行传输或者存储的
- I帧开始之前也有非VCL的NAL单元,用于保存其他信息,比如:PPS、SPS
- PPS(Picture Parameter Sets):图像参数集
- SPS(Sequence Parameter Set):序列参数集
- 在实际的H264数据帧中,往往帧前面带有00 00 00 01 或 00 00 01分隔符,一般来说编码器编出的首帧数据为PPS与SPS,接着为I帧,后续是B帧、P帧等数据
3.如何获取SPS和PPS
使用RTP传输H264的时候,需要用到sdp协议描述,其中有两项:Sequence Parameter Sets (SPS) 和Picture Parameter Set (PPS)需要用到,那么这两项从哪里获取呢?答案是从H264码流中获取.在H264码流中,都是以"0x00 0x00 0x01"或者"0x00 0x00 0x00 0x01"为开始码的,找到开始码之后,使用开始码之后的第一个字节的低5位判断是否为7(sps)或者8(pps), 及data[4] & 0x1f == 7 || data[4] & 0x1f == 8,得到的信息就可以用于sdp.sps和pps需要用逗号分隔开来.
typedef enum
{
NALUTypeSliceNoneIDR = 1,
NALUTypeSliceIDR = 5,
NALUTypeSPS = 7,
NALUTypePPS = 8
} NALUType;
- (int)getNALUType:(NSData *)NALU
{
uint8_t * bytes = (uint8_t *) NALU.bytes;
return bytes[4] & 0x1F;
}
-(void)logType:(NSData *)h264Data
{
int type = [self getNALUType: h264Data];
switch (type) {
case NALUTypeSliceNoneIDR:
NSLog(@"NALUTypeSliceNoneIDR");
break;
case NALUTypeSliceIDR:
NSLog(@"_______________NALUTypeSliceIDR");
break;
case NALUTypeSPS:
NSLog(@"~~~~~~NALUTypeSPS");
break;
case NALUTypePPS:
NSLog(@"~~~~~~NALUTypePPS");
break;
default:
NSLog(@"type = %i",type);
break;
}
}