#iphone #avfoundation #avassetwriter #avmutablecomposition
#iPhone #avfoundation #avassetwriter #изменяемая композиция
Вопрос:
Мне нужно экспортировать фильм из моего приложения для iPhone, которое содержит UIImage из NSArray, и добавить несколько аудиофайлов в формате .caf, которые должны начинаться в заранее указанное время. Теперь я смог использовать AVAssetWriter (после просмотра множества вопросов и ответов на этом и других сайтах) для экспорта части видео, содержащей изображения, но, похоже, не могу найти способ добавить аудиофайлы для завершения фильма.
Вот что у меня получилось на данный момент
-(void) writeImagesToMovieAtPath:(NSString *) path withSize:(CGSize) size
{
NSLog(@"Write Started");
NSError *error = nil;
AVAssetWriter *videoWriter = [[AVAssetWriter alloc] initWithURL:
[NSURL fileURLWithPath:path] fileType:AVFileTypeQuickTimeMovie
error:amp;error];
NSParameterAssert(videoWriter);
NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
AVVideoCodecH264, AVVideoCodecKey,
[NSNumber numberWithInt:size.width], AVVideoWidthKey,
[NSNumber numberWithInt:size.height], AVVideoHeightKey,
nil];
AVAssetWriterInput* videoWriterInput = [[AVAssetWriterInput
assetWriterInputWithMediaType:AVMediaTypeVideo
outputSettings:videoSettings] retain];
AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor
assetWriterInputPixelBufferAdaptorWithAssetWriterInput:videoWriterInput
sourcePixelBufferAttributes:nil];
NSParameterAssert(videoWriterInput);
NSParameterAssert([videoWriter canAddInput:videoWriterInput]);
videoWriterInput.expectsMediaDataInRealTime = YES;
[videoWriter addInput:videoWriterInput];
//Start a session:
[videoWriter startWriting];
[videoWriter startSessionAtSourceTime:kCMTimeZero];
CVPixelBufferRef buffer = NULL;
//convert uiimage to CGImage.
int frameCount = 0;
for(UIImage * img in imageArray)
{
buffer = [self pixelBufferFromCGImage:[img CGImage] andSize:size];
BOOL append_ok = NO;
int j = 0;
while (!append_ok amp;amp; j < 30)
{
if (adaptor.assetWriterInput.readyForMoreMediaData)
{
printf("appending %d attemp %dn", frameCount, j);
CMTime frameTime = CMTimeMake(frameCount,(int32_t) kRecordingFPS);
append_ok = [adaptor appendPixelBuffer:buffer withPresentationTime:frameTime];
if(buffer)
CVBufferRelease(buffer);
[NSThread sleepForTimeInterval:0.05];
}
else
{
printf("adaptor not ready %d, %dn", frameCount, j);
[NSThread sleepForTimeInterval:0.1];
}
j ;
}
if (!append_ok) {
printf("error appending image %d times %dn", frameCount, j);
}
frameCount ;
}
}
//Finish the session:
[videoWriterInput markAsFinished];
[videoWriter finishWriting];
NSLog(@"Write Ended");
}
А теперь код для pixelBufferFromCGImage
- (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, (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(0, 0, CGImageGetWidth(image),
CGImageGetHeight(image)), image);
CGColorSpaceRelease(rgbColorSpace);
CGContextRelease(context);
CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
return pxbuffer;
}
Итак, можете ли вы помочь мне относительно того, как добавлять аудиофайлы и как создавать буферы для них, а также настройки адаптера и ввода и т.д.
Если этот подход может вызвать проблему, подскажите мне, как использовать AVMutableComposition для использования массива изображений для экспорта видео
Комментарии:
1. Хорошо, я смог добавить аудиофайлы, используя AVAssetReaders и AVAssetWriterInputs, однако, когда я добавляю аудиофайлы, они запускаются один за другим без какой-либо паузы (один заканчивается, а следующий запускается) вместо того, чтобы начинаться в заранее определенное время, итак, как мне сказать AVAssetWriter принимать входные данные в определенное время. Это потому, что, как я понимаю, [startSessionAtSourceTime] предназначен для определения времени источника, а не времени в конечном фильме, поэтому любые подсказки
2. Вы великолепны, что публикуете такие подробные решения для других.
3. работает ли это также с изображениями 1080 * 1920? Поскольку я внедрил тот же код, и он хорошо работает с разрешением 720 * 1280 (720/16), но не работает с теми видео, ширина которых приводит к уменьшению значения (ширина видео / 16), есть какие-либо предложения?
Ответ №1:
В итоге я экспортировал видео отдельно, используя приведенный выше код, и добавил аудиофайлы отдельно, используя AVComposition и AVExportSession. Вот код
-(void) addAudioToFileAtPath:(NSString *) filePath toPath:(NSString *)outFilePath
{
NSError * error = nil;
AVMutableComposition * composition = [AVMutableComposition composition];
AVURLAsset * videoAsset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:filePath] options:nil];
AVAssetTrack * videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVMutableCompositionTrack *compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo
preferredTrackID: kCMPersistentTrackID_Invalid];
[compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,videoAsset.duration) ofTrack:videoAssetTrack atTime:kCMTimeZero
error:amp;error];
CMTime audioStartTime = kCMTimeZero;
for (NSDictionary * audioInfo in audioInfoArray)
{
NSString * pathString = [audioInfo objectForKey:audioFilePath];
AVURLAsset * urlAsset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:pathString] options:nil];
AVAssetTrack * audioAssetTrack = [[urlAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
AVMutableCompositionTrack *compositionAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio
preferredTrackID: kCMPersistentTrackID_Invalid];
[compositionAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,urlAsset.duration) ofTrack:audioAssetTrack atTime:audioStartTime error:amp;error];
audioStartTime = CMTimeAdd(audioStartTime, CMTimeMake((int) (([[audioInfo objectForKey:audioDuration] floatValue] * kRecordingFPS) 0.5), kRecordingFPS));
}
AVAssetExportSession* assetExport = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetMediumQuality];
assetExport.videoComposition = mutableVideoComposition;
assetExport.outputFileType =AVFileTypeQuickTimeMovie;// @"com.apple.quicktime-movie";
assetExport.outputURL = [NSURL fileURLWithPath:outFilePath];
[assetExport exportAsynchronouslyWithCompletionHandler:
^(void ) {
switch (assetExport.status)
{
case AVAssetExportSessionStatusCompleted:
// export complete
NSLog(@"Export Complete");
break;
case AVAssetExportSessionStatusFailed:
NSLog(@"Export Failed");
NSLog(@"ExportSessionError: %@", [assetExport.error localizedDescription]);
// export error (see exportSession.error)
break;
case AVAssetExportSessionStatusCancelled:
NSLog(@"Export Failed");
NSLog(@"ExportSessionError: %@", [assetExport.error localizedDescription]);
// export cancelled
break;
}
}];
}
Комментарии:
1. Не могли бы вы, пожалуйста, заменить цикл «for» одним словарем «audioInfo», в котором есть все значения, которые необходимо установить, чтобы он стал более удобным для копирования и вставки? 🙂
2. @Chintan Patel: Мне пришлось добавлять разные аудиофайлы переменной длины к разным частям видео в сгенерированном фильме (в итоге я использую полные аудиофайлы вместо этого), поэтому я создал словарь для каждого включаемого аудиофайла и добавил их все в массив (audioInfoArray). Словарь audioInfo содержит следующие ключи audioFilePath и audioDuration , NSString и float соответственно.
3. В коде ошибка. Что такое переменная mutableVideoComposition? assetExport.videoComposition = изменяемая видеокомпозиция; На него больше нигде нет ссылок.
4. Это может быть переменная compositionVideoTrack, но я не уверен, потому что я не касался этого кода уже почти год
5. @PaulSolt Вы можете просто прокомментировать эту строку. Он мертв и без кода должен работать нормально.
Ответ №2:
Не могли бы вы, пожалуйста, заменить цикл «for» одним словарем «audioInfo», в котором есть все значения, которые необходимо установить, чтобы он стал более удобным для копирования и вставки? 🙂
Если вы просто хотите добавить один аудиофайл, следующий код должен заменить цикл for :
NSString * pathString = [self getAudioFilePath];
AVURLAsset * urlAsset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:pathString] options:nil];
AVAssetTrack * audioAssetTrack = [[urlAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
AVMutableCompositionTrack *compositionAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio
preferredTrackID: kCMPersistentTrackID_Invalid];
[compositionAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,urlAsset.duration) ofTrack:audioAssetTrack atTime:kCMTimeZero error:amp;error];