AVAssetWriterInputPixelBufferAdaptor和CMTime

14

我正在使用 AVAssetWriterInputPixelBufferAdaptor 将一些帧写入视频,并且其时间行为不符合我的期望。

如果我只写入一帧:

 [videoWriter startSessionAtSourceTime:kCMTimeZero];
 [adaptor appendPixelBuffer:pxBuffer withPresentationTime:kCMTimeZero];

这会给我一个长度为零的视频,这正是我所期望的。

但是如果我继续添加第二帧:

 // 3000/600 = 5 sec, right?
 CMTime nextFrame = CMTimeMake(3000, 600); 
 [adaptor appendPixelBuffer:pxBuffer withPresentationTime:nextFrame];

我得到了十秒钟的视频,但我期望只有五秒。

这是怎么回事?withPresentationTime是否同时设置帧的开始和持续时间?

请注意,我没有调用endSessionAtSourceTime,只是调用了finishWriting


为什么不调用 endSessionAtSourceTime?我认为你必须这样做才能使导出正常工作(如果我记得代码正确的话)。 - zoul
2
根据文档endSessionAtSourceTime是可选的。 "如果您在不调用此方法的情况下调用finishWriting,则会话的有效结束时间将是会话样本的最新结束时间戳。" 但我不明白如何设置结束时间戳 - 感觉Apple忘记了一个参数。 - David Moles
嗯,我猜我让你偏离了轨道。(不过还是试试 endSessionAtSourceTime 吧。) - zoul
3个回答

6

请尝试查看此示例并进行逆向工程,以在5秒后添加1帧...

这是示例代码链接: git@github.com:RudyAramayo/AVAssetWriterInputPixelBufferAdaptorSample.git

以下是您需要的代码:

- (void) testCompressionSession
{
CGSize size = CGSizeMake(480, 320);


NSString *betaCompressionDirectory = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/Movie.m4v"];

NSError *error = nil;

unlink([betaCompressionDirectory UTF8String]);

//----initialize compression engine
AVAssetWriter *videoWriter = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:betaCompressionDirectory]
                                                       fileType:AVFileTypeQuickTimeMovie
                                                          error:&error];
NSParameterAssert(videoWriter);
if(error)
    NSLog(@"error = %@", [error localizedDescription]);

NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey,
                               [NSNumber numberWithInt:size.width], AVVideoWidthKey,
                               [NSNumber numberWithInt:size.height], AVVideoHeightKey, nil];
AVAssetWriterInput *writerInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];

NSDictionary *sourcePixelBufferAttributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
                                                       [NSNumber numberWithInt:kCVPixelFormatType_32ARGB], kCVPixelBufferPixelFormatTypeKey, nil];

AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:writerInput
                                                                                                                 sourcePixelBufferAttributes:sourcePixelBufferAttributesDictionary];
NSParameterAssert(writerInput);
NSParameterAssert([videoWriter canAddInput:writerInput]);

if ([videoWriter canAddInput:writerInput])
    NSLog(@"I can add this input");
else
    NSLog(@"i can't add this input");

[videoWriter addInput:writerInput];

[videoWriter startWriting];
[videoWriter startSessionAtSourceTime:kCMTimeZero];

//---
// insert demo debugging code to write the same image repeated as a movie

CGImageRef theImage = [[UIImage imageNamed:@"Lotus.png"] CGImage];

dispatch_queue_t    dispatchQueue = dispatch_queue_create("mediaInputQueue", NULL);
int __block         frame = 0;

[writerInput requestMediaDataWhenReadyOnQueue:dispatchQueue usingBlock:^{
    while ([writerInput isReadyForMoreMediaData])
    {
        if(++frame >= 120)
        {
            [writerInput markAsFinished];
            [videoWriter finishWriting];
            [videoWriter release];
            break;
        }

        CVPixelBufferRef buffer = (CVPixelBufferRef)[self pixelBufferFromCGImage:theImage size:size];
        if (buffer)
        {
            if(![adaptor appendPixelBuffer:buffer withPresentationTime:CMTimeMake(frame, 20)])
                NSLog(@"FAIL");
            else
                NSLog(@"Success:%d", frame);
            CFRelease(buffer);
        }
    }
}];

NSLog(@"outside for loop");

}


- (CVPixelBufferRef )pixelBufferFromCGImage:(CGImageRef)image size:(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, (CFDictionaryRef) options, &pxbuffer);
// CVReturn status = CVPixelBufferPoolCreatePixelBuffer(NULL, adaptor.pixelBufferPool, &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, kCGImageAlphaPremultipliedFirst);
NSParameterAssert(context);

CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image);

CGColorSpaceRelease(rgbColorSpace);
CGContextRelease(context);

CVPixelBufferUnlockBaseAddress(pxbuffer, 0);

return pxbuffer;
}

我正在尝试这段代码,但是对于第一个帧,我得到了成功的结果,而对于所有接下来的帧都失败了。我正在尝试每秒仅捕获两个帧(0、0.5、1、1.5秒),然后将它们附加到适配器中。有什么帮助吗? - nr5

0

你尝试过将这个作为你的第一个调用吗

CMTime t = CMTimeMake(0, 600);
[videoWriter startSessionAtSourceTime:t];
[adaptor appendPixelBuffer:pxBuffer withPresentationTime:t];

kCMTimeZero 代替?不,我没有想到。这会有什么区别吗? - David Moles
1
建议使用“是”代替“kCMTimeZero”。我提出这个建议的原因是两次调用之间的时间刻度将不同。kCMTimeZero的时间刻度为0,而您下一次调用的时间刻度为600。虽然0/时间刻度仍为0,但我不确定这是否会有所不同。无论如何,我建议尝试一下,因为AVFoundation可能直接使用时间刻度(例如,在MOV文件的头部设置属性)。 - Steve McFarlin

0
根据-[AVAssetWriterInput appendSampleBuffer:]方法的文档:
对于除音频轨道以外的轨道类型,为了确定输出文件中除最后一个附加的样本之外所有样本的持续时间,将使用样本缓冲区的输出DTS和下一个样本缓冲区的输出DTS之间的差异。最后一个样本的持续时间如下确定:
  1. 如果在最后一个媒体承载样本之后附加了带有kCMSampleBufferAttachmentKey_EndsPreviousSampleDuration标记的标记样本缓冲区,则将使用标记样本缓冲区的输出DTS和最后一个媒体承载样本的输出DTS之间的差异。
  2. 如果未提供标记样本缓冲区并且最后一个媒体承载样本的输出持续时间有效,则将使用它。
  3. 如果最后一个媒体承载样本的输出持续时间无效,则将使用倒数第二个样本的持续时间。
所以,基本上你处于情况#3:
  • 第一个样本的持续时间为 5s,基于第一个和第二个样本之间的PTS差异
  • 第二个样本的持续时间也是 5s,因为使用了倒数第二个样本的持续时间

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接