来源: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

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的代表。

参考

iOS硬解H.264:-VideoToolboxDemo源码分析[草稿]的更多相关文章

  1. 使用VideoToolbox硬编码H.264<转>

    文/落影loyinglin(简书作者)原文链接:http://www.jianshu.com/p/37784e363b8a著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”. ======= ...

  2. linux内存源码分析 - 零散知识点

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 直接内存回收中的等待队列 内存回收详解见linux内存源码分析 - 内存回收(整体流程),在直接内存回收过程中, ...

  3. jQuery-1.9.1源码分析系列完毕目录整理

    jQuery 1.9.1源码分析已经完毕.目录如下 jQuery-1.9.1源码分析系列(一)整体架构 jQuery-1.9.1源码分析系列(一)整体架构续 jQuery-1.9.1源码分析系列(二) ...

  4. MyBatis源码分析-MyBatis初始化流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  5. python3 整数类型PyLongObject 和PyObject源码分析

    python3 整数类型PyLongObject 和PyObject源码分析 一 测试环境介绍和准备 测试环境: 操作系统:windows10 Python版本:3.7.0 下载地址 VS版本:vs2 ...

  6. iOS学习——布局利器Masonry框架源码深度剖析

    iOS开发过程中很大一部分内容就是界面布局和跳转,iOS的布局方式也经历了 显式坐标定位方式 --> autoresizingMask --> iOS 6.0推出的自动布局(Auto La ...

  7. SpringMVC异常处理机制详解[附带源码分析]

    目录 前言 重要接口和类介绍 HandlerExceptionResolver接口 AbstractHandlerExceptionResolver抽象类 AbstractHandlerMethodE ...

  8. 《iOS开发指南》正式出版-源码-样章-目录,欢迎大家提出宝贵意见

    智捷iOS课堂-关东升老师最新作品:<iOS开发指南-从0基础到AppStore上线>正式出版了 iOS架构设计.iOS性能优化.iOS测试驱动.iOS调试.iOS团队协作版本控制.... ...

  9. Hadoop RCFile存储格式详解(源码分析、代码示例)

    RCFile   RCFile全称Record Columnar File,列式记录文件,是一种类似于SequenceFile的键值对(Key/Value Pairs)数据文件.   关键词:Reco ...

随机推荐

  1. MySQL8 修改密码验证插件

    MySQL8 修改密码验证插件 查看当前用户使用的密码验证插件 mysql> show variables like '%auth%'; +--------------------------- ...

  2. TCP/IP协议族(五)

    目前实际使用的网络模型是 TCP/IP 模型,它对 OSI 模型进行了简化,只包含了四层,从上到下分别是应用层.传输层.网络层和链路层(网络接口层),每一层都包含了若干协议. 协议(Protocol) ...

  3. static inline和inline的区别——stm32实测

    参考:http://armbbs.cn/forum.php?mod=viewthread&tid=95190&extra=page%3D1 对于内联函数,不能像普通函数那样,直接在.h ...

  4. Spring Batch 跑批框架

    SpringBatch的框架包括启动批处理作业的组件和存储Job执行产生的元数据. 如果作为一个批处理应用程序的开发人员,你暂时没有必要跟这些组件打交道, 因为它们主要为我们提供组件支持的角色,但是您 ...

  5. Manthan Codefest 19 题解

    这套题还是有点质量的吧 -- 题目链接 A. XORinacci 傻叉签到题,因为异或的性质所以这个序列的循环节长度只有 \(3\) -- 查看代码 B. Uniqueness 因为序列长度乃至数的种 ...

  6. 【转】Java代码编译过程简述

    转载:https://blog.csdn.net/fuzhongmin05/article/details/54880257. 代码编译是由Javac编译器来完成,流程如下图1所示: 图1 Javac ...

  7. 8.9 NOIP模拟测试15 建设城市(city)+轰炸行动(bomb)+石头剪刀布(rps)

    鉴于T3的惨烈程度,我决定先来颓篇题解. T1 建设城市(city) 挡板法+容斥 m个建设队分成n组,每组必须有一个,先不考虑上限,共有 C(m-1,n-1)种方案. 有i个组是超过k个的,容斥掉 ...

  8. [LeetCode] 694. Number of Distinct Islands 不同岛屿的个数

    Given a non-empty 2D array grid of 0's and 1's, an island is a group of 1's (representing land) conn ...

  9. [LeetCode] 5. Longest Palindromic Substring 最长回文子串

    Given a string s, find the longest palindromic substring in s. You may assume that the maximum lengt ...

  10. 公司ES升级带来的坑怎么填?

    前言 公司的ES最近需要全部进行升级,目的是方便维护和统一管理.以前的版本不统一,这次准备统一升级到一个固定的版本. 同时还会给ES加上权限控制,虽然都是部署在内网,为了防止误操作,加上权限还是有必要 ...