设置场景
我正在开发一个视频处理应用程序,该应用程序从命令行运行以读取、处理然后导出视频。 我正在使用 4 首曲目。
- 我把很多剪辑附加到一个轨道中,以制作一个视频。 我们称之为ugcVideoComposition。
- 带有 Alpha 的剪辑位于第二个轨道并使用图层指令,在导出时设置为合成,以便在 ugcVideoComposition 的顶部播放。
- 音乐音轨。
- 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 }