AVSampleBufferDisplayLayer

AVSampleBufferDisplayLayer

我正在尝试使用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参数之后,客户端读取流,将有效负载打包到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解码数据。在那种情况下,解码器会给我没有错误的回帧,但是我一直无法弄清楚如何将它们渲染到屏幕上。

最佳答案

事实证明,问题出在get-launch-1.0管道的“mtu = 100000000”部分。

Gstreamer将尝试发送大于网络处理能力的数据包。即使udp上的理论数据包大小限制为〜65k,网络(甚至OS)也可能施加其自己的“最大传输单元”限制。我确定我使用的网络的MTU为1492。

因此,为了使数据正确传输(并成功接收所有数据包),我不得不将MTU更改为较小的大小。我选择1400。在客户端,这意味着NAL单元将在FU-A型NAL结构中的多个RTP数据包中分段。因此,在将它们发送到解码器之前,我不得不将这些数据包聚合到其原始的全尺寸NAL中。

关于ios - 在iOS8中从gstreamer解码基本h264流,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/28680535/

10-12 19:14