AVFoundation AssetWriter: создание фильма с изображениями и аудио

#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];