gpt4 book ai didi

ios - 使用 VTDecompressionSession 的图像缓冲区显示顺序

转载 作者:可可西里 更新时间:2023-11-01 03:32:10 33 4
gpt4 key购买 nike

我有一个项目,我需要从实时网络流中解码 h264 视频,并最终得到可以在 iOS 设备上的另一个框架 (Unity3D) 中显示的纹理。我可以使用 VTDecompressionSession 成功解码视频,然后使用 CVMetalTextureCacheCreateTextureFromImage(或 OpenGL 变体)抓取纹理。当我使用低延迟编码器并且图像缓冲区按显示顺序出现时效果很好,但是,当我使用常规编码器时,图像缓冲区不会按显示顺序出现并且重新排序图像缓冲区显然要困难得多我预料到了。

第一次尝试是用 kVTDecodeFrame_EnableAsynchronousDecompression 和 kVTDecodeFrame_EnableTemporalProcessing 设置 VTDecodeFrameFlags...然而,事实证明 VTDecompressionSession 可以选择忽略该标志并做任何它想做的事...在我的例子中,它选择忽略该标志并且仍然以编码器顺序(而不是显示顺序)输出缓冲区。基本上没用。

下一次尝试是将图像缓冲区与呈现时间戳相关联,然后将它们放入一个向量中,这样我就可以在创建纹理时获取所需的图像缓冲区。问题似乎是进入与时间戳相关联的 VTDecompressionSession 的图像缓冲区不再是输出的同一缓冲区,基本上使时间戳变得无用。

例如,进入解码器...

  VTDecodeFrameFlags flags = kVTDecodeFrame_EnableAsynchronousDecompression;
VTDecodeInfoFlags flagOut;
// Presentation time stamp to be passed with the buffer
NSNumber *nsPts = [NSNumber numberWithDouble:pts];

VTDecompressionSessionDecodeFrame(_decompressionSession, sampleBuffer, flags,
(void*)CFBridgingRetain(nsPts), &flagOut);

在回调方面...

void decompressionSessionDecodeFrameCallback(void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef imageBuffer, CMTime presentationTimeStamp, CMTime presentationDuration)
{
// The presentation time stamp...
// No longer seems to be associated with the buffer that it went in with!
NSNumber* pts = CFBridgingRelease(sourceFrameRefCon);
}

排序后,回调端的时间戳以预期的速率单调增加,但缓冲区的顺序不正确。有人看到我在这里犯错了吗?或者知道如何确定回调端缓冲区的顺序?至此,我已经尝试了几乎所有我能想到的方法。

最佳答案

在我的例子中,问题不在于 VTDecompressionSession,而是解复用器获取错误 PTS 的问题。虽然我无法让 VTDecompressionSession 使用 kVTDecodeFrame_EnableAsynchronousDecompression 和 kVTDecodeFrame_EnableTemporalProcessing 标志按时间(显示)顺序排列帧,但我可以使用一个小向量根据 PTS 自己对帧进行排序。

首先,确保将所有计时信息与 CMSampleBuffer 以及 block 缓冲区相关联,以便在 VTDecompressionSession 回调中接收它。

// Wrap our CMBlockBuffer in a CMSampleBuffer...
CMSampleBufferRef sampleBuffer;

CMTime duration = ...;
CMTime presentationTimeStamp = ...;
CMTime decompressTimeStamp = ...;

CMSampleTimingInfo timingInfo{duration, presentationTimeStamp, decompressTimeStamp};

_sampleTimingArray[0] = timingInfo;
_sampleSizeArray[0] = nalLength;

// Wrap the CMBlockBuffer...
status = CMSampleBufferCreate(kCFAllocatorDefault, blockBuffer, true, NULL, NULL, _formatDescription, 1, 1, _sampleTimingArray, 1, _sampleSizeArray, &sampleBuffer);

然后,解码帧...值得尝试使用标志按显示顺序取出帧。

VTDecodeFrameFlags flags = kVTDecodeFrame_EnableAsynchronousDecompression | kVTDecodeFrame_EnableTemporalProcessing;
VTDecodeInfoFlags flagOut;

VTDecompressionSessionDecodeFrame(_decompressionSession, sampleBuffer, flags,
(void*)CFBridgingRetain(NULL), &flagOut);

在回调方面,我们需要一种方法来对收到的 CVImageBufferRefs 进行排序。我使用包含 CVImageBufferRef 和 PTS 的结构。然后是一个大小为 2 的向量,它将进行实际排序。

struct Buffer
{
CVImageBufferRef imageBuffer = NULL;
double pts = 0;
};

std::vector <Buffer> _buffer;

我们还需要一种方法来对缓冲区进行排序。始终写入和读取具有最低 PTS 的索引效果很好。

 -(int) getMinIndex
{
if(_buffer[0].pts > _buffer[1].pts)
{
return 1;
}

return 0;
}

在回调中,我们需要用缓冲区填充向量...

 void decompressionSessionDecodeFrameCallback(void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef imageBuffer, CMTime presentationTimeStamp, CMTime presentationDuration)
{
StreamManager *streamManager = (__bridge StreamManager *)decompressionOutputRefCon;

@synchronized(streamManager)
{
if (status != noErr)
{
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
NSLog(@"Decompressed error: %@", error);
}
else
{
// Get the PTS
double pts = CMTimeGetSeconds(presentationTimeStamp);

// Fill our buffer initially
if(!streamManager->_bufferReady)
{
Buffer buffer;

buffer.pts = pts;
buffer.imageBuffer = imageBuffer;

CVBufferRetain(buffer.imageBuffer);

streamManager->_buffer[streamManager->_bufferIndex++] = buffer;
}
else
{
// Push new buffers to the index with the lowest PTS
int index = [streamManager getMinIndex];

// Release the old CVImageBufferRef
CVBufferRelease(streamManager->_buffer[index].imageBuffer);

Buffer buffer;

buffer.pts = pts;
buffer.imageBuffer = imageBuffer;

// Retain the new CVImageBufferRef
CVBufferRetain(buffer.imageBuffer);

streamManager->_buffer[index] = buffer;
}

// Wrap around the buffer when initialized
// _bufferWindow = 2
if(streamManager->_bufferIndex == streamManager->_bufferWindow)
{
streamManager->_bufferReady = YES;
streamManager->_bufferIndex = 0;
}
}
}
}

最后,我们需要按时间(显示)顺序排空缓冲区...

 - (void)drainBuffer
{
@synchronized(self)
{
if(_bufferReady)
{
// Drain buffers from the index with the lowest PTS
int index = [self getMinIndex];

Buffer buffer = _buffer[index];

// Do something useful with the buffer now in display order
}
}
}

关于ios - 使用 VTDecompressionSession 的图像缓冲区显示顺序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33245023/

33 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com