使用 VideoToolbox API (kVTVideoDecoderBadDataErr) 解码 H264 流
Decode an H264 stream using the VideoToolbox API (kVTVideoDecoderBadDataErr)
我的目标是使用 nvenc 对我的 Windows 机器的主帧缓冲区进行编码,并使用 VideoToolbox API
将其内容流式传输到我的 iPad
我用来编码 h264 流的代码基本上是 https://github.com/NVIDIA/video-sdk-samples/tree/master/nvEncDXGIOutputDuplicationSample 的 copy/paste,唯一的变化是我没有写入文件,而是发送数据
解码我用的是https://github.com/zerdzhong/SwfitH264Demo/blob/master/SwiftH264/ViewController.swift#L71
当我将所有内容写入文件时,编码工作完美,我可以毫无问题地使用 h264->mp4 在线转换器,问题是解码器在函数 decompressionSessionDecodeFrameCallback[ 中给我错误 kVTVideoDecoderBadDataErr =15=]
所以我尝试的是:
- 先用h264分析器发现帧序是:7/8/5/5/5/5/1...
- 我发现 nvenc 只在一个数据包中编码帧 7/8/5/5/5/5
- 我确实尝试使用序列 (0x00 0x00 0x00 0x01) 将此数据包分成多个,它分别给了我帧 7/8/5
- 如你所见,我只有一个 5 帧,大约 100KB,H264 分析器说有四个 5 帧(大约 40KB、20KB、30KB、10KB)
- 使用十六进制文件查看器,我看到分隔这 5 帧的序列是 (0x00 0x00 0x01),我也尝试将它们分开,但我在解压缩时遇到了完全相同的 VideoToolbox 错误
这是我用来分离和发送帧的代码:
该协议只是 PACKET_SIZE->PACKET_DATA
swift 代码能够读取 NALU 类型,所以我相信这不是问题所在
unsafe {
Setup();
loop {
CaptureFrame();
let frame_count = GetDataCount();
if frame_count == 0 {
continue;
}
for i in 0..frame_count {
let size = RetrieveDataSize(i as i32);
let size_slice = &(u32::to_le_bytes(size as u32));
let data = RetrieveData(i as i32);
let data_slice = std::slice::from_raw_parts(data, size);
let mut last_frame = 0;
for x in 0..size {
if data_slice[x] == 0 &&
data_slice[x + 1] == 0 &&
data_slice[x + 2] == 0 &&
data_slice[x + 3] == 1 {
let frame_size = x - last_frame;
if frame_size > 0 {
let frame_data = &data_slice[last_frame..x];
stream.write(&(u32::to_le_bytes(frame_size as u32))).unwrap();
stream.write(frame_data).unwrap();
println!("SEND MULTIPLE {}", frame_size);
}
last_frame = x;
println!("NALU {}", data_slice[x + 4] & 0x1F);
//println!("TEST {} {}",i, size);
continue;
}
}
// Packet was a single frame
let frame_size = size - last_frame;
let frame_data = &data_slice[last_frame..size];
stream.write(&(u32::to_le_bytes(frame_size as u32))).unwrap();
stream.write(frame_data).unwrap();
println!("SEND SINGLE {} {}", last_frame, size);
}
}
}
可能是纹理格式的问题,VideoToolbox提到了kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,而NVENC代码提到了YUV420和NV12,我不确定两者是否相同
这是我的格式说明:
Optional(<CMVideoFormatDescription 0x2823dd410 [0x1e0921e20]> {
mediaType:'vide'
mediaSubType:'avc1'
mediaSpecific: {
codecType: 'avc1' dimensions: 3840 x 2160
}
extensions: {{
CVFieldCount = 1;
CVImageBufferChromaLocationBottomField = Left;
CVImageBufferChromaLocationTopField = Left;
CVPixelAspectRatio = {
HorizontalSpacing = 1;
VerticalSpacing = 1;
};
FullRangeVideo = 0;
SampleDescriptionExtensionAtoms = {
avcC = {length = 41, bytes = 0x01640033 ffe10016 67640033 ac2b401e ... 68ee3cb0 fdf8f800 };
};
}}
})
好吧,虽然听起来很奇怪,但我的代码确实可以在模拟器上运行,但不能在我的 iPad pro 上运行。最后它确实有效,所以我仍然将其标记为正确答案
我的目标是使用 nvenc 对我的 Windows 机器的主帧缓冲区进行编码,并使用 VideoToolbox API
将其内容流式传输到我的 iPad我用来编码 h264 流的代码基本上是 https://github.com/NVIDIA/video-sdk-samples/tree/master/nvEncDXGIOutputDuplicationSample 的 copy/paste,唯一的变化是我没有写入文件,而是发送数据
解码我用的是https://github.com/zerdzhong/SwfitH264Demo/blob/master/SwiftH264/ViewController.swift#L71
当我将所有内容写入文件时,编码工作完美,我可以毫无问题地使用 h264->mp4 在线转换器,问题是解码器在函数 decompressionSessionDecodeFrameCallback[ 中给我错误 kVTVideoDecoderBadDataErr =15=]
所以我尝试的是:
- 先用h264分析器发现帧序是:7/8/5/5/5/5/1...
- 我发现 nvenc 只在一个数据包中编码帧 7/8/5/5/5/5
- 我确实尝试使用序列 (0x00 0x00 0x00 0x01) 将此数据包分成多个,它分别给了我帧 7/8/5
- 如你所见,我只有一个 5 帧,大约 100KB,H264 分析器说有四个 5 帧(大约 40KB、20KB、30KB、10KB)
- 使用十六进制文件查看器,我看到分隔这 5 帧的序列是 (0x00 0x00 0x01),我也尝试将它们分开,但我在解压缩时遇到了完全相同的 VideoToolbox 错误
这是我用来分离和发送帧的代码: 该协议只是 PACKET_SIZE->PACKET_DATA swift 代码能够读取 NALU 类型,所以我相信这不是问题所在
unsafe {
Setup();
loop {
CaptureFrame();
let frame_count = GetDataCount();
if frame_count == 0 {
continue;
}
for i in 0..frame_count {
let size = RetrieveDataSize(i as i32);
let size_slice = &(u32::to_le_bytes(size as u32));
let data = RetrieveData(i as i32);
let data_slice = std::slice::from_raw_parts(data, size);
let mut last_frame = 0;
for x in 0..size {
if data_slice[x] == 0 &&
data_slice[x + 1] == 0 &&
data_slice[x + 2] == 0 &&
data_slice[x + 3] == 1 {
let frame_size = x - last_frame;
if frame_size > 0 {
let frame_data = &data_slice[last_frame..x];
stream.write(&(u32::to_le_bytes(frame_size as u32))).unwrap();
stream.write(frame_data).unwrap();
println!("SEND MULTIPLE {}", frame_size);
}
last_frame = x;
println!("NALU {}", data_slice[x + 4] & 0x1F);
//println!("TEST {} {}",i, size);
continue;
}
}
// Packet was a single frame
let frame_size = size - last_frame;
let frame_data = &data_slice[last_frame..size];
stream.write(&(u32::to_le_bytes(frame_size as u32))).unwrap();
stream.write(frame_data).unwrap();
println!("SEND SINGLE {} {}", last_frame, size);
}
}
}
可能是纹理格式的问题,VideoToolbox提到了kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,而NVENC代码提到了YUV420和NV12,我不确定两者是否相同
这是我的格式说明:
Optional(<CMVideoFormatDescription 0x2823dd410 [0x1e0921e20]> {
mediaType:'vide'
mediaSubType:'avc1'
mediaSpecific: {
codecType: 'avc1' dimensions: 3840 x 2160
}
extensions: {{
CVFieldCount = 1;
CVImageBufferChromaLocationBottomField = Left;
CVImageBufferChromaLocationTopField = Left;
CVPixelAspectRatio = {
HorizontalSpacing = 1;
VerticalSpacing = 1;
};
FullRangeVideo = 0;
SampleDescriptionExtensionAtoms = {
avcC = {length = 41, bytes = 0x01640033 ffe10016 67640033 ac2b401e ... 68ee3cb0 fdf8f800 };
};
}}
})
好吧,虽然听起来很奇怪,但我的代码确实可以在模拟器上运行,但不能在我的 iPad pro 上运行。最后它确实有效,所以我仍然将其标记为正确答案