AVFoundation - обратный AVAsset и выходной видеофайл

Я видел этот вопрос несколько раз, но ни у кого из них нет рабочих ответов.

Требование состоит в том, чтобы отменить и вывести видеофайл (а не просто воспроизводить его в обратном порядке), сохраняя в качестве исходного видео одинаковое сжатие, формат и частоту кадров.

В идеале решение могло бы сделать все это в памяти или буфере и не генерировать фреймы в файлы изображений (например: используя AVAssetImageGenerator), а затем перекомпилировать его (ресурсоемкие, ненадежные результаты синхронизации, изменения в кадре/качество изображения с оригинала и т.д.).

-

Мой вклад: Это все еще не работает, но лучшее, что я пробовал до сих пор:

  • Прочитайте в образце фреймов в массив CMSampleBufferRef[] с помощью AVAssetReader.
  • Запишите его в обратном порядке, используя AVAssetWriter.
  • Проблема: Кажется, что время для каждого кадра сохраняется в CMSampleBufferRef, поэтому даже добавление их назад не будет работать.
  • Затем я попытался заменить информацию о времени каждого кадра рамкой обратного/зеркального отображения.
  • Проблема: это вызывает неизвестную ошибку с помощью AVAssetWriter.
  • Следующий шаг: я рассмотрю AVAssetWriterInputPixelBufferAdaptor

    - (AVAsset *)assetByReversingAsset:(AVAsset *)asset {
        NSURL *tmpFileURL = [NSURL URLWithString:@"/tmp/test.mp4"];    
        NSError *error;
    
        // initialize the AVAssetReader that will read the input asset track
        AVAssetReader *reader = [[AVAssetReader alloc] initWithAsset:asset error:&error];
        AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] lastObject];
    
        AVAssetReaderTrackOutput* readerOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:videoTrack outputSettings:nil];
        [reader addOutput:readerOutput];
        [reader startReading];
    
        // Read in the samples into an array
        NSMutableArray *samples = [[NSMutableArray alloc] init];
    
        while(1) {
            CMSampleBufferRef sample = [readerOutput copyNextSampleBuffer];
    
            if (sample == NULL) {
                break;
            }
    
            [samples addObject:(__bridge id)sample];
            CFRelease(sample);
        }
    
        // initialize the the writer that will save to our temporary file.
        CMFormatDescriptionRef formatDescription = CFBridgingRetain([videoTrack.formatDescriptions lastObject]);
        AVAssetWriterInput *writerInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:nil sourceFormatHint:formatDescription];
        CFRelease(formatDescription);
    
        AVAssetWriter *writer = [[AVAssetWriter alloc] initWithURL:tmpFileURL
                                                          fileType:AVFileTypeMPEG4
                                                             error:&error];
        [writerInput setExpectsMediaDataInRealTime:NO];
        [writer addInput:writerInput];
        [writer startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp((__bridge CMSampleBufferRef)samples[0])];
        [writer startWriting];
    
    
        // Traverse the sample frames in reverse order
        for(NSInteger i = samples.count-1; i >= 0; i--) {
            CMSampleBufferRef sample = (__bridge CMSampleBufferRef)samples[i];
    
            // Since the timing information is built into the CMSampleBufferRef 
            // We will need to make a copy of it with new timing info. Will copy
            // the timing data from the mirror frame at samples[samples.count - i -1]
    
            CMItemCount numSampleTimingEntries;
            CMSampleBufferGetSampleTimingInfoArray((__bridge CMSampleBufferRef)samples[samples.count - i -1], 0, nil, &numSampleTimingEntries);
            CMSampleTimingInfo *timingInfo = malloc(sizeof(CMSampleTimingInfo) * numSampleTimingEntries);
            CMSampleBufferGetSampleTimingInfoArray((__bridge CMSampleBufferRef)sample, numSampleTimingEntries, timingInfo, &numSampleTimingEntries);
    
            CMSampleBufferRef sampleWithCorrectTiming;
            CMSampleBufferCreateCopyWithNewTiming(
                                                  kCFAllocatorDefault,
                                                  sample,
                                                  numSampleTimingEntries,
                                                  timingInfo,
                                                  &sampleWithCorrectTiming);
    
            if (writerInput.readyForMoreMediaData)  {
                [writerInput appendSampleBuffer:sampleWithCorrectTiming];
            }
    
            CFRelease(sampleWithCorrectTiming);
            free(timingInfo);
        }
    
        [writer finishWriting];
    
        return [AVAsset assetWithURL:tmpFileURL];
    }
    

Ответ 1

Работал над этим в течение последних нескольких дней и смог заставить его работать.

Исходный код здесь: http://www.andyhin.com/post/5/reverse-video-avfoundation

Использует AVAssetReader для считывания сэмплов/кадров, извлекает буфер изображения/пикселя и затем добавляет его со временем представления зеркального кадра.