【发布时间】:2021-03-29 08:47:57
【问题描述】:
我正在尝试使用 Swift (macOS) 中的 VideoToolbox API 解码原始 H264 流。
在viewDidLoad() 中,我将显示层和 CMTimeBase 设置为:
self.view.wantsLayer = true
self.VideoLayer = AVSampleBufferDisplayLayer()
self.VideoLayer.frame = self.view.bounds
self.view.layer?.addSublayer(self.VideoLayer)
var _CMTimebasePointer: CMTimebase? = nil
let status = CMTimebaseCreateWithMasterClock(
allocator: kCFAllocatorDefault,
masterClock: CMClockGetHostTimeClock(),
timebaseOut: &_CMTimebasePointer)
self.VideoLayer.controlTimebase = _CMTimebasePointer
CMTimebaseSetTime(
self.VideoLayer.controlTimebase!,
time: CMTime.zero);
CMTimebaseSetRate(
self.VideoLayer.controlTimebase!,
rate: 1.0);
然后我将我的 H264 文件作为原始字节读取并解析为单独的 NALU。 (我在其他项目中与 NALU 解析器进行了交叉检查,我的 NALU 解析器是正确的,但是如果您认为我应该在这里发布它的代码,请发表评论,我会编辑我的问题 :))
这就是我处理每个 NALU 的方式(我基本上将 NALU 长度设置在前 4 个字节中(以转换为 avcC 格式),而对于 SPS 和 PPS NALU,我忽略了前 4 个字节。):
func decodeFrame(_ videoPacket: inout VideoPacket)
{
// replace start code with nal size
var biglen = CFSwapInt32HostToBig(UInt32(videoPacket.count - 4)) // NALU length doesn't contain the first 4 size bytes
memcpy(&videoPacket, &biglen, 4)
let nalType = videoPacket[4] & 0x1F
switch nalType
{
case 0x05:
// print("Nal type is IDR frame")
// inside this I create the format description and decompression session
createDecompressionSession()
decodeVideoPacket(videoPacket)
case 0x07:
// print("Nal type is SPS")
spsSize = videoPacket.count - 4
sps = Array(videoPacket[4..<videoPacket.count])
case 0x08:
// print("Nal type is PPS")
ppsSize = videoPacket.count - 4
pps = Array(videoPacket[4..<videoPacket.count])
default:
// print("Nal type is B/P frame: \(nalType)")
decodeVideoPacket(videoPacket)
break;
}
}
然后我像这样创建 VideoFormatDescription:
let pointerSPS = UnsafePointer<UInt8>(spsData)
let pointerPPS = UnsafePointer<UInt8>(ppsData)
// make pointers array
let dataParamArray = [pointerSPS, pointerPPS]
let parameterSetPointers = UnsafePointer<UnsafePointer<UInt8>>(dataParamArray)
// make parameter sizes array
let sizeParamArray = [spsData.count, ppsData.count]
let parameterSetSizes = UnsafePointer<Int>(sizeParamArray)
let status = CMVideoFormatDescriptionCreateFromH264ParameterSets(
allocator: kCFAllocatorDefault,
parameterSetCount: 2,
parameterSetPointers: parameterSetPointers,
parameterSetSizes: parameterSetSizes,
nalUnitHeaderLength: 4,
formatDescriptionOut: &self.VideoFormatDescription) // class variable
我将VTDecompressionSession 设为这样:
let decoderParameters = NSMutableDictionary()
let destinationPixelBufferAttributes = NSMutableDictionary()
destinationPixelBufferAttributes.setValue(
NSNumber(value: kCVPixelFormatType_32ARGB), // I've tried various values here to no avail...
forKey: kCVPixelBufferPixelFormatTypeKey as String
)
var outputCallback = VTDecompressionOutputCallbackRecord()
outputCallback.decompressionOutputCallback = decompressionSessionDecodeFrameCallback
outputCallback.decompressionOutputRefCon = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
let status = VTDecompressionSessionCreate(
allocator: kCFAllocatorDefault,
formatDescription: videoDescription,
decoderSpecification: decoderParameters,
imageBufferAttributes: destinationPixelBufferAttributes,
outputCallback: &outputCallback,
decompressionSessionOut: &self.DecompressionSession)
然后,这就是我解码每一帧的方式:
func decodeVideoPacket(_ videoPacket: VideoPacket)
{
let bufferPointer = UnsafeMutablePointer<UInt8>(mutating: videoPacket)
var blockBuffer: CMBlockBuffer?
var status = CMBlockBufferCreateWithMemoryBlock(
allocator: kCFAllocatorDefault,
memoryBlock: bufferPointer,
blockLength: videoPacket.count,
blockAllocator: kCFAllocatorNull,
customBlockSource: nil,
offsetToData: 0,
dataLength: videoPacket.count,
flags: 0,
blockBufferOut: &blockBuffer)
if status != noErr
{
print("CMBlockBufferCreateWithMemoryBlock ERROR: \(status)")
return
}
var sampleBuffer: CMSampleBuffer?
let sampleSizeArray = [videoPacket.count]
let frameFPS = Double(1) / Double(60)
let tval = Double(frameFPS * Double(self.frameCount))
let presentationTime = CMTimeMakeWithSeconds(tval, preferredTimescale: 1000)
var info = CMSampleTimingInfo(
duration: CMTimeMakeWithSeconds(frameFPS, preferredTimescale: 1000),
presentationTimeStamp: presentationTime,
decodeTimeStamp: presentationTime)
self.frameCount += 1
status = CMSampleBufferCreateReady(
allocator: kCFAllocatorDefault,
dataBuffer: blockBuffer,
formatDescription: self.VideoFormatDescription,
sampleCount: 1,
sampleTimingEntryCount: 1,
sampleTimingArray: &info,
sampleSizeEntryCount: 1,
sampleSizeArray: sampleSizeArray,
sampleBufferOut: &sampleBuffer)
if status != noErr
{
print("CMSampleBufferCreateReady ERROR: \(status)")
return
}
guard let buffer = sampleBuffer
else
{
print("Could not unwrap sampleBuffer!")
return
}
if self.VideoLayer.isReadyForMoreMediaData
{
self.VideoLayer?.enqueue(buffer)
self.VideoLayer.displayIfNeeded()
}
if let session = self.DecompressionSession
{
var outputBuffer: CVPixelBuffer?
status = VTDecompressionSessionDecodeFrame(
session,
sampleBuffer: buffer,
flags: [],
frameRefcon: &outputBuffer,
infoFlagsOut: nil)
if status != noErr
{
print("VTDecompressionSessionDecodeFrame ERROR: \(status)")
}
status = VTDecompressionSessionWaitForAsynchronousFrames(session)
if status != noErr
{
print("VTDecompressionSessionWaitForAsynchronousFrames ERROR: \(status)")
}
}
}
最后,在解码回调函数中,目前我只是尝试检查imageBuffer 是否为nil,但它始终是nil 并且OSStatus 始终设置为noErr
private func decompressionSessionDecodeFrameCallback(
_ decompressionOutputRefCon: UnsafeMutableRawPointer?,
_ sourceFrameRefCon: UnsafeMutableRawPointer?,
_ status: OSStatus,
_ infoFlags: VTDecodeInfoFlags,
_ imageBuffer: CVImageBuffer?,
_ presentationTimeStamp: CMTime,
_ presentationDuration: CMTime) -> Void
{
print("status: \(status), image_nil?: \(imageBuffer == nil)")
}
很明显,由于imageBuffer 是nil,我认为有问题...
(AVSampleBufferDisplayLayer 也不渲染任何图像)
你们能否帮我找出我的代码有什么问题,或者告诉我如何深入找出可能发生但对我隐藏的 VTDecompression 错误?
PS:让我知道我的代码中可能需要更多解释的地方
【问题讨论】:
-
您好,欢迎来到本站!这是一个非常写得很好的问题,我赞扬你!
-
您需要将收到的所有 NALU 传递给 H.264 解码器,包括 SPS 和 PPS NALU。 (您还可以将 SPS 和 PPS 放入解码器上下文中,就像您正在做的那样。)而且,一帧视频可以在多个 NALU 中编码。我认为,但我不确定,您的代码假定每个 VideoPacket 仅包含一个 NALU,因此您只对第一个进行定界符到长度(AnnexB 到 avcC)的转换。而且,我同意@Alexander。欢迎!我希望在这里看到您的更多贡献。
标签: ios swift macos h.264 video-toolbox