来源: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:
方法实现:
- CVPixelBufferLockBaseAddress
-- 获取图像内部数据 -- - CVPixelBufferGetWidth
- CVPixelBufferGetHeight
- CVPixelBufferGetBytesPerRow
-- 使用Core Graphics创建CGImage -- - CGColorSpaceCreateDeviceRGB
- CGBitmapContextCreate
- CGColorSpaceRelease
-- CGImage转换成UIImage -- - CGBitmapContextCreateImage
- [UIImage imageWithCGImage:]
-- 清理操作 -- - CGImageRelease
- CGContextRelease
- 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,解码编程步骤为:
VTDecompressionSessionCreate
:创建解码会话VTDecompressionSessionDecodeFrame
:解码一个视频帧VTDecompressionSessionInvalidate
:释放解码会话
添加VideoToolbox.framework
到工程并包含#include
开始硬解编程。
硬解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 01
或00 00 00 01
- Length表现形式:
00 00 80 00
处理流程为 00 00 01
或 00 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
FIELD | No. of BITS | VALUE | CodeNum | 描述符 |
---|---|---|---|---|
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
FIELD | No. of BITS | VALUE | CodeNum | 描述符 |
---|---|---|---|---|
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的代表。
参考
- Direct Access to Video Encoding and Decoding
- How to use VideoToolbox to decompress H.264 video stream
- h.264 SPS和PPS分析方法
- 【流媒體】H264—MP4格式及在MP4文件中提取H264的SPS、PPS及码流