【问题标题】:How to get CVPixelBuffer handle from UnsafeMutablePointer<UInt8> in Swift?如何从 Swift 中的 UnsafeMutablePointer<UInt8> 获取 CVPixelBuffer 句柄?
【发布时间】:2019-08-12 15:30:19
【问题描述】:

我得到了一个解码的 AVFrame,其格式显示为 160/Videotoolbox_vld。在google了一些文章(here)并查看了FFmpeg源代码(herehere)后,CVBuffer句柄应该是AVFrame.data[3]。但我得到的CVBuffer 似乎无效,任何CVPixelBufferGetXXX() 函数都返回0 或nil。

如果我像 ffmpeg 的 example/hw_decode.c 那样使用 av_hwframe_transfer_data(),则可以将样本从硬件下载到软件缓冲区。它的AVFrame.format 将是nv12。通过sws_scale 转换为bgra 后,样本可以正确显示在视图中。

我认为 VideoToolbox 解码帧没问题。我将AVFrame.data[3] 转换为CVBuffer 的方式可能是错误的。刚刚学会了快速访问 c 指针,但我不确定如何正确读取指针中的资源句柄(CVBuffer)。

以下是我尝试从 AVFrame 中提取 CVBuffer 的方法

var pFrameOpt: UnsafeMutablePointer<AVFrame>? = av_frame_alloc()
avcodec_receive_frame(..., pFrameOpt)

let data3: UnsafeMutablePointer<UInt8>? = pFrameOpt?.pointee.data.3
data3?.withMemoryRebound(to: CVBuffer.self, capacity: 1) { pCvBuf in
    let fW = pFrameOpt!.pointee.width // print 3840
    let fH = pFrameOpt!.pointee.height // print 2160
    let fFmt = pFrameOpt!.pointee.format // print 160

    let cvBuf: CVBuffer = pCvBuf.pointee

    let a1 = CVPixelBufferGetDataSize(cvBuf)               // print 0
    let a2 = CVPixelBufferGetPixelFormatType(cvBuf)        // print 0
    let a3 = CVPixelBufferGetWidth(cvBuf)                  // print 0
    let a4 = CVPixelBufferGetHeight(cvBuf)                 // print 0
    let a5 = CVPixelBufferGetBytesPerRow(cvBuf)            // print 0
    let a6 = CVPixelBufferGetBytesPerRowOfPlane(cvBuf, 0)  // print 0
    let a7 = CVPixelBufferGetWidthOfPlane(cvBuf, 0)        // print 0
    let a8 = CVPixelBufferGetHeightOfPlane(cvBuf, 0)       // print 0
    let a9 = CVPixelBufferGetPlaneCount(cvBuf)             // print 0
    let a10 = CVPixelBufferIsPlanar(cvBuf)                 // print false
    let a11 = CVPixelBufferGetIOSurface(cvBuf)             // print nil
    let a12 = CVPixelBufferGetBaseAddress(cvBuf)           // print nil
    let a13 = CVPixelBufferGetBaseAddressOfPlane(cvBuf, 0) // print nil

    let b1 = CVImageBufferGetCleanRect(cvBuf)   // print 0, 0, 0, 0
    let b2 = CVImageBufferGetColorSpace(cvBuf)  // print nil 
    let b3 = CVImageBufferGetDisplaySize(cvBuf) // print 0, 0, 0, 0
    let b4 = CVImageBufferGetEncodedSize(cvBuf) // print 0, 0, 0, 0
    let b5 = CVImageBufferIsFlipped(cvBuf)      // print false

    // bad exec
    var cvTextureOut: CVMetalTexture?
    CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, ..., cvBuf, nil, .bgra8Unorm, 3840, 2160, 0, ...) 


}

【问题讨论】:

    标签: swift ffmpeg video-toolbox


    【解决方案1】:

    CVBuffer 不是固定大小,因此重新绑定内存不会以这种方式工作。你需要这样做:

    Unmanaged<CVBuffer>.fromOpaque(data!).takeRetainedValue()
    

    但是,最重要的是 FFmpeg 的 VideoToolbox 后端没有创建 CVPixelBuffer 并将 kCVPixelBufferMetalCompatibilityKey 设置为 true。在任何情况下,您都无法成功调用CVMetalTextureCacheCreateTextureFromImage(...)

    您可以考虑使用具有适当设置的CVPixelBufferPool(包括将kCVPixelBufferMetalCompatibilityKey 设置为true),然后使用VTPixelTransferSession 将FFmpeg 的像素缓冲区快速复制到您自己的。

    【讨论】:

      【解决方案2】:

      似乎我错误地将void* 转换为CVPixelBuffer*,而不是直接将void* 转换为CVPixelBuffer。我找不到一种快速的方法来进行从指针到数值的这种 c 风格转换。 (使用as! CVPixelBuffer 会导致崩溃)。

      所以我在 C 代码中为 void*CVPixelBufferRef 创建了一个函数来完成这种转换工作。

      // util.h
      #include <CoreVideo/CVPixelBuffer.h>
      CVPixelBufferRef CastToCVPixelBuffer(void* p);
      
      // util.c
      CVPixelBufferRef CastToCVPixelBuffer(void* p)
      {
          return (CVPixelBufferRef)p;
      }
      
      // BridgeHeader.h
      #include "util.h"
      

      然后将UnsafeMutablePointer&lt;UInt8&gt;传入,得到CVPixelBuffer句柄。

      let pFrameOpt: UnsafeMutablePointer<AVFrame>? = ...
      let data3: UnsafeMutablePointer<UInt8>? = pFrameOpt?.pointee.data.3
      
      let cvBuf: CVBuffer = CastToCVPixelBuffer(data3).takeUnretainedValue()
      
      let width = CVPixelBufferGetWidth(cvBuf)   // print 3840
      let height = CVPixelBufferGetHeight(cvBuf) // print 2160
      

      【讨论】:

      • 刚刚检查过。我的单行答案是正确的,也是最直接的方法,无需将 obj-c 加入其中。
      【解决方案3】:

      试试这个

      let cvBuf: CVBuffer = Array(UnsafeMutableBufferPointer(start: data3, count: 3))
      .withUnsafeBufferPointer {
         $0.baseAddress!.withMemoryRebound(to: CVBuffer.self, capacity: 1) { $0 }
      }.pointee
      

      甚至可能

      let cvBuf: CVBuffer = unsafeBitcast(UnsafeMutableBufferPointer(start: data3, count: 3), to: CVBuffer.self)
      

      【讨论】:

      • 我试过这个但失败了。所有 CVPixelXX Getter 函数都返回无效数据。我认为它只是直接转换为 CVPixelBuffer* 而不是 CVPixelBuffer。
      【解决方案4】:
      /**
          @function   CVPixelBufferGetBaseAddressOfPlane
          @abstract   Returns the base address of the plane at planeIndex in the PixelBuffer.
          @discussion Retrieving the base address for a PixelBuffer requires that the buffer base address be locked
                      via a successful call to CVPixelBufferLockBaseAddress. On OSX 10.10 and earlier, or iOS 8 and
                      earlier, calling this function with a non-planar buffer will have undefined behavior.
          @param      pixelBuffer Target PixelBuffer.
          @param      planeIndex  Identifying the plane.
          @result     Base address of the plane, or NULL for non-planar CVPixelBufferRefs.
      */
      @available(iOS 4.0, *)
      public func CVPixelBufferGetBaseAddressOfPlane(_ pixelBuffer: CVPixelBuffer, _ planeIndex: Int) -> UnsafeMutableRawPointer?
      

      也许你可以在使用 CVPixelBufferGetBaseAddressOfPlane 之前尝试使用 CVPixelBufferLockBaseAddress

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-10-20
        • 2014-05-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-05-15
        相关资源
        最近更新 更多