Отображение декодированного видеопотока с MTKView приводит к нежелательному размытому выводу - PullRequest
1 голос
/ 05 апреля 2019

Мне удалось создать приложение, которое получает живой видеопоток в формате h264, а затем декодирует и отображает видео с помощью Video Toolbox и AVSampleBufferDisplayLayer. Это работает, как и ожидалось, но я хочу иметь возможность применять фильтры к отображаемому выводу, поэтому я переключился на декодирование с помощью Video Toolbox и отображение / рендеринг декодированного видео с помощью MetalKit. Единственная проблема, с которой я столкнулся, заключается в том, что мои выходные данные с помощью MetalKit заметно более размытые, чем выходные данные, полученные с помощью AVSampleBufferDisplayLayer, и мне не удалось выяснить, почему.

Вот вывод из AVSampleBufferDisplayLayer AVSampleBufferDisplayLayer

Вот вывод из MetalKit MetalKit

Я попытался пропустить MetalKit и выполнить рендеринг непосредственно в CAMetalLayer, но проблема сохраняется. Я пытаюсь преобразовать свой CVImageBufferRef в UIImage, который я могу отобразить с помощью UIView. Если это также в конечном итоге расплывчато, то, возможно, проблема связана с моей VTDecompressionSession, а не с металлической стороной вещей.

Декодирующая часть очень похожа на приведенную здесь Как использовать VideoToolbox для распаковки видеопотока H.264

Я постараюсь просто вставить интересные фрагменты моего кода.

Это параметры, которые я даю моей VTDecompressionSession.

NSDictionary *destinationImageBufferAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                                      [NSNumber numberWithInteger:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange],
                                                      (id)kCVPixelBufferPixelFormatTypeKey,
                                                      nil];

Это мой взгляд, который наследуется от MTKView

@interface StreamView : MTKView

@property id<MTLCommandQueue> commandQueue;
@property id<MTLBuffer> vertexBuffer;
@property id<MTLBuffer> colorConversionBuffer;
@property id<MTLRenderPipelineState> pipeline;
@property CVMetalTextureCacheRef textureCache;

@property CFMutableArrayRef imageBuffers;

-(id)initWithRect:(CGRect)rect withDelay:(int)delayInFrames;
-(void)addToRenderQueue:(CVPixelBufferRef)image renderAt:(int)frame;

@end

Вот как я инициализирую представление из моего контроллера представления. Видео, которое я получаю, имеет тот же размер, то есть 666x374.

streamView = [[StreamView alloc] initWithRect:CGRectMake(0, 0, 666, 374) withDelay:0];
[self.view addSubview:streamView];

Это содержимое метода initWithRect в StreamView

id<MTLDevice> device = MTLCreateSystemDefaultDevice();
self = [super initWithFrame:rect device:device];

self.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
self.commandQueue = [self.device newCommandQueue];
[self buildTextureCache];
[self buildPipeline];
[self buildVertexBuffers];

Это метод buildPipeline

- (void)buildPipeline
{
    NSBundle *bundle = [NSBundle bundleForClass:[self class]];
    id<MTLLibrary> library = [self.device newDefaultLibraryWithBundle:bundle error:NULL];

    id<MTLFunction> vertexFunc = [library newFunctionWithName:@"vertex_main"];
    id<MTLFunction> fragmentFunc = [library newFunctionWithName:@"fragment_main"];

    MTLRenderPipelineDescriptor *pipelineDescriptor = [MTLRenderPipelineDescriptor new];
    pipelineDescriptor.vertexFunction = vertexFunc;
    pipelineDescriptor.fragmentFunction = fragmentFunc;
    pipelineDescriptor.colorAttachments[0].pixelFormat = self.colorPixelFormat;

    self.pipeline = [self.device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:NULL];
}

Вот как я на самом деле рисую свою текстуру

CVImageBufferRef image = (CVImageBufferRef)CFArrayGetValueAtIndex(_imageBuffers, 0);

id<MTLTexture> textureY = [self getTexture:image pixelFormat:MTLPixelFormatR8Unorm planeIndex:0];
id<MTLTexture> textureCbCr = [self getTexture:image pixelFormat:MTLPixelFormatRG8Unorm planeIndex:1];
if(textureY == NULL || textureCbCr == NULL)
   return;

id<CAMetalDrawable> drawable = self.currentDrawable;

id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
MTLRenderPassDescriptor *renderPass = self.currentRenderPassDescriptor;
renderPass.colorAttachments[0].clearColor = MTLClearColorMake(0.5, 1, 0.5, 1);

id<MTLRenderCommandEncoder> commandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPass];
[commandEncoder setRenderPipelineState:self.pipeline];
[commandEncoder setVertexBuffer:self.vertexBuffer offset:0 atIndex:0];
[commandEncoder setFragmentTexture:textureY atIndex:0];
[commandEncoder setFragmentTexture:textureCbCr atIndex:1];
[commandEncoder setFragmentBuffer:_colorConversionBuffer offset:0 atIndex:0];
[commandEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4 instanceCount:1];
[commandEncoder endEncoding];

[commandBuffer presentDrawable:drawable];
[commandBuffer commit];

Вот так я конвертирую CVPixelBufferRef в текстуру MTL

- (id<MTLTexture>)getTexture:(CVPixelBufferRef)image pixelFormat:(MTLPixelFormat)pixelFormat planeIndex:(int)planeIndex {
    id<MTLTexture> texture;
    size_t width, height;

    if (planeIndex == -1)
    {
        width = CVPixelBufferGetWidth(image);
        height = CVPixelBufferGetHeight(image);
        planeIndex = 0;
    }
    else
    {
        width = CVPixelBufferGetWidthOfPlane(image, planeIndex);
        height = CVPixelBufferGetHeightOfPlane(image, planeIndex);
        NSLog(@"texture %d, %ld, %ld", planeIndex, width, height);
    }

    CVMetalTextureRef textureRef = NULL;
    CVReturn status = CVMetalTextureCacheCreateTextureFromImage(NULL, _textureCache, image, NULL, pixelFormat, width, height, planeIndex, &textureRef);
    if(status == kCVReturnSuccess)
    {
        texture = CVMetalTextureGetTexture(textureRef);
        CFRelease(textureRef);
    }
    else
    {
        NSLog(@"CVMetalTextureCacheCreateTextureFromImage failed with return stats %d", status);
        return NULL;
    }

    return texture;
}

Это мой фрагментный шейдер

fragment float4 fragment_main(Varyings in [[ stage_in ]],
                              texture2d<float, access::sample> textureY [[ texture(0) ]],
                              texture2d<float, access::sample> textureCbCr [[ texture(1) ]],
                              constant ColorConversion &colorConversion [[ buffer(0) ]])
{
    constexpr sampler s(address::clamp_to_edge, filter::linear);
    float3 ycbcr = float3(textureY.sample(s, in.texcoord).r, textureCbCr.sample(s, in.texcoord).rg);

    float3 rgb = colorConversion.matrix * (ycbcr + colorConversion.offset);

    return float4(rgb, 1.0);
}

Поскольку и код, и код, который я кодирую, имеют размер 666x374, я попытался изменить тип выборки в фрагментном шейдере на filter :: near. Я думал, что это будет соответствовать пикселям 1: 1, но все равно было размыто. Еще одна странная вещь, которую я заметил, заключается в том, что если вы откроете загруженные изображения в новой вкладке, вы увидите, что они намного больше, чем 666x374 ... Я сомневаюсь, что я делаю ошибку на стороне кодирования, и даже если я это сделал AVSampleBufferDisplayLayer по-прежнему удается отображать видео без размытия, поэтому они должны делать что-то правильно, что мне не хватает.

1 Ответ

1 голос
/ 09 апреля 2019

Похоже, у вас решена самая серьезная проблема с масштабированием вида, другие проблемы - это правильный рендеринг YCbCr (которого, похоже, вы избежите, выводя пиксели BGRA при декодировании), а затем происходит масштабирование исходного фильма до соответствовать размерам вида. Когда вы запрашиваете данные пикселей BGRA, данные кодируются как sRGB, поэтому вы должны обрабатывать данные в текстуре как sRGB. Metal автоматически выполняет нелинейное преобразование в линейное при чтении из текстуры sRGB, но вы должны сообщить Metal, что это пиксельные данные sRGB (используя MTLPixelFormatBGRA8Unorm_sRGB). Чтобы реализовать масштабирование, вам просто нужно визуализировать данные BGRA в представление с линейной передискретизацией. См. Вопрос SO, который я связал выше, если вы хотите взглянуть на исходный код для MetalBT709Decoder, который является моим собственным проектом, который реализует правильную визуализацию BT.709.

...