VideoToolbox使用说明

使用VideoToolbox硬编&硬解

VideoToolbox简介

VideoToolbox 是一个低级的框架,可直接访问硬件的编解码器。能够为视频提供压缩和解压缩的服务,同时也提供存储在 CoreVideo 像素缓冲区的图像进行格式的转换。

优点

  • 利用GPU或者专用处理器对视频流进行编解码,不用大量占用CPU资源。性能高,很好的实时性。

缺点

  • 低码率下通常质量低于软编

VideoToolbox数据

  1. CVPixelBuffer

    // CVPixelBuffer 与 CVImageBuffer 类型相同
    typealias CVPixelBuffer = CVImageBuffer
    

    CVPixelBuffer 是存储在内存中的一个未压缩的光栅图像 Buffer,包括图像的宽度、高度等。

  2. CMBlockBuffer

    CMBlockBuffer 是一个任意的 Buffer,相当于 Buffer 中的 Any. 在管道中压缩视频的时候,会把它包装成 CMBlockBuffer。相当于 CMBlockBuffer 代表着一个压缩的数据。

  3. CMSampleBuffer

    CMSampleBuffer 可能是一个压缩的数据,也可能是一个未压缩的数据。取决于 CMSampleBuffer 里面是 CMBlockBuffer(压缩后) 还是 CVPixelBuffer(未压缩)

对于VideoToolbox,可以通过直接访问硬编解码器,将 H.264 文件或传输流转换为 iOS上的 CMSampleBuffer 并解码成 CVPixelBuffer, 或将未压缩的 CVPixelBuffer 编码成 CMSampleBuffer(将未编码的CMSampleBuffer(CVPixelBuffer)与已编码的CMSampleBuffer(CMBlockBuffer)的相互转换):

解码

  • H.264 -> CMSampleBuffer -> CVPixelBuffer

编码:

  • CVPixelBuffer -> CMSampleBuffer -> H.264

解码

把原始码流包装成 CMSampleBuffer

解码前的原始数据为H264码流,iOS可以使用 NSInputStream 读取H264文件。

H264 有两种封装格式,一种为 MP4 格式,一种是annexb格式。MP4格式是以NALU的长度分割;annexb格式是以 0x00000001 或 0x0000000001 分割。

VideoToolbox解码使用的 H264 为MP4格式,因此需要替换NALU的Header

  • 使用 CMVideoFormatDescriptionCreateFromH264ParameterSets 将 SPS 和 PPS 封装成 CMVideoFormatDescription

    typealias CMVideoFormatDescription = CMFormatDescription
    
  • 修改 NALU 的 Header

    NALU 只要有两种格式:Annex B 和 AVCC。Annex B 格式以 0x 00 00 01 或 0x 00 00 00 01 开头, AVCC 格式以所在 NALU 的长度开头。

    替换掉NALU 的 StartCode

  • 使用 CMBlockBufferCreateWithMemoryBlock 接口将 NALU unit 封装成 CMBlockBuffer

  • 通过 CMSampleBufferCreate 将 CMBlockBuffer + CMVideoFormatDescription + CMTime 创建成 CMSampleBuffer

解码流程:

  1. 使用 VTDecompressionSessionCreate 创建解码会话

    VT_EXPORT OSStatus 
    VTDecompressionSessionCreate(
        // 会话的分配器,默认使用kCFAllocatorDefault 
     CM_NULLABLE CFAllocatorRef allocator,
        // 源视频帧的描述(包含SPS & PPS 信息)
     CM_NONNULL CMVideoFormatDescriptionRef videoFormatDescription,
        // 视频解码器(默认为空,由 VideoToolbox 选择)
     CM_NULLABLE CFDictionaryRef videoDecoderSpecification,
        // 包含解码配置信息的数组
     CM_NULLABLE CFDictionaryRef destinationImageBufferAttributes,
        // 回调函数
     const VTDecompressionOutputCallbackRecord * CM_NULLABLE outputCallback,
        // 解码会话对象的指针
     CM_RETURNS_RETAINED_PARAMETER CM_NULLABLE VTDecompressionSessionRef * CM_NONNULL decompressionSessionOut)
    
  2. 使用 VTSessionSetProperty 设置会话设置

    VTSessionSetProperty(
      // 解码会话
      CM_NONNULL VTSessionRef       session,
      // 属性 KEY
      CM_NONNULL CFStringRef        propertyKey,
      // 设置的属性值
      CM_NULLABLE CFTypeRef         propertyValue )
    
  3. 使用 VTDecompressionSessionDecodeFrame 编码视频帧,在之前设置的回调函数中获取编码后的结果

    VT_EXPORT OSStatus
    VTDecompressionSessionDecodeFrame(
        // 解码会话
     CM_NONNULL VTDecompressionSessionRef    session,
        // 要解码的视频数据(包含一个或多个视频帧)
     CM_NONNULL CMSampleBufferRef            sampleBuffer,
        // 解码器和解码会话的指令
     VTDecodeFrameFlags                      decodeFlags, 
        // 解码后的数据
     void * CM_NULLABLE                      sourceFrameRefCon,
     VTDecodeInfoFlags * CM_NULLABLE         infoFlagsOut)
    

    回调函数返回数据

    typedef void (*VTDecompressionOutputCallback)(
         // VTDecompressionOutputCallbackRecord 的 decompressionOutputRefCon字段值
         void * CM_NULLABLE decompressionOutputRefCon,
         // 解码返回的数据
         void * CM_NULLABLE sourceFrameRefCon,
         // 错误码
         OSStatus status, 
         // 解码操作的信息
         VTDecodeInfoFlags infoFlags,
         // 包含解压缩的帧数据
         CM_NULLABLE CVImageBufferRef imageBuffer,
         // 帧数据的时间戳
         CMTime presentationTimeStamp, 
         // 帧数据的表示时间
         CMTime presentationDuration );
    
  4. 使用 VTCompressionSessionCompleteFrames 强制结束并完成编码

  5. 编码完成后使用 VTCompressionSessionInvalidate 结束编码,并释放内存

编码

  1. 使用 VTDecompressionSessionCreate 创建 session(编码会话)

    VTCompressionSessionCreate(
        // 分配器,传NULL或KCFAllocatorDefault
     CM_NULLABLE CFAllocatorRef      allocator,
        // 宽度
     int32_t     width,
        // 高度
     int32_t     height,
        // 编码类型
     CMVideoCodecType  codecType,
        // 编码规范 传NULL,videotoolbox自行选择
     CM_NULLABLE CFDictionaryRef     encoderSpecification,
        // 源像素缓冲区
     CM_NULLABLE CFDictionaryRef     sourceImageBufferAttributes,
        // 压缩数据分配器
     CM_NULLABLE CFAllocatorRef      compressedDataAllocator,
        // 回调函数
     CM_NULLABLE VTCompressionOutputCallback             outputCallback,
        // 回调函数的引用
     void * CM_NULLABLE              outputCallbackRefCon,
        // 编码会话对象指针
     CM_RETURNS_RETAINED_PARAMETER CM_NULLABLE VTCompressionSessionRef * CM_NONNULL compressionSessionOut) 
    
  2. VTSessionSetProperty 配置相关属性

    设置一些例如码率、帧率、分辨率等属性

    • FPS(Frames PerSecond):每秒刷新的帧数。帧数越高,流畅度越高
    • 分辨率
    • 比特率/码率:表示经过编码(压缩)后的视频数据每秒钟需要用多少个比特来表示。比特率越高,视频的质量就越好;但编码后的文件也就越大。
  3. VTCompressionSessionPrepareToEncodeFrames 准备编码

    VTCompressionSessionPrepareToEncodeFrames(self.session);
    
  4. 调用VTCompressionSessionEncodeFrame传入需要编码的视频帧

    VTCompressionSessionEncodeFrame(
        // 编码会话
     CM_NONNULL VTCompressionSessionRef  session,
        // 要编码的数据
     CM_NONNULL CVImageBufferRef         imageBuffer,
        // 时间戳
     CMTime                              presentationTimeStamp,
        // 表示时间(may be kCMTimeInvalid)
     CMTime                              duration,
        // 数据的其他属性(key-value)
     CM_NULLABLE CFDictionaryRef         frameProperties,
        // 帧数据的引用,将被传递给回调函数
     void * CM_NULLABLE                  sourceFrameRefCon,
     VTEncodeInfoFlags * CM_NULLABLE     infoFlagsOut )
    
  5. 执行编码回调函数 VTCompressionOutputCallback

    如果是关键帧调用 CMSampleBufferGetFormatDescription 获取 CMFormatDescriptionRef,;

    然后用CMVideoFormatDescriptionGetH264ParameterSetAtIndex取得PPS和SPS;

    最后把每一帧的所有NALU数据前四个字节变成 0X00,00,00,01 之后再写入文件

    void didCompressionOutputCallback(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer) {
         //获取传入的参数
        VideoEncode *encode = (__bridge VideoEncode *)outputCallbackRefCon;
        
        //判断是否是关键帧
        CFArrayRef arrayRef = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true,);
    }
    
  6. 结束编码

    调用编码完成函数,将编码会话销毁,释放资源

    VTCompressionSessionCompleteFrames(session, KCMTimeInvalid);
    VTCompressionSessionInvalidate(session);
    CFRelease(session);
    session = NULL;
    frameID = 0;
    

读取H264文件,解码然后编码的Demo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容