#iphone #ios #avassetwriter
#iPhone #iOS #avassetwriter
Вопрос:
Я пытаюсь использовать AVAssetWriter для записи CGImages в файл для создания видео из изображений.
Я заставил это успешно работать тремя различными способами в симуляторе, но каждый метод завершается неудачей на iPhone 4 под управлением iOS 4.3.
Все это связано с пиксельными буферами.
Моим первым методом было просто создать пиксельные буферы по мере необходимости без использования пула. Это работает, но для работы на устройстве требуется слишком много памяти.
Мой второй метод состоял в том, чтобы использовать рекомендованный AVAssetWriterInputPixelBufferAdaptor, а затем извлекать пиксельные буферы из адаптеров pixelBufferPool с помощью CVPixelBufferPoolCreatePixelBuffer.
Это также работает на симуляторе, но не работает на устройстве, поскольку пул буферов пикселей адаптера никогда не выделяется. Я не получаю сообщений об ошибках.
Наконец, я попытался создать собственный пул буферов пикселей с помощью CVPixelBufferPoolCreate. Это также работает в симуляторе, но на устройстве все работает нормально, пока я не попытаюсь добавить пиксельный буфер с помощью appendPixelBuffer, который каждый раз терпит неудачу.
Я нашел очень минимальную информацию об этом в Интернете. Я основал свой код на примерах, которые я нашел, но вот уже несколько дней безуспешно. Если у кого-нибудь есть опыт успешного выполнения этого с помощью AVAssetWriter, пожалуйста, взгляните и дайте мне знать, если увидите что-нибудь неуместное.
ПРИМЕЧАНИЕ: вы увидите закомментированный блок попыток.
Во-первых, настройка
- (BOOL) openVideoFile: (NSString *) path withSize:(CGSize)imageSize {
size = CGSizeMake (480.0, 320.0);//imageSize;
NSError *error = nil;
videoWriter = [[AVAssetWriter alloc] initWithURL:
[NSURL fileURLWithPath:path] fileType:AVFileTypeQuickTimeMovie
error:amp;error];
if (error != nil)
return NO;
NSDictionary *videoCleanApertureSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithDouble:size.width], AVVideoCleanApertureWidthKey,
[NSNumber numberWithDouble:size.height], AVVideoCleanApertureHeightKey,
[NSNumber numberWithInt:10], AVVideoCleanApertureHorizontalOffsetKey,
[NSNumber numberWithInt:10], AVVideoCleanApertureVerticalOffsetKey,
nil];
NSDictionary *videoAspectRatioSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:1], AVVideoPixelAspectRatioHorizontalSpacingKey,
[NSNumber numberWithInt:1],AVVideoPixelAspectRatioVerticalSpacingKey,
nil];
NSDictionary *codecSettings = [NSDictionary dictionaryWithObjectsAndKeys:
//[NSNumber numberWithInt:960000], AVVideoAverageBitRateKey,
// [NSNumber numberWithInt:1],AVVideoMaxKeyFrameIntervalKey,
videoCleanApertureSettings, AVVideoCleanApertureKey,
videoAspectRatioSettings, AVVideoPixelAspectRatioKey,
//AVVideoProfileLevelH264Main31, AVVideoProfileLevelKey,
nil];
NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
AVVideoCodecH264, AVVideoCodecKey,
codecSettings,AVVideoCompressionPropertiesKey,
[NSNumber numberWithDouble:size.width], AVVideoWidthKey,
[NSNumber numberWithDouble:size.height], AVVideoHeightKey,
nil];
writerInput = [[AVAssetWriterInput
assetWriterInputWithMediaType:AVMediaTypeVideo
outputSettings:videoSettings] retain];
NSMutableDictionary * bufferAttributes = [[NSMutableDictionary alloc] init];
[bufferAttributes setObject: [NSNumber numberWithInt: kCVPixelFormatType_32ARGB]
forKey: (NSString *) kCVPixelBufferPixelFormatTypeKey];
[bufferAttributes setObject: [NSNumber numberWithInt: 480]
forKey: (NSString *) kCVPixelBufferWidthKey];
[bufferAttributes setObject: [NSNumber numberWithInt: 320]
forKey: (NSString *) kCVPixelBufferHeightKey];
//NSDictionary *bufferAttributes = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kCVPixelFormatType_32ARGB], kCVPixelBufferPixelFormatTypeKey, nil];
//[bufferAttributes setObject: [NSNumber numberWithInt: 640]
// forKey: (NSString *) kCVPixelBufferWidthKey];
//[bufferAttributes setObject: [NSNumber numberWithInt: 480]
// forKey: (NSString *) kCVPixelBufferHeightKey];
adaptor = [[AVAssetWriterInputPixelBufferAdaptor
assetWriterInputPixelBufferAdaptorWithAssetWriterInput:writerInput
sourcePixelBufferAttributes:nil] retain];
//CVPixelBufferPoolCreate (kCFAllocatorSystemDefault,NULL,(CFDictionaryRef)bufferAttributes,amp;pixelBufferPool);
//Create buffer pool
NSMutableDictionary* attributes;
attributes = [NSMutableDictionary dictionary];
int width = 480;
int height = 320;
[attributes setObject:[NSNumber numberWithInt:kCVPixelFormatType_32ARGB] forKey:(NSString*)kCVPixelBufferPixelFormatTypeKey];
[attributes setObject:[NSNumber numberWithInt:width] forKey: (NSString*)kCVPixelBufferWidthKey];
[attributes setObject:[NSNumber numberWithInt:height] forKey: (NSString*)kCVPixelBufferHeightKey];
CVReturn theError = CVPixelBufferPoolCreate(kCFAllocatorDefault, NULL, (CFDictionaryRef) attributes, amp;pixelBufferPool);
NSParameterAssert(writerInput);
NSParameterAssert([videoWriter canAddInput:writerInput]);
[videoWriter addInput:writerInput];
writerInput.expectsMediaDataInRealTime = YES;
//Start a session:
[videoWriter startWriting];
[videoWriter startSessionAtSourceTime:kCMTimeZero];
buffer = NULL;
lastTime = kCMTimeZero;
presentTime = kCMTimeZero;
return YES;
}
Далее, два метода, которые добавляют средство записи и создают пиксельный буфер для добавления.
- (void) writeImageToMovie:(CGImageRef)image
{
if([writerInput isReadyForMoreMediaData])
{
// CMTime frameTime = CMTimeMake(1, 20);
// CMTime lastTime=CMTimeMake(i, 20); //i is from 0 to 24 of the loop above
// CMTime presentTime=CMTimeAdd(lastTime, frameTime);
buffer = [self pixelBufferFromCGImage:image];
BOOL success = [adaptor appendPixelBuffer:buffer withPresentationTime:presentTime];
if (!success) NSLog(@"Failed to appendPixelBuffer");
CVPixelBufferRelease(buffer);
presentTime = CMTimeAdd(lastTime, CMTimeMake(5, 1000));
lastTime = presentTime;
}
else
{
NSLog(@"error - writerInput not ready");
}
}
- (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image
{
CVPixelBufferRef pxbuffer;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey,
[NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey,
nil];
if (pixelBufferPool == NULL) NSLog(@"pixelBufferPool is null!");
CVReturn status = CVPixelBufferPoolCreatePixelBuffer (NULL, pixelBufferPool, amp;pxbuffer);
/*if (pxbuffer == NULL) {
CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, size.width,
size.height, kCVPixelFormatType_32ARGB, (CFDictionaryRef) options,
amp;pxbuffer);
}*/
//NSParameterAssert(status == kCVReturnSuccess amp;amp; 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(90, 10, CGImageGetWidth(image),
CGImageGetHeight(image)), image);
CGColorSpaceRelease(rgbColorSpace);
CGContextRelease(context);
CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
return pxbuffer;
}
Комментарии:
1. Вау, это разочаровывает. Никто не имеет понятия?
Ответ №1:
Я нашел решение этой проблемы.
Если вы хотите, чтобы AVAudioPlayer и AVAssetWriter вели себя корректно вместе, у вас должна быть категория аудиосеанса «с возможностью микширования».
Вы можете использовать категорию, которую можно смешивать, например AVAudioSessionCategoryAmbient.
Тем не менее, мне нужно было использовать AVAudioSessionCategoryPlayAndRecord.
Вы можете настроить любую категорию для смешивания, реализовав это:
OSStatus propertySetError = 0;
UInt32 allowMixing = true;
propertySetError = AudioSessionSetProperty (
kAudioSessionProperty_OverrideCategoryMixWithOthers, // 1
sizeof (allowMixing), // 2
amp;allowMixing // 3
);
Комментарии:
1. Я не совсем понимаю, как это решение связано с вашим исходным кодом.
Ответ №2:
Ну, сначала вам нужно передать некоторые bufferAttributes
при создании этого объекта adapter:
NSDictionary *bufferAttributes = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey, nil];
AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor
assetWriterInputPixelBufferAdaptorWithAssetWriterInput:_videoWriterInput
sourcePixelBufferAttributes:bufferAttributes];
Затем удалите этот вызов CVPixelBufferPoolCreate
, в объекте adapter уже создан пул буферов пикселей, поэтому вместо этого вызовите только это:
CVPixelBufferRef pixelBuffer = NULL;
CVPixelBufferPoolCreatePixelBuffer(NULL, adaptor.pixelBufferPool, amp;pixelBuffer);
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
// ...fill the pixelbuffer here
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
CMTime frameTime = CMTimeMake(frameCount,(int32_t) 30);
BOOL res = [adaptor appendPixelBuffer:pixelBuffer withPresentationTime:frameTime];
CVPixelBufferRelease(pixelBuffer);
CFRelease(sampleBuffer);
Я думаю, что это должно сработать, в какой-то момент у меня была похожая ошибка, и я решил ее, создав адаптер и пиксельный буфер, как показано здесь..
Комментарии:
1. В дополнение к предложению выше, вы должны проверить, чтобы убедиться, что пиксельный буфер был успешно создан после вызова startSessionAtSourceTime в вашем коде. Ваш код должен проверять «if (adapter.pixelBufferPool == nil)», а затем обрабатывать случай сбоя, прежде чем переходить к циклу кодирования кадров.