使用 AVFoundation (OSX) 为视频添加过滤器 - 如何将生成的图像写回 AVWriter



设置场景

我正在开发一个视频处理应用程序,该应用程序从命令行运行以读取、处理然后导出视频。 我正在使用 4 首曲目。

  1. 我把很多剪辑附加到一个轨道中,以制作一个视频。 我们称之为ugcVideoComposition。
  2. 带有 Alpha 的剪辑位于第二个轨道并使用图层指令,在导出时设置为合成,以便在 ugcVideoComposition 的顶部播放。
  3. 音乐音轨。
  4. ugcVideoComposition的音轨,包含附加到单个轨道中的剪辑中的音频。

我有这一切工作,可以合成它并使用AVExportSession正确导出它。

问题所在

我现在要做的是将滤镜和渐变应用于ugcVideoComposition。

到目前为止,我的研究表明,这是通过使用AVReader和AVWriter来完成的,提取CIImage,用过滤器操纵它,然后将其写出来。

我还没有获得上述所有功能的工作,但是我已经设法使用AssetReader和AssetWriter将ugcVideoComposition读入并写回磁盘。

    BOOL done = NO;
    while (!done)
    {
        while ([assetWriterVideoInput isReadyForMoreMediaData] && !done)
        {
            CMSampleBufferRef sampleBuffer = [videoCompositionOutput copyNextSampleBuffer];
            if (sampleBuffer)
            {
                // Let's try create an image....
                CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
                CIImage *inputImage = [CIImage imageWithCVImageBuffer:imageBuffer];
                // < Apply filters and transformations to the CIImage here
                // < HOW TO GET THE TRANSFORMED IMAGE BACK INTO SAMPLE BUFFER??? >
                // Write things back out.
                [assetWriterVideoInput appendSampleBuffer:sampleBuffer];
                CFRelease(sampleBuffer);
                sampleBuffer = NULL;
            }
            else
            {
                // Find out why we couldn't get another sample buffer....
                if (assetReader.status == AVAssetReaderStatusFailed)
                {
                    NSError *failureError = assetReader.error;
                    // Do something with this error.
                }
                else
                {
                    // Some kind of success....
                    done = YES;
                    [assetWriter finishWriting];
                }
            }
         }
      }

如您所见,我甚至可以从CMSampleBuffer获得CIImage,我相信我可以研究出如何操作图像并应用我需要的任何效果等。 我不知道该怎么做的是将生成的处理图像放回 SampleBuffer 中,以便我可以再次将其写出。

问题

给定一个 CIImage,我怎样才能把它放到一个 sampleBuffer 中,让它与 assetWriter 一起附加?

感谢任何帮助 - AVFoundation 文档很糟糕,要么错过了关键点(比如如何在提取图像后将其放回原处,要么专注于将图像渲染到 iPhone 屏幕,这不是我想要做的。

非常感谢和感谢!

我最终通过挖掘大量半完整的示例和 Apple 糟糕的 AVFoundation 文档找到了解决方案。

最大的困惑是,虽然在高级别,AVFoundation在iOS和OSX之间"合理地"一致,但较低级别的项目行为不同,具有不同的方法和不同的技术。 此解决方案适用于 OSX。

设置资产编写器

第一件事是确保在设置资产编写器时,添加一个适配器以从 CVPixelBuffer 读入。 此缓冲区将包含修改后的帧。

    // Create the asset writer input and add it to the asset writer.
    AVAssetWriterInput *assetWriterVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:[[videoTracks objectAtIndex:0] mediaType] outputSettings:videoSettings];
    // Now create an adaptor that writes pixels too!
    AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor
                                                   assetWriterInputPixelBufferAdaptorWithAssetWriterInput:assetWriterVideoInput
                                                 sourcePixelBufferAttributes:nil];
    assetWriterVideoInput.expectsMediaDataInRealTime = NO;
    [assetWriter addInput:assetWriterVideoInput];

阅读和写作

这里的挑战是我无法在iOS和OSX之间找到直接可比的方法 - iOS能够将上下文直接渲染到PixelBuffer,而OSX不支持该选项。 iOS 和 OSX 之间的上下文配置也不同。

请注意,您还应该将QuartzCore.Framework包含在XCode项目中。

在 OSX 上创建上下文。

    CIContext *context = [CIContext contextWithCGContext:
                      [[NSGraphicsContext currentContext] graphicsPort]
                                             options: nil]; // We don't want to always create a context so we put it outside the loop

现在你想要循环遍历,读取资产读取器并写入资产写入器...但请注意,您是通过之前创建的适配器写入的,而不是使用 SampleBuffer。

    while ([adaptor.assetWriterInput isReadyForMoreMediaData] && !done)
    {
        CMSampleBufferRef sampleBuffer = [videoCompositionOutput copyNextSampleBuffer];
        if (sampleBuffer)
        {
            CMTime currentTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
            // GRAB AN IMAGE FROM THE SAMPLE BUFFER
            CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
            NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey,
                                     [NSNumber numberWithInt:640.0], kCVPixelBufferWidthKey,
                                     [NSNumber numberWithInt:360.0], kCVPixelBufferHeightKey,
                                     nil];
            CIImage *inputImage = [CIImage imageWithCVImageBuffer:imageBuffer options:options];
            //-----------------
            // FILTER IMAGE - APPLY ANY FILTERS IN HERE
            CIFilter *filter = [CIFilter filterWithName:@"CISepiaTone"];
            [filter setDefaults];
            [filter setValue: inputImage forKey: kCIInputImageKey];
            [filter setValue: @1.0f forKey: kCIInputIntensityKey];
            CIImage *outputImage = [filter valueForKey: kCIOutputImageKey];

            //-----------------
            // RENDER OUTPUT IMAGE BACK TO PIXEL BUFFER
            // 1. Firstly render the image
            CGImageRef finalImage = [context createCGImage:outputImage fromRect:[outputImage extent]];
            // 2. Grab the size
            CGSize size = CGSizeMake(CGImageGetWidth(finalImage), CGImageGetHeight(finalImage));
            // 3. Convert the CGImage to a PixelBuffer
            CVPixelBufferRef pxBuffer = NULL;
            // pixelBufferFromCGImage is documented below.
            pxBuffer = [self pixelBufferFromCGImage: finalImage andSize: size];
            // 4. Write things back out.
            // Calculate the frame time
            CMTime frameTime = CMTimeMake(1, 30); // Represents 1 frame at 30 FPS
            CMTime presentTime=CMTimeAdd(currentTime, frameTime); // Note that if you actually had a sequence of images (an animation or transition perhaps), your frameTime would represent the number of images / frames, not just 1 as I've done here.
            // Finally write out using the adaptor.
            [adaptor appendPixelBuffer:pxBuffer withPresentationTime:presentTime];
            CFRelease(sampleBuffer);
            sampleBuffer = NULL;
        }
        else
        {
            // Find out why we couldn't get another sample buffer....
            if (assetReader.status == AVAssetReaderStatusFailed)
            {
                NSError *failureError = assetReader.error;
                // Do something with this error.
            }
            else
            {
                // Some kind of success....
                done = YES;
                [assetWriter finishWriting];
            }
        }
    }
}

创建像素缓冲区

一定有一种更简单的方法,但是就目前而言,这是有效的,并且是我发现直接从 CIImage 到 OSX 上的 PixelBuffer(通过 CGImage)的唯一方法。 以下代码是从 AVFoundation + AssetWriter 剪切和粘贴的: 生成带有图像和音频的电影

    - (CVPixelBufferRef) pixelBufferFromCGImage: (CGImageRef) image andSize:(CGSize) size
    {
        NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                         [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey,
                         [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey,
                         nil];
        CVPixelBufferRef pxbuffer = NULL;
        CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, size.width,
                                      size.height, kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef) options,
                                      &pxbuffer);
        NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL);
        CVPixelBufferLockBaseAddress(pxbuffer, 0);
        void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);
        NSParameterAssert(pxdata != NULL);
        CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
        CGContextRef context = CGBitmapContextCreate(pxdata, size.width,
                                             size.height, 8, 4*size.width, rgbColorSpace,
                                             kCGImageAlphaNoneSkipFirst);
        NSParameterAssert(context);
        CGContextConcatCTM(context, CGAffineTransformMakeRotation(0));
        CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image),
                                       CGImageGetHeight(image)), image);
        CGColorSpaceRelease(rgbColorSpace);
        CGContextRelease(context);
        CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
        return pxbuffer;
    }

尝试使用: SDAVAssetExportSession

SDAVAssetExportSession on GITHub

然后实现委托来处理像素

- (void)exportSession:(SDAVAssetExportSession *)exportSession renderFrame:(CVPixelBufferRef)pixelBuffer withPresentationTime:(CMTime)presentationTime toBuffer:(CVPixelBufferRef)renderBuffer
{ Do CIImage and CIFilter inside here }

相关内容

  • 没有找到相关文章

最新更新