【发布时间】:2015-04-25 04:12:16
【问题描述】:
我正在尝试使用 AVSampleBufferDisplayLayer 来呈现通过 UDP 连接传来的基本 h264 流。 对于一个来源,我正在使用这个 gstreamer 命令:
gst-launch-1.0 -v videotestsrc is-live=true pattern-ball ! video/x-raw,width-120,height=90,framerate=15/1 ! x264enc tune=zerolatency ! h264parse ! video/x-h264,stream-format=byte-stream ! rtph264pay mtu=100000000 ! udpsink host=127.0.0.1 port=1234
客户端获取SPS和PPS参数后,读取流,将payload打包成CMBlockBuffer(带有正确的NAL头),将CMBlockBuffer打包成CMSampleBuffer,然后将CMSampleBuffer传递给AVSampleBufferDisplayLayer。 This post on using the AVSampleBufferDisplayLayer 提供了一个很好的执行示例,我的原型实现非常相似。对于这个基本情况,它的效果很好,而且我非常小的 (120x90) 视频也能完美呈现。
但是,当我尝试提高源视频的分辨率时,一切都崩溃了。 在 400x300 分辨率的视频中,我开始在每个访问单元分隔符数据包之间获取 4 个 NAL 单元(每个单元在一个单独的 UDP 数据包中)。我希望这些 NAL 单元中的每一个现在都代表最终帧的一部分,而不是整个帧。因此,我正在收集一帧的所有切片并将它们捆绑到单个 CMSampleBuffer 中以传递给 AVSampleBufferDisplayLayer。
捆绑切片时,AVSampleBufferDisplayLayer 不渲染任何内容。 当切片作为单独的 CMSampleBuffers 单独传递给图层时,AVSampleBufferDisplayLayer 显示大部分为绿色,顶部有一条闪烁的黑色条带。
这是在将数据包数据发送到 AVSampleBufferDisplayLayer 之前捆绑数据包数据的代码:
-(void)udpStreamSource:(UdpSource*)source didReceiveCodedSlicePacket:(NSData*)packet withPayloadOffset:(NSInteger)payloadOffset withPayloadLength:(NSInteger)payloadLength {
const size_t nalLength = payloadLength+4;
uint8_t *videoData = malloc(sizeof(uint8_t)*nalLength);
// first byte of payload is NAL header byte
[packet getBytes:videoData+4 range:NSMakeRange(payloadOffset, payloadLength)];
// prepend payloadLength to NAL
videoData[0] = (uint8_t)(payloadLength >> 24);
videoData[1] = (uint8_t)(payloadLength >> 16);
videoData[2] = (uint8_t)(payloadLength >> 8);
videoData[3] = (uint8_t) payloadLength;
sliceSizes[sliceCount] = nalLength;
sliceList[sliceCount++] = videoData;
}
-(void)udpStreamSource:(UdpSource*)source didReceiveAccessUnitDelimiter:(NSData*)packet {
if (sliceCount <= 0) {
return;
}
CMBlockBufferRef videoBlock = NULL;
OSStatus status = CMBlockBufferCreateWithMemoryBlock(NULL, sliceList[0], sliceSizes[0], NULL, NULL, 0, sliceSizes[0], 0, &videoBlock);
if (status != noErr) {
NSLog(@"CMBlockBufferCreateWithMemoryBlock failed with error: %d", status);
}
for (int i=1; i < sliceCount; i++) {
status = CMBlockBufferAppendMemoryBlock(videoBlock, sliceList[i], sliceSizes[i], NULL, NULL, 0, sliceSizes[i], 0);
if (status != noErr) {
NSLog(@"CMBlockBufferAppendMemoryBlock failed with error: %d", status);
}
}
CMSampleBufferRef sampleBuffer = NULL;
status = CMSampleBufferCreate(NULL, videoBlock, TRUE, NULL, NULL, _formatDescription, sliceCount, 0, NULL, sliceCount, sliceSizes, &sampleBuffer);
if (status != noErr) {
NSLog(@"CMSampleBufferCreate failed with error: %d", status);
}
CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);
CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
sliceCount = 0;
// videoLayer is an AVSampleBufferDisplayLayer
[videoLayer enqueueSampleBuffer:sampleBuffer];
[videoLayer setNeedsDisplay];
}
任何帮助或想法将不胜感激。
请注意,当我使用低分辨率视频源时,实现效果很好。只有当我提高视频源的分辨率时才会遇到问题。
我也尝试过使用 VTDecompressionSession 来解码数据。在这种情况下,解码器给了我没有错误的帧,但我无法弄清楚如何将它们渲染到屏幕上。
【问题讨论】:
-
你是不是推得太快了?你在使用 requestMediaDataWhenReadyOnQueue:usingBlock: 吗?您为 MTU 使用的高值是否会导致任何问题?
-
您好,我希望构建一个播放器来通过 UDP 播放来自 gstreamer 源的流,但不确定如何像使用
UDPSource那样获取流。您是否有更多示例代码可以分享,以了解如何让整个系统正常工作?谢谢! -
UDPSource 是我创建的一个非常基本的类。它主要只是包装一个 GCDAsyncUDPSocket (github.com/robbiehanson/CocoaAsyncSocket) 并根据数据包的 NAL 类型将数据包传递给代理的各种功能。
标签: ios video udp gstreamer h.264