ios 为什么老系统的手机h264硬件h264编解码编码时会session失败

公司项目原因,接触了一下视频流H264的编解码知识,之前项目使用的是FFMpeg多媒体库,利用CPU做视频的编码和解码,俗称为软编软解。该方法比较通用,但是占用CPU资源,编解码效率不高。一般系统都会提供GPU或者专用处理器来对视频流进行编解码,也就是硬件编码和解码,简称为硬编解码。苹果在iOS 8.0系统之前,没有开放系统的硬件编码解码功能,不过Mac OS系统一直有,被称为Video ToolBox的框架来处理硬件的编码和解码,终于在iOS 8.0后,苹果将该框架引入iOS系统。
由此,开发者便可以在iOS里面,调用Video Toolbox框架提供的接口,来对视频进行硬件编解码的工作,为VOIP视频通话,视频流播放等应用的视频编解码提供了便利。
(PS:按照苹果WWDC《direct access to media encoding and decoding》的描述,苹果之前提供的AVFoundation框架也使用硬件对视频进行硬编码和解码,但是编码后直接写入文件,解码后直接显示。Video Toolbox框架可以得到编码后的帧结构,也可以得到解码后的原始图像,因此具有更大的灵活性做一些视频图像处理。)
一,VideoToolbox基本数据结构。
Video Toolbox视频编解码前后需要应用的数据结构进行说明。
(1)CVPixelBuffer:编码前和解码后的图像数据结构。
(2)CMTime、CMClock和CMTimebase:时间戳相关。时间以64-bit/32-bit的形式出现。
(3)CMBlockBuffer:编码后,结果图像的数据结构。
(4)CMVideoFormatDescription:图像存储方式,编解码器等格式描述。
(5)CMSampleBuffer:存放编解码前后的视频图像的容器数据结构。
图1.1视频H264编解码前后数据结构示意图
如图1.1所示,编解码前后的视频图像均封装在CMSampleBuffer中,如果是编码后的图像,以CMBlockBuffe方式存储;解码后的图像,以CVPixelBuffer存储。CMSampleBuffer里面还有另外的时间信息CMTime和视频描述信息CMVideoFormatDesc。
二,硬解码使用方法。
通过如图2.1所示的一个典型应用,来说明如何使用硬件解码接口。该应用场景是从网络处传来H264编码后的视频码流,最后显示在手机屏幕上。
图2.1 H264典型应用场景
1,将H264码流转换成解码前的CMSampleBuffer。
由图1.1所示,解码前的CMSampleBuffer = CMTime + FormatDesc + CMBlockBuffer。需要从H264的码流里面提取出以上的三个信息。最后组合成CMSampleBuffer,提供给硬解码接口来进行解码工作。
H264的码流由NALU单元组成,NALU单元包含视频图像数据和H264的参数信息。其中视频图像数据就是CMBlockBuffer,而H264的参数信息则可以组合成FormatDesc。具体来说参数信息包含SPS(Sequence Parameter Set)和PPS(Picture Parameter Set)。图2.2显示一个H264码流的结构。
图2.2 H264码流结构
(1)提取sps和pps生成format description。
a,每个NALU的开始码是0x00 00 01,按照开始码定位NALU。
b,通过类型信息找到sps和pps并提取,开始码后第一个byte的后5位,7代表sps,8代表pps。
c,CMVideoFormatDescriptionCreateFromH264ParameterSets函数来构建CMVideoFormatDescriptionRef。具体代码可以见demo。
(2)提取视频图像数据生成CMBlockBuffer。
a,通过开始码,定位到NALU。
b,确定类型为数据后,将开始码替换成NALU的长度信息(4 Bytes)。
c,CMBlockBufferCreateWithMemoryBlock接口构造CMBlockBufferRef。具体代码可以见demo。
(3)根据需要,生成CMTime信息。(实际测试时,加入time信息后,有不稳定的图像,不加入time信息反而没有,需要进一步研究,这里建议不加入time信息)
根据上述得到CMVideoFormatDescriptionRef、CMBlockBufferRef和可选的时间信息,使用CMSampleBufferCreate接口得到CMSampleBuffer数据这个待解码的原始的数据。见图2.3的H264数据转换示意图。
图2.3 H264码流转换CMSampleBuffer示意图
2,硬件解码图像显示。
硬件解码显示的方式有两种:
(1)通过系统提供的AVSampleBufferDisplayLayer来解码并显示。
AVSampleBufferDisplayLayer是苹果提供的一个专门显示编码后的H264数据的显示层,它是CALayer的子类,因此使用方式和其它CALayer类似。该层内置了硬件解码功能,将原始的CMSampleBuffer解码后的图像直接显示在屏幕上面,非常的简单方便。图2.4显示了这一解码过程。
图2.4 AVSampleBufferDisplayLayer硬解压后显示图像
显示的接口为[_avslayer enqueueSampleBuffer:sampleBuffer];
(2)通过VTDecompression接口来,将CMSampleBuffer解码成图像,将图像通过UIImageView或者OpenGL上显示。
a,初始化VTDecompressionSession,设置解码器的相关信息。初始化信息需要CMSampleBuffer里面的FormatDescription,以及设置解码后图像的存储方式。demo里面设置的CGBitmap模式,使用RGB方式存放。编码后的图像经过解码后,会调用一个回调函数,将解码后的图像交个这个回调函数来进一步处理。我们就在这个回调里面,将解码后的图像发给control来显示,初始化的时候要将回调指针作为参数传给create接口函数。最后使用create接口对session来进行初始化。
b,a中所述的回调函数可以完成CGBitmap图像转换成UIImage图像的处理,将图像通过队列发送到Control来进行显示处理。
c,调用VTDecompresSessionDecodeFrame接口进行解码操作。解码后的图像会交由a,b步骤设置的回调函数,来进一步的处理。
图2.5显示来硬解码的过程步骤。
图2.5 VTDecompression硬解码过程示意图
三,硬编码使用方法。
硬编码的使用也通过一个典型的应用场景来描述。首先,通过摄像头来采集图像,然后将采集到的图像,通过硬编码的方式进行编码,最后编码后的数据将其组合成H264的码流通过网络传播。
1,摄像头采集数据。
摄像头采集,iOS系统提供了AVCaptureSession来采集摄像头的图像数据。设定好session的采集解析度。再设定好input和output即可。output设定的时候,需要设置delegate和输出队列。在delegate方法,处理采集好的图像。
注意,需要说明的是,图像输出的格式,是未编码的CMSampleBuffer形式。
2,使用VTCompressionSession进行硬编码。
(1)初始化VTCompressionSession。
VTCompressionSession初始化的时候,一般需要给出width宽,height长,编码器类型kCMVideoCodecType_H264等。然后通过调用VTSessionSetProperty接口设置帧率等属性,demo里面提供了一些设置参考,测试的时候发现几乎没有什么影响,可能需要进一步调试。最后需要设定一个回调函数,这个回调是视频图像编码成功后调用。全部准备好后,使用VTCompressionSessionCreate创建session。
(2)提取摄像头采集的原始图像数据给VTCompressionSession来硬编码。
摄像头采集后的图像是未编码的CMSampleBuffer形式,利用给定的接口函数CMSampleBufferGetImageBuffer从中提取出CVPixelBufferRef,使用硬编码接口VTCompressionSessionEncodeFrame来对该帧进行硬编码,编码成功后,会自动调用session初始化时设置的回调函数。
(3)利用回调函数,将因编码成功的CMSampleBuffer转换成H264码流,通过网络传播。
基本上是硬解码的一个逆过程。解析出参数集SPS和PPS,加上开始码后组装成NALU。提取出视频数据,将长度码转换成开始码,组长成NALU。将NALU发送出去。
图2.6显示了整个硬编码的处理逻辑。
图2.6硬编码处理流程示意图
四,硬编解码的一些编码说明。
由于Video Toolbox是基础的core Foundation库函数,C语言写成,和使用core Foundation所有的其它功能一样需要适应,记得Github有个同志,将其改成了OC语言能方便调用的模式,但是地址忘了,以后有缘找到,就会提供下链接。
阅读(...) 评论()ios8 - Decoding h264 in iOS 8 with video tool box - Stack Overflow
to customize your list.
Stack Overflow is a community of 4.7 million programmers, just like you, helping each other.
J it only takes a minute:
Join the Stack Overflow community to:
Ask programming questions
Answer and help your peers
Get recognized for your expertise
Need to decode h264 stream and get the pixel buffers
I know its possible with video tool box on iOS 8
1.How do I convert the h264 stream to CMSampleBufferRef ?
2.How do I use the video tool box to decode?
3,165101938
I assume you get the stream in Annex B format, if it is already in AVCC format (read MP4), then you can you the AssetReader and do not need to do much.
For an Annex B stream (this is what ppl. often call raw h264 stream).
extract SPS/PPS NAL units and create a parameter set from then. You receive them periodically. They contain information for the decode how a frame is supposed to be decoded.
create the TimingInfo Array with the duration (you can take it from parsing the VUI part of SPS) and presentation time stamp and decoding timestamp.Iif the stream is received as MPEG2 TS take the timestamps from the PESr. If not just supply missing info based on your calculations.
Wrap the VLC NAL units in a CMBlockBuffer. You can put more then one into them. If you receive your stream over RTP that might fragment the NAL units make sure every NAL unit is complete.
When wrapping the NAL unit in a CMBlockbuffer replace the 3- or 4-byte start code with a length header.
Supply the information to CMSampleBufferCreate and you can decode the frame in VTDecompressionSession
There is a presetation from WWDC available that explains these steps a bit more in detail ans also provide sample code.
Try this working code.Supply the encoded CMSampleBufferRef to sampleBuffer.
if(!decompressionSession)
CMFormatDescriptionRef formatDescription=CMSampleBufferGetFormatDescription(sampleBuffer);
decompressionSession = NULL;
VTDecompressionOutputCallbackRecord callBackR
callBackRecord.decompressionOutputCallback=didD
callBackRecord.decompressionOutputRefCon = (__bridge void *)
OSStatus status1= VTDecompressionSessionCreate(kCFAllocatorDefault, formatDescription, NULL, NULL, &callBackRecord, &decompressionSession);
VTDecodeFrameFlags flags = kVTDecodeFrame_EnableAsynchronousD
VTDecodeInfoFlags flagO
VTDecompressionSessionDecodeFrame(decompressionSession, sampleBuffer, flags, NULL, &flagOut);
VTDecompressionSessionWaitForAsynchronousFrames(decompressionSession);
Decompression all back
static void didDecompress( void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef imageBuffer, CMTime presentationTimeStamp, CMTime presentationDuration ){
if(status==noErr)
NSLog(@"SUCCESS PROCEED FROM HERE !!!!");
Keep in mind , you provide the correct presentation time while encoding.Here i am providing you the encoding details..
//ENCODING-------------------ENCODING---------------ENCODING
if(!_compression_session)
NSDictionary* pixelBufferOptions = @{
(NSString*) kCVPixelBufferWidthKey : @(widthOFCaptureImage),
(NSString*) kCVPixelBufferHeightKey : @(heightOFCaptureImage),
(NSString*) kCVPixelBufferOpenGLESCompatibilityKey : @YES,
(NSString*) kCVPixelBufferIOSurfacePropertiesKey : @{}};
_compression_session=NULL;
CFMutableDictionaryRef encoderSpecifications = NULL;
err = VTCompressionSessionCreate(
kCFAllocatorDefault,
widthOFCaptureImage,
heightOFCaptureImage,
kCMVideoCodecType_H264,
encoderSpecifications,
(__bridge CFDictionaryRef)pixelBufferOptions,
compressionCallback,
(__bridge void *)self,
&_compression_session);
CMTime presentationTimeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBufferIs);
CVPixelBufferRef pixelbufferPassing= CMSampleBufferGetImageBuffer(sampleBufferIs);
OSStatus status1= VTCompressionSessionEncodeFrame(_compression_session, pixelbufferPassing, presentationTimeStamp, kCMTimeInvalid, NULL, NULL, NULL);
VTCompressionSessionEndPass(_compression_session, NO, NULL);
//ENCODING CALL BACK-----------------------------------------
static void compressionCallback(void *outputCallbackRefCon,
void *sourceFrameRefCon,
OSStatus status,
VTEncodeInfoFlags infoFlags,
CMSampleBufferRef sampleBuffer ){
//Best wishes :) happy coding :)
Your Answer
Sign up or
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Post as a guest
By posting your answer, you agree to the
Not the answer you're looking for?
Browse other questions tagged
Stack Overflow works best with JavaScript enablediOS硬解H.264:-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
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_
return packet.pts * (double)timeBase.num / timeBase.
其实,有时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 =
id imageBufferObject =
@synchronized(self){
insertionIndex = [self.presentationTimes firstObject];
imageBufferObject = [self.outputFrames firstObject];
imageBuffer = (__bridge CVImageBufferRef)imageBufferO
@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
--&获取图像内部数据&--CVPixelBufferGetWidthCVPixelBufferGetHeightCVPixelBufferGetBytesPerRow
--&使用Core Graphics创建CGImage&--CGColorSpaceCreateDeviceRGBCGBitmapContextCreateCGColorSpaceRelease
--&CGImage转换成UIImage&--CGBitmapContextCreateImage[UIImage imageWithCGImage:]
--&清理操作&--CGImageReleaseCGContextReleaseCVPixelBufferUnlockBaseAddress
(五)获取视频播放的当前帧图片
使用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
NSString *fileN
fileName = [Utilities documentsPath:[NSString stringWithFormat:@&image%04d.ppm&,iFrame]];
C. AVPicture转UIImage
-(UIImage *)imageFromAVPicture:(AVPicture)pict width:(int)width height:(int)height
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderD
CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, pict.data[0], pict.linesize[0]*height, kCFAllocatorNull);
CGDataProviderRef provider = CGDataProviderCreateWithCFData(data);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGImageRef cgImage = CGImageCreate(width,
pict.linesize[0],
colorSpace,
bitmapInfo,
kCGRenderingIntentDefault);
CGColorSpaceRelease(colorSpace);
UIImage *image = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
CGDataProviderRelease(provider);
CFRelease(data);
(六)初始化音频
存在音频流时才初始化音频,由&-[SuperVideoFrameExtractor
setupAudioDecoder]&完成,主要工作是分配缓冲区(_audioBuffer)内存,打开解码器和初始化AVAudioSession。
2 - 音视频输出
(一)视频
play:&方法启动后台线程进行解码,每读取一个有效的视频包时调用VideoToolbox解码。在VideoToolbox回调函数中通过委托方法,将CVPixelBuffer转换成图片显示。
(二)音频
音频的输出由AVAudiosession实现,此项目并没实现音频输出。
3 - VideoToolbox解码
iOS 8开放了H.264硬件编解码接口VideoToolbox.framework,解码编程步骤为:
VTDecompressionSessionCreate:创建解码会话VTDecompressionSessionDecodeFrame:解码一个视频帧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 -&
int size = pCodecCtx -& extradata_
而以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 01Length表现形式: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
按我理解,由于是MP4容器,故只需提取SPS、PPS数据。
(二)创建视频格式描述对象
CMFormatDescription描述了视频的基本信息,有时也用CMVideoFormatDescriptionRef表示,typedef
CMFormatDescriptionRef CMVideoFormatDescriptionR&,示意图如下。
有两个接口可创建视频格式描述对象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.decompressionOutputCallback = didD
callback.decompressionOutputRefCon = (__bridge void *)
回调函数原型为&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
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
CMSampleBuffer包装了数据采样,就视频而言,CMSampleBuffer可包装压缩视频帧或未压缩视频帧,它组合了如下类 型:CMTime(采样的显示时间)、CMVideoFormatDescription(描述了CMSampleBuffer包含的数据)、 CMBlockBuffer(对于压缩视频帧)、CMSampleBuffer(未压缩光栅化图像,可能包含在CVPixelBuffer或 CMBlockBuffer),如图所示。
VideoToolbox支持同、异步解码,由VTDecodeFrameFlags指定,VTDecodeFrameFlags
flags = kVTDecodeFrame_EnableAsynchronousD&,默认为同步解码。
同步解码时,调用解码函数&VTDecompressionSessionDecodeFrame&后系统回调我们提供的回调函数,然后解码函数才结束调用。异步解码则回调顺序不确定,故需要自行整理帧序。
VTDecompressionSessionDecodeFrame(session,
sbRef, flags, &sbRef, &flagOut);
(五)时间
项目有段未使用的代码,如下所示:
int32_t timeSpan = 90000;
CMSampleTimingInfo timingI
timingInfo.presentationTimeStamp = CMTimeMake(0, timeSpan);
timingInfo.duration =
CMTimeMake(3000, timeSpan);
timingInfo.decodeTimeStamp = kCMTimeI
因为CMSampleBuffer只有图片数据,无时间信息,需要在解码时提供额外的时间说明。
CMTime是VideoToolbox中关于时间的基本描述,示意图如下。
由于CMTime一直在增长,不好控制,苹果提供了易于控制的CMTimebase。
(六)刷新正在解码的视频帧
(七)清理资源
输出AVPacket
- (void) dumpPacketData
// Free scaler
sws_freeContext(img_convert_ctx)
定位到指定时间关键帧
- (void)seekTime:(double)seconds {
AVRational timeBase = pFormatCtx-&streams[videoStream]-&time_
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);
SPS、PPS一旦设置,将被用于后续的NALU,只有更新它才会影响新的NALU解码,和OpenGL一样,状态设置后一直保持。
对于Elementary Stream,SPS和PPS包含在一个NALU中,方便回话。MP4则将它提取出来,放在文件头部,这样可支持随机访问。
Elementary Stream与MP4容器格式之类的SPS、PPS转换。
不同容器格式的NAL Unit头之间的区别。
转换方法是,将Elementary Stream起始码换成长度,这在前面有描述。
AVSampleBufferDisplay
前面说过,CMTime难以控制,所以AVSampleBufferDisplay提供了CMTimebase的控制方式。
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
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
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头)
10 10 01 10 0 00
profile_idc
constraint_set0_flag
constraint_set1_flag
constraint_set2_flag
constraint_set3_flag
reserved_zero_4bits
seq_parameter_set_id
log2_max_frame_num_minus4
pic_order_cnt_type
log2_max_pic_order_cnt_lsb_minus4
num_ref_frames
gaps_in_frame_num_value_allowed_flag
pic_width_in_mbs_minus1
pic_height_in_map_units_minus1
frame_mbs_only_flag
mb_adaptive_frame_field_flag
direct_8x8_inference_flag
frame_cropping_flag
vui_parameters_present_flag
68 ce 05 8b 72 (68为pps头)
pic_parameter_set_id
seq_parameter_set_id
entropy_coding_mode_flag
pic_order_present_flag
num_slice_groups_minus1
num_ref_idx_l0_active_minus1
num_ref_idx_l1_active_minus1
weighted_pred_flag
weighted_bipred_idc
pic_init_qp_minus26
pic_init_qs_minus26
chroma_qp_index_offset
deblocking_filter_control_present_flag
constrained_intra_pred_flag
redundant_pic_cnt_present_flag
长度与起始码
MP4封装格式对应标准为&
MP4封装格式是基于QuickTime容器格式定义,媒体描述与媒体数据分开,目前被广泛应用于封装h.264视频和ACC音频,是高清视频/HDV的代表。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:15724次
排名:千里之外
原创:11篇
转载:56篇
(2)(4)(13)(26)(1)(9)(2)(1)(1)(1)(6)(1)

我要回帖

更多关于 android h264硬件编码 的文章

 

随机推荐