一.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(媒体内容)。编解码的过程可以简述为CMSampleBufferNAUL之间的转换。

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

results matching ""

    No results matching ""