iOS硬解H.264:-VideoToolboxDemo源码分析[草稿]

来源:http://www.cnblogs.com/michaellfx/p/understanding_-VideoToolboxDemo.html

 

iOS硬解H.264:-VideoToolboxDemo源码分析[草稿]

iOS硬解H.264:-VideoToolboxDemo源码分析

-VideoToolboxDemo为VideoToolbox的简单应用示例。

1 - 初始化

技术分享

(一)初始化FFmpeg

SuperVideoFrameExtractor类提供了两个初始化方法,

  • initWithVideo:usesTcp:
  • initWithVideo:

分别对应本地文件与网络流。

为了提高响应速度,在读取网络流时,手动遍历 AVFormatContext.streams[] 字段,本地文件则调用av_find_best_stream()获取指定的音视频流。正如函数名所示,为了查找最佳信息,av_find_best_stream()需要读取更多的流数据才能返回流信息,考虑到网络传输速度不稳定,有时该函数执行时间略长。

另外,读取网络流时,打开流之前,需初始化网络和设置传输协议,由avformat_network_init()av_dict_set("rtsp_transport", "tcp") 实现。

(二)视频时长与当前播放时间

视频总长度可从AVFormatContext中读取,单位微秒。

video.duration {
	return (double)pFormatCtx->duration / AV_TIME_BASE/* 1000000 */;
}

duration字段说明如下

/**
 * Duration of the stream, in AV_TIME_BASE fractional
 * seconds. Only set this value if you know none of the individual stream
 * durations and also do not set any of them. This is deduced from the
 * AVStream values if not set.
 *
 * Demuxing only, set by libavformat.
 */

当前播放时间由AVPacket.pts字段与AVStream.time_base字段计算得出:

- (double)currentTime {
    AVRational timeBase = pFormatCtx->streams[videoStream]->time_base;
    return packet.pts * (double)timeBase.num / timeBase.den;
}

其实,有时AVPacket并无pts数据,FFmpeg会从视频帧所属的包中复制第一帧的显示时间戳给后面的帧,所以,此值不一定准确。

(三)视频刷新

使用CADisplayLink以每秒60次调用 displayLinkCallback: 方法刷新视频。此方法读取显示时间戳pts、视频转成的图片数组,在时间戳数据足够( if ([self.presentationTimes count] == 3) )时发出继续解码信号,同时在图像数据有值时转换成图片并显示在UIImageView上。

- (void)displayLinkCallback:(CADisplayLink *)sender
{
    if ([self.outputFrames count] && [self.presentationTimes count]) {
        CVImageBufferRef imageBuffer = NULL;
        NSNumber *insertionIndex = nil;
        id imageBufferObject = nil;
        @synchronized(self){
            insertionIndex = [self.presentationTimes firstObject];
            imageBufferObject = [self.outputFrames firstObject];
            imageBuffer = (__bridge CVImageBufferRef)imageBufferObject;
        }
        
        @synchronized(self){
            if (imageBufferObject) {
                [self.outputFrames removeObjectAtIndex:0];
            }
            if (insertionIndex) {
                [self.presentationTimes removeObjectAtIndex:0];
                if ([self.presentationTimes count] == 3) {
                    NSLog(@"====== start ======");
                    dispatch_semaphore_signal(self.bufferSemaphore);
                }
            }
        }
        
        if (imageBuffer) {
            NSLog(@"====== show ====== %lu", (unsigned long)self.presentationTimes.count);
            [self displayImage:imageBuffer];
        }

    }
}

(四)CVImageBuffer转换成UIImage

转换算法如本节开始的流程图所示,由 displayImage: 方法实现:

  1. CVPixelBufferLockBaseAddress
    -- 获取图像内部数据 --
  2. CVPixelBufferGetWidth
  3. CVPixelBufferGetHeight
  4. CVPixelBufferGetBytesPerRow
    -- 使用Core Graphics创建CGImage --
  5. CGColorSpaceCreateDeviceRGB
  6. CGBitmapContextCreate
  7. CGColorSpaceRelease
    -- CGImage转换成UIImage --
  8. CGBitmapContextCreateImage
  9. [UIImage imageWithCGImage:]
    -- 清理操作 --
  10. CGImageRelease
  11. CGContextRelease
  12. CVPixelBufferUnlockBaseAddress

(五)获取视频播放的当前帧图片

使用VideoToolbox解码可以直接从CVPixelBuffer生成图片,然而,本项目使用FFmpeg软解实现此功能。

技术分享

A. AVFrame转换成AVPicture

sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, picture.data, picture.linesize);

B. 以PPM格式保存图片至闪存(非必要操作)

-(void)savePicture:(AVPicture)pict width:(int)width height:(int)height index:(int)iFrame
{
    FILE *pFile;
    NSString *fileName;
    int  y;
    
    fileName = [Utilities documentsPath:[NSString stringWithFormat:@"image%04d.ppm",iFrame]];
    // Open file
    NSLog(@"write image file: %@",fileName);
    pFile=fopen([fileName cStringUsingEncoding:NSASCIIStringEncoding], "wb");
    if(pFile==NULL)
        return;
    
    // Write header
    fprintf(pFile, "P6\n%d %d\n255\n", width, height);
    
    // Write pixel data
    for(y=0; y<height; y++)
        fwrite(pict.data[0]+y*pict.linesize[0], 1, width*3, pFile);
    
    // Close file
    fclose(pFile);
}

C. AVPicture转UIImage

-(UIImage *)imageFromAVPicture:(AVPicture)pict width:(int)width height:(int)height
{
    CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
    CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, pict.data[0], pict.linesize[0]*height, kCFAllocatorNull);
    CGDataProviderRef provider = CGDataProviderCreateWithCFData(data);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGImageRef cgImage = CGImageCreate(width,
                                       height,
                                       8,
                                       24,
                                       pict.linesize[0],
                                       colorSpace,
                                       bitmapInfo,
                                       provider,
                                       NULL,
                                       NO,
                                       kCGRenderingIntentDefault);
    CGColorSpaceRelease(colorSpace);
    UIImage *image = [UIImage imageWithCGImage:cgImage];
    CGImageRelease(cgImage);
    CGDataProviderRelease(provider);
    CFRelease(data);
    
    return image;
}

(六)初始化音频

存在音频流时才初始化音频,由 -[SuperVideoFrameExtractor setupAudioDecoder] 完成,主要工作是分配缓冲区(_audioBuffer)内存,打开解码器和初始化AVAudioSession。

2 - 音视频输出

(一)视频

技术分享

play: 方法启动后台线程进行解码,每读取一个有效的视频包时调用VideoToolbox解码。在VideoToolbox回调函数中通过委托方法,将CVPixelBuffer转换成图片显示。

(二)音频

音频的输出由AVAudiosession实现,此项目并没实现音频输出。

3 - VideoToolbox解码

iOS 8开放了H.264硬件编解码接口VideoToolbox.framework,解码编程步骤为:

  1. VTDecompressionSessionCreate:创建解码会话
  2. VTDecompressionSessionDecodeFrame:解码一个视频帧
  3. VTDecompressionSessionInvalidate:释放解码会话

添加VideoToolbox.framework到工程并包含#include <VideoToolbox/VideoToolbox.h>开始硬解编程。

技术分享

硬解H.264前需配置VideoToolbox,简单地说,VideoToolbox只有了解输入数据源才能进行有效解码,我们要做的就是给它提 供H.264的SPS(Sequence Parameter Sets)和PPS(Picture Parameter Set)数据,创建出格式描述对象,由此创建解码会话。

(一)SPS与PPS

H.264的SPS和PPS包含了初始化H.264解码器所需要的信息参数,包括编码所用的profile,level,图像的宽和高,deblock滤波器等。

MP4或MPEG-TS格式的H.264数据可以从AVCodecContext的extradata中读取到SPS和PPS数据,如

uint8_t *data = pCodecCtx -> extradata;
int size = pCodecCtx -> extradata_size;

而以Elementary Stream形式从网络接收H.264裸流时,不存在单独的SPS、PPS包或帧,而是附加在I帧前面,存储的一般形式为 00 00 00 01 SPS 00 00 00 01 PPS 00 00 00 01 I帧,前面的这些00 00数据称为起始码(Start Code),它们不属于SPS、PPS的内容,只作标识。所以,创建CMFormatDescription时需要过滤它们。

由于VideoToolbox接口只接受MP4容器格式,当接收到Elementary Stream形式的H.264流,需把Start Code(3- or 4-Byte Header)换成Length(4-Byte Header)。

技术分享

  • Start Code表现形式:00 00 0100 00 00 01
  • Length表现形式:00 00 80 00

技术分享

处理流程为 00 00 0100 00 00 01 => 00 00 80 00(当前帧长度)。每个帧都需要处理。

技术分享

本项目给出URL为 rtsp://192.168.2.73:1935/vod/sample.mp4 ,它的处理方式为:

for (int i = 0; i < size; i++) {
    if (i >= 3) {
        if (data[i] == 0x01 && data[i-1] == 0x00 && data[i-2] == 0x00 && data[i-3] == 0x00) {
            if (startCodeSPSIndex == 0) {
                startCodeSPSIndex = i;
            }
            if (i > startCodeSPSIndex) {
                startCodePPSIndex = i;
            }
        }
    }
}

spsLength = startCodePPSIndex - startCodeSPSIndex - 4;
ppsLength = size - (startCodePPSIndex + 1);

nalu_type = ((uint8_t) data[startCodeSPSIndex + 1] & 0x1F);
if (nalu_type == 7/* Sequence parameter set (non-VCL) */) {
    spsData = [NSData dataWithBytes:&(data[startCodeSPSIndex + 1]) length: spsLength];
}

nalu_type = ((uint8_t) data[startCodePPSIndex + 1] & 0x1F);
if (nalu_type == 8/* Picture parameter set (non-VCL) */) {
    ppsData = [NSData dataWithBytes:&(data[startCodePPSIndex + 1]) length: ppsLength];
}

按我理解,由于是MP4容器,故只需提取SPS、PPS数据。

(二)创建视频格式描述对象

CMFormatDescription描述了视频的基本信息,有时也用CMVideoFormatDescriptionRef表示,typedef CMFormatDescriptionRef CMVideoFormatDescriptionRef; ,示意图如下。

技术分享

有两个接口可创建视频格式描述对象CMFormatDescriptionRef,本项目使用了 CMVideoFormatDescriptionCreateFromH264ParameterSets ,因为前面已处理SPS及PPS。

const uint8_t* const parameterSetPointers[2] = { (const uint8_t*)[spsData bytes], (const uint8_t*)[ppsData bytes] };
const size_t parameterSetSizes[2] = { [spsData length], [ppsData length] };
status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, parameterSetPointers, parameterSetSizes, 4, &videoFormatDescr);

另一个接口是不处理,以atom形式提供给VideoToolbox,由它自行处理,如

CFMutableDictionaryRef atoms = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks);
CFMutableDictionarySetData(atoms, CFSTR ("avcC"), (uint8_t *)extradata, extradata_size);
CFMutableDictionaryRef extensions = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFMutableDictionarySetObject(extensions, CFSTR ("SampleDescriptionExtensionAtoms"), (CFTypeRef *) atoms);

CMVideoFormatDescriptionCreate(NULL, format_id, width, height, extensions, &videoFormatDescr);

(三)创建解码会话

创建解码会话需要提供回调函数以便系统解码完成时将解码数据、状态等信息返回给用户。

VTDecompressionOutputCallbackRecord callback;
callback.decompressionOutputCallback = didDecompress;
callback.decompressionOutputRefCon = (__bridge void *)self;

回调函数原型为 void didDecompress( void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef imageBuffer, CMTime presentationTimeStamp, CMTime presentationDuration ),每解码一帧视频都调用一次此函数。

CVImageBufferRef与CVPixelBufferRef是同一个类型,typedef CVImageBufferRef CVPixelBufferRef ,依图像缓冲区类型,像素缓冲区为图像缓冲区提供内存存储空间。CVPixelBuffer持有图像信息的描述,如宽度、高度、像素格式类型,示意图如下:

技术分享

由于CVPixelBuffer的创建与释放属于耗性能操作,苹果提供了CVPixelBufferPool管理CVPixelBuffer,它在 后端提供了高效的CVPixelBuffer循环利用机制。CVPixelBufferPool维持了CVPixelBuffer的引用计数,当计数为0 时,将CVPixelBuffer收回它的循环利用队列,下次遇到创建CVPixelBuffer请求时,返回其中一个可用的 CVPixelBuffer,而非直接释放。

技术分享

创建解码会话时还需要提供一些解码指导信息,如已解码数据是否为OpenGL ES兼容、是否需要YUV转换RGB(此项一般不设置,OpenGL转换效率更高,VideoToolbox转换不仅需要使用更多内存,同时也消耗CPU)等等,如

NSDictionary *destinationImageBufferAttributes =[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO],(id)kCVPixelBufferOpenGLESCompatibilityKey,[NSNumber numberWithInt:kCVPixelFormatType_32BGRA],(id)kCVPixelBufferPixelFormatTypeKey,nil];

准备工作都完成了,现在正式创建解码会话 VTDecompressionSessionCreate(kCFAllocatorDefault, videoFormatDescr, NULL, (CFDictionaryRef)destinationImageBufferAttributes, &callback, &session); ,该会话在每次解码时都会被调用。

(四)解码

A. 解码前将AVPacket的数据(网络抽象层单元数据,NALU)拷贝到CMBlockBuffer:

int nalu_type = ((uint8_t)packet.data[startCodeIndex + 1] & 0x1F);
if (nalu_type == 1/* Coded slice of a non-IDR picture (VCL) */ || nalu_type == 5/* Coded slice of an IDR picture (VCL) */) {
	CMBlockBufferCreateWithMemoryBlock(NULL, packet.data, packet.size, kCFAllocatorNull, NULL, 0, packet.size, 0, &videoBlock);
}

CMBlockBuffer提供一种包装任意Core Media数据块的基本办法。在视频处理流水线遇到的压缩视频数据,几乎都被包装在CMBlockBuffer中。

B. 用4字节长度代码(4 byte length code (the length of the NalUnit including the unit code))替换分隔码(separator code)

int reomveHeaderSize = packet.size - 4;
const uint8_t sourceBytes[] = {(uint8_t)(reomveHeaderSize >> 24), (uint8_t)(reomveHeaderSize >> 16), (uint8_t)(reomveHeaderSize >> 8), (uint8_t)reomveHeaderSize};
status = CMBlockBufferReplaceDataBytes(sourceBytes, videoBlock, 0, 4);

C. 由CMBlockBuffer创建CMSampleBuffer

const size_t sampleSizeArray[] = {packet.size};
CMSampleBufferCreate(kCFAllocatorDefault, videoBlock, true, NULL, NULL, videoFormatDescr, 1, 0, NULL/* 可传递CMSampleTimingInfo */, 1, sampleSizeArray, &sbRef);

CMSampleBuffer包装了数据采样,就视频而言,CMSampleBuffer可包装压缩视频帧或未压缩视频帧,它组合了如下类 型:CMTime(采样的显示时间)、CMVideoFormatDescription(描述了CMSampleBuffer包含的数据)、 CMBlockBuffer(对于压缩视频帧)、CMSampleBuffer(未压缩光栅化图像,可能包含在CVPixelBuffer或 CMBlockBuffer),如图所示。

技术分享

D. 解码

VideoToolbox支持同、异步解码,由VTDecodeFrameFlags指定,VTDecodeFrameFlags flags = kVTDecodeFrame_EnableAsynchronousDecompression; ,默认为同步解码。

同步解码时,调用解码函数 VTDecompressionSessionDecodeFrame 后系统回调我们提供的回调函数,然后解码函数才结束调用。异步解码则回调顺序不确定,故需要自行整理帧序。

VTDecompressionSessionDecodeFrame(session, sbRef, flags, &sbRef, &flagOut);

(五)时间

项目有段未使用的代码,如下所示:

int32_t timeSpan = 90000;
CMSampleTimingInfo timingInfo;
timingInfo.presentationTimeStamp = CMTimeMake(0, timeSpan);
timingInfo.duration =  CMTimeMake(3000, timeSpan);
timingInfo.decodeTimeStamp = kCMTimeInvalid;

因为CMSampleBuffer只有图片数据,无时间信息,需要在解码时提供额外的时间说明。

CMTime是VideoToolbox中关于时间的基本描述,示意图如下。

技术分享

由于CMTime一直在增长,不好控制,苹果提供了易于控制的CMTimebase。

技术分享

(六)刷新正在解码的视频帧

/* Flush in-process frames. */
VTDecompressionSessionFinishDelayedFrames(session);
/* Block until our callback has been called with the last frame. */
VTDecompressionSessionWaitForAsynchronousFrames(session);

(七)清理资源

/* Clean up. */
VTDecompressionSessionInvalidate(session);
CFRelease(session);
CFRelease(videoFormatDescr);

4 - 总结

5 - 附录

输出AVPacket

- (void) dumpPacketData
{
    // Log dump
    int index = 0;
    NSString *tmp = [NSString new];
    for(int i = 0; i < packet.size; i++) {
        NSString *str = [NSString stringWithFormat:@" %.2X",packet.data[i]];
        if (i == 4) {
            NSString *header = [NSString stringWithFormat:@"%.2X",packet.data[i]];
            NSLog(@" header ====>> %@",header);
            if ([header isEqualToString:@"41"]) {
                NSLog(@"P Frame");
            }
            if ([header isEqualToString:@"65"]) {
                NSLog(@"I Frame");
            }
        }
        tmp = [tmp stringByAppendingString:str];
        index++;
        if (index == 16) {
            NSLog(@"%@",tmp);
            tmp = @"";
            index = 0;
        }
    }
}

内存清理

// Free scaler
sws_freeContext(img_convert_ctx);

// Free RGB picture
avpicture_free(&picture);

// Free the packet that was allocated by av_read_frame
av_free_packet(&packet);

// Free the YUV frame
av_free(pFrame);

// Close the codec
if (pCodecCtx) avcodec_close(pCodecCtx);

// Close the video file
if (pFormatCtx) avformat_close_input(&pFormatCtx);

定位到指定时间关键帧

- (void)seekTime:(double)seconds {
    AVRational timeBase = pFormatCtx->streams[videoStream]->time_base;
    int64_t targetFrame = (int64_t)((double)timeBase.den / timeBase.num * seconds);
    avformat_seek_file(pFormatCtx, videoStream, targetFrame, targetFrame, targetFrame, AVSEEK_FLAG_FRAME);
    avcodec_flush_buffers(pCodecCtx);
}

H.264基础

技术分享

SPS、PPS一旦设置,将被用于后续的NALU,只有更新它才会影响新的NALU解码,和OpenGL一样,状态设置后一直保持。

对于Elementary Stream,SPS和PPS包含在一个NALU中,方便回话。MP4则将它提取出来,放在文件头部,这样可支持随机访问。

技术分享

Elementary Stream与MP4容器格式之类的SPS、PPS转换。

技术分享

不同容器格式的NAL Unit头之间的区别。

技术分享

转换方法是,将Elementary Stream起始码换成长度,这在前面有描述。

AVSampleBufferDisplay

前面说过,CMTime难以控制,所以AVSampleBufferDisplay提供了CMTimebase的控制方式。

技术分享

SPS与PPS

0x67是SPS的NAL头,0x68是PPS的NAL头,举例:

[000]=0x00 [001]=0x00 [002]=0x00 [003]=0x01 [004]=0x67 [005]=0x64 [006]=0x00 [007]=0x32 [008]=0xAC [009]=0xB3 [010]=0x00 [011]=0xF0 [012]=0x04 [013]=0x4F [014]=0xCB [015]=0x08 [016]=0x00 [017]=0x00 [018]=0x03 [019]=0x00 [020]=0x08 [021]=0x00 [022]=0x00 [023]=0x03 [024]=0x01 [025]=0x94 [026]=0x78 [027]=0xC1 [028]=0x93 [029]=0x40 [030]=0x00 [031]=0x00 [032]=0x00 [033]=0x01 [034]=0x68 [035]=0xE9 [036]=0x73 [037]=0x2C [038]=0x8B [039]=0x00 [040]=0x00 [041]=0x01 [042]=0x65

成分为:

Start Code:0x00 0x00 0x00 0x01

SPS从[004]开始,长度为24:


0x67 0x64 0x00 0x32 0xAC 0xB3 0x00 0xF0 0x04 0x4F 0xCB 0x08 0x00 0x00 0x03 0x00 0x08 0x00 0x00 0x03 0x01 0x94 0x78 0xC1 0x93 0x40

PPS从[034]开始,长度为5:0x68 0xE9 0x73 0x2C 0x8B

SPS内容:

profile_idc = 66
constrained_set0_flag = 1
constrained_set1_flag = 1
constrained_set2_flag = 1
constrained_set3_flag = 0
level_idc = 20
seq_parameter_set_id = 0
chroma_format_idc = 1
bit_depth_luma_minus8 = 0
bit_depth_chroma_minus8 = 0
seq_scaling_matrix_present_flag = 0
log2_max_frame_num_minus4 = 0
pic_order_cnt_type = 2
log2_max_pic_order_cnt_lsb_minus4 = 0
delta_pic_order_always_zero_flag = 0
offset_for_non_ref_pic = 0
offset_for_top_to_bottom_field = 0
num_ref_frames_in_pic_order_cnt_cycle = 0
num_ref_frames = 1
gaps_in_frame_num_value_allowed_flag = 0
pic_width_in_mbs_minus1 = 21
pic_height_in_mbs_minus1 = 17
frame_mbs_only_flag = 1
mb_adaptive_frame_field_flag = 0
direct_8x8_interence_flag = 0
frame_cropping_flag = 0
frame_cropping_rect_left_offset = 0
frame_cropping_rect_right_offset = 0
frame_cropping_rect_top_offset = 0
frame_cropping_rect_bottom_offset = 0
vui_parameters_present_flag = 0

pic_width_in_mbs_minus1 = 21
pic_height_in_mbs_minus1 = 17
分别表示图像的宽和高,以宏块(16x16)为单位的值减1
因此,实际的宽为 (21+1)*16 = 352

PPS内容:

pic_parameter_set_id = 0
seq_parameter_set_id = 0
entropy_coding_mode_flag = 0
pic_order_present_flag = 0
num_slice_groups_minus1 = 0
slice_group_map_type = 0
num_ref_idx_l0_active_minus1 = 0
num_ref_idx_l1_active_minus1 = 0
weighted_pref_flag = 0
weighted_bipred_idc = 0
pic_init_qp_minus26 = 0
pic_init_qs_minus26 = 0
chroma_qp_index_offset = 10
deblocking_filter_control_present_flag = 1
constrained_intra_pred_flag = 0
redundant_pic_cnt_present_flag = 0
transform_8x8_mode_flag = 0
pic_scaling_matrix_present_flag = 0
second_chroma_qp_index_offset = 10

分析方法

67 42 e0 0a 89 95 42 c1 2c 80 (67为sps头)

0100 0010 1110 0000 0000 1010 1000 1001 1001 0101 0100 0010 11000001 0010 1100 1000 0000

FIELDNo. of BITSVALUECodeNum描述符
profile_idc 8 01000010 66 u(8)
constraint_set0_flag 1 1   u(1)
constraint_set1_flag 1 1   u(1)
constraint_set2_flag 1 1   u(1)
constraint_set3_flag 1 0   u(1)
reserved_zero_4bits 4 0000   u(4)
level_idc 8 00001010 10 u(8)
seq_parameter_set_id 1 1 0 ue(v)
log2_max_frame_num_minus4 7 0001001 8 ue(v)
pic_order_cnt_type 1 1 0 ue(v)
log2_max_pic_order_cnt_lsb_minus4 5 00101 4 ue(v)
num_ref_frames 3 010   ue(v)
gaps_in_frame_num_value_allowed_flag 1 1   u(1)
pic_width_in_mbs_minus1 9 000010110 20 ue(v)
pic_height_in_map_units_minus1 9 000010010 16 ue(v)
frame_mbs_only_flag 1 1 0 u(1)
mb_adaptive_frame_field_flag 1 1 0 u(1)
direct_8x8_inference_flag 1 0   u(1)
frame_cropping_flag 1 0   u(1)
vui_parameters_present_flag 1 1 0 u(1)

68 ce 05 8b 72 (68为pps头)
1100 1110 0000 0101 1000 1011 0111 0010 pps

FIELDNo. of BITSVALUECodeNum描述符
pic_parameter_set_id 1 1 0 ue(v)
seq_parameter_set_id 1 1 0 ue(v)
entropy_coding_mode_flag 1 0   ue(1)
pic_order_present_flag 1 0   ue(1)
num_slice_groups_minus1 1 1 0 ue(v)
num_ref_idx_l0_active_minus1 1 1 0 ue(v)
num_ref_idx_l1_active_minus1 1 1 0 ue(v)
weighted_pred_flag 1 0   ue(1)
weighted_bipred_idc 2 00   ue(2)
pic_init_qp_minus26 7 0001011 10(-5) se(v)
pic_init_qs_minus26 7 0001011 10(-5) se(v)
chroma_qp_index_offset 3 011 2(-1) se(v)
deblocking_filter_control_present_flag 1 1   ue(1)
constrained_intra_pred_flag 1 0   ue(1)
redundant_pic_cnt_present_flag 1 0   ue(1)

长度与起始码

rtsp

MP4

MP4封装格式对应标准为 ISO/IEC 14496-12(信息技术 视听对象编码的第12部分: ISO 基本媒体文件格式/Information technology Coding of audio-visual objects Part 12: ISO base media file format)

MP4封装格式是基于QuickTime容器格式定义,媒体描述与媒体数据分开,目前被广泛应用于封装h.264视频和ACC音频,是高清视频/HDV的代表。

参考

文章来自:http://www.cnblogs.com/sunminmin/p/4976418.html
© 2021 jiaocheng.bubufx.com  联系我们
ICP备案:鲁ICP备09046678号-3