一.H.264图像编解码器的基本知识
H.264是一种图像压缩编码协议,基于该协议实现的编码器称作H.264编码器。实现上主要有硬件编码器和软件编码器的区别,硬件编码器需要特定的硬件支持而软件编码器则是使用CPU模拟这个过程。
从应用开发的角度来看,我们不需要知道H.264具体是如何实现的,只需要知道它的操作结构是怎样的。在协议上,它将数据封装为NALU
(Network Abstraction Layer Unit)的基本单元来进行操作。
NALU
结构如下图:
它由两部分组成:数据头部和数据内容。可以取头部字节的后5位来判断它的具体类型。
typedef NS_ENUM(NSInteger,VTH264NaluType)
{
kVTH264NaluTypeNonIFrame = 1,
kVTH264NaluTypeIFrame = 5,
kVTH264NaluTypeSPS = 7,
kVTH264NaluTypePPS = 8
};
+ (VTH264NaluType)getH264NaluType:(NSData *)nalu
{
NSInteger nalType;
if(nalu.length < 4) return 0;
[nalu getBytes:&nalType range:NSMakeRange(4, 1)];
nalType = nalType & 0x1F;
return nalType;
}
H.264编码器输出数据结构如下图:
具体的实现上和协议规定不同的是,这里加入了NALU Start Code(0x00000001 4个字节)
来切分每个NALU
,使得编码器的输出变得有序方便应用软件来提取每个NALU
数据包。其中SPS/PPS这两个NALU
可以看作是描述了该次编码的媒体基本信息,在实践中,常常使用这两个作为基本参数来创建解码会话。
在iOS中的H.264编码器数据输出序列:
二.iOS中的H.264编码/解码操作
在iOS中使用CMSampleBuffer
这个结构来描述图像数据,它包括三个部分CMTime
(媒体时间信息),CMVideoFormatDesc
(媒体格式信息),CMBlockBuffer
/CVPixelBuffer
(媒体内容)。编解码的过程可以简述为CMSampleBuffer
和NAUL
之间的转换。
CMSampleBuffer的结构:
在iOS中利用VedioToolBox框架的api将H.264数据流转换为图像数据(Decode)流程:
将start code转为数据长度->创建CMBlockBuffer->转化为CMSampleBuffer->解码出CVImageBufferRef(CVPixelBuffer)
- (void)decodeProcess:(NSData *)stream
{
............
//将start code转为数据长度
int tempLen = (int)streamLen-4;
tempLen = CFSwapInt32HostToBig(tempLen);
NSMutableData *temp = [[NSMutableData alloc] init];
[temp appendData:stream];
[temp replaceBytesInRange:NSMakeRange(0, 4) withBytes:&tempLen length:4];
//(NSData*)stream to (void*)stream
void *data = malloc(streamLen);
if (data == NULL)
{
NSLog(@"H264 Decoder buffer malloc mem err");
return;
}
[temp getBytes:data range:NSMakeRange(0, streamLen)];
//创建CMBlockBuffer
OSStatus status;
CMBlockBufferRef blockBuffer = nil;//buffer handle
status = CMBlockBufferCreateWithMemoryBlock(NULL,
data, //mem block pointer
streamLen, //mem block len
NULL,
NULL,
0, //copy index
streamLen, //len
0,
&blockBuffer); //handle
.........
//转化为CMSampleBuffer
CMSampleBufferRef sampleBuffer = nil;
const size_t sampleSizeArray[] = {streamLen};
status = CMSampleBufferCreateReady(NULL,
blockBuffer,
_videoFormatDESC,
1, //num of sample in buffer
0, //num of entries in sampleTimingArray
NULL, //sampleTimingArray
1, //num of entries in sampleSizeArray
sampleSizeArray,
&sampleBuffer);
.........
// 解码
VTDecodeFrameFlags frameFlags = 0;//async flagss=kVTDecodeFrame_EnableAsynchronousDecompression
VTDecodeInfoFlags infoFlags = 0;
CVPixelBufferRef outputPixelBuffer = nil;
status = VTDecompressionSessionDecodeFrame(_decoderSession,
sampleBuffer,
frameFlags,
&outputPixelBuffer,
&infoFlags);
CFRelease(sampleBuffer);
CFRelease(blockBuffer);
.........
}
// 解码器回调函数在decode session初始化的时候指定
static void didDecompress(void *decompressionOutputRefCon,
void *sourceFrameRefCon,
OSStatus status,
VTDecodeInfoFlags infoFlags,
CVImageBufferRef pixelBuffer,
CMTime presentationTimeStamp,
CMTime presentationDuration )
{
if(!pixelBuffer) return;
UIImage *image = nil;
if(CURRENT_SYSTEM_VERSION < 9){
image = [[UIImage alloc] initWithCIImage:[CIImage imageWithCVPixelBuffer:pixelBuffer]];
}
else{
CGImageRef giImageRef;
VTCreateCGImageFromCVPixelBuffer(pixelBuffer, NULL, &giImageRef);
image = [UIImage imageWithCGImage:giImageRef];
CGImageRelease(giImageRef);
}
.......
}
三.H.264的多媒体数据封装——FLV
多媒体包括视频和音频,使用特定的文件格式将H.264和AAC数据包裹起来才是我们常见的多媒体文件。
- FLV:Flash Video是现在非常流行的流媒体格式,由于其视频文件体积轻巧、封装播放简单等特点,使其很适合在网络上进行应用。
- TS:全称为MPEG2-TS,TS即"Transport Stream"的缩写。它的特点是任意一个视频片流都能够独立的进行解析,常用于广播电视系统中。
H.264/AAC的FLV封装
FLV包括文件头(File Header)和文件体(File Body)两部分,其中文件体由一系列的Tag及Previos Tag Size对组成。
Tag的类型可以是视频、音频和Script,每个Tag只能包含以上三种类型的数据中的一种。
三种Tag对应三种不同的Tag Data。其中Scritpt Tag封装着这个媒体的格式信息,有且只有一个,一般作为Tag1紧跟着Flv Header出现。
1.Script Tag Data结构(控制帧)
该类型Tag又通常被称为Metadata Tag,会放一些关于FLV视频和音频的元数据信息如:duration、width、height等。它一般包含几个AMF(Action Message Format),AMF是Adobe设计的一种通用数据封装格式,在Adobe的很多产品中应用,简单来说,AMF将不同类型的数据用统一的格式来描述。
AMF也是由头部和内容组成的一个数据包结构,头部包括1个字节的类型,几个字节的内容长度。
第一个AMF包:
第1个字节表示AMF包类型,一般总是0x02,表示字符串。第2-3个字节为Uint16类型值,标识字符串的长度,一般总是一个字符串的长度0x000A(“onMetaData”长度),后面字节为具体的字符串,一般总为字符串“onMetaData”(6F,6E,4D,65,74,61,44,61,74,61)。
第二个AMF包:
第1个字节表示AMF包类型,一般总是0x08,表示词典。第2-5个字节为Uint32类型值,表示词典元素的个数。后面即为各词典元素的封装。其中第二个AMF常见的字符串词典元素有:
字符串key | 内容 |
---|---|
duration | 时长 |
width | 视频宽度 |
height | 视频高度 |
videodatarate | 视频码率 |
framerate | 视频帧率 |
videocodecid | 视频编码方式 |
audiosamplerate | 音频采样率 |
audiosamplesize | 音频采样精度 |
stereo | 是否为立体声 |
audiocodecid | 音频编码方式 |
filesize | 文件大小 |
2.Video Tag Data结构(视频Tag)
视频Tag用开始的第1个字节包含视频数据的参数信息,从第2个字节为视频流数据。
前四位:
帧类型值 | 类型 |
---|---|
1 | keyframe (for AVC,a seekable frame) |
2 | inter frame (for AVC,a nonseekable frame) |
3 | disposable inter frame (H.263 only) |
4 | generated keyframe (reserved for serve use only) |
5 | video info/command frame |
后四位:
视频编码器类型 | 类型 |
---|---|
1 | JPEG (currently unused) |
2 | Sorenson H.263 |
3 | Screen video |
4 | On2 VP6 |
5 | On2 VP6 with alpha channel |
6 | Screen video version 2 |
7 | AVC |
3.Audio Tag Data结构(音频Tag)
音频Tag开始的第1个字节包含了音频数据的参数信息,从第2个字节开始为音频流数据。
前四位:
音频编码类型 | 类型 |
---|---|
0 | Linear PCM,Platform endian |
1 | ADPCM |
2 | MP3 |
3 | Linear PCM,little endian |
4 | Nellymoser 16-kHz mono |
5 | Nellymoser 8-kHz mono |
6 | Nellymoser |
7 | G.711 A-law logarithmic PCM |
8 | G.711 mu-law logarithmic PCM |
9 | reserved |
10 | AAC |
14 | MP3 8-Khz |
15 | Device-specific sound |
5-6位:
采样率类型 | 值 |
---|---|
0 | 5.5kHz |
1 | 11kHz |
2 | 22kHz |
3 | 44kHz |
从协议上看,FLV是不支持48kHZ采样率的音频的。
第7位:
音频采样精度 | 值 |
---|---|
0 | 8bits |
1 | 16bits |
第8位:
音频类型 | 值 |
---|---|
0 | sndMono |
1 | sndStereo |