2019独角兽企业重金招聘Python工程师标准>>>
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
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及码流