Динамический FPS при записи видео на Mac OS
От: Kroumvud Украина  
Дата: 30.05.14 08:12
Оценка:
Здравствуйте,

есть у меня одна задачка под Mac OS. Необходимо написать программу которая будет записывать видео с динамическим FPS.
Осноной проблемой является то, что при моей текущей логике при записи видео и аудио получается рассинхронизация. Также одним из условий является то что необходимо объединить два изображения в одно.

Теперь более подробно.
У меня есть 2 запущенных сессии. Первая сессия берет видео с камеры №1, вторая -- с камеры №2. Аудио записывается только с первой сессии.
После получения видео фрейма с каждой камеры я вытягиваю из CMSampleBufferRef данные изображения для каждой камеры:

- (void)processVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer
{
    CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    CVPixelBufferLockBaseAddress(pixelBuffer, 0);
    
    size_t bufferWidth = CVPixelBufferGetWidth(pixelBuffer);
    size_t bufferHeight = CVPixelBufferGetHeight(pixelBuffer);
    size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
    CMTime currentTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
    Byte *src_buff = (Byte*)CVPixelBufferGetBaseAddress(pixelBuffer);
...
}


и передаю эти данные дальше на обработку.
Далее мне необходимо объединить два этих извображения в одно. Процедуру микса я провожу при помощи OpenCV и успешно объединяю два изображения в одно.
На выходе этой процедуры у меня есть буфер с данным, длинна, ширина и высота новой картинки. После этого я предаю буфер и все дополнительные данные на записью
И тут я получаю свою проблему с рассинхроном.
Для записи видео и аудио я использую AVAssetWriter с AVAssetWriterInputPixelBufferAdaptor[/CODE].
Дя того что бы записать CVPixelBufferRef в AVAssetWriterInputPixelBufferAdaptor я использую метод
- (BOOL)appendPixelBuffer:(CVPixelBufferRef)pixelBuffer withPresentationTime:(CMTime)presentationTime;

и в этой ситуации видео фрейму необходимо дать тайм штамп. По всем канонам я генерирую тайм штамп от текущего времени:

timestamp = CMTimeMake(getUptimeInMilliseconds() * 6, 6000);
 dispatch_sync(_movieWritingQueue, ^{
        if(![_assetWriterPixelBufferInput appendPixelBuffer:videoBuffer withPresentationTime:timestamp])
        {
            DDLogWarn(@"Problem appending pixel buffer at time: %lld", currentSampleTime.value);
        }
        else
        {
            if (self.delegate && [self.delegate respondsToSelector:@selector(receiveNewVideoBufferWithTimestamp:)])
            {
                [self.delegate receiveNewVideoBufferWithTimestamp:currentSampleTime];
            }
        }

    });
    ....

       int getUptimeInMilliseconds()
       {
          const int64_t kOneMillion = 1000 * 1000;
          static mach_timebase_info_data_t s_timebase_info;

          if (s_timebase_info.denom == 0) {
              (void) mach_timebase_info(&s_timebase_info);
           }

          // mach_absolute_time() returns billionth of seconds,
          // so divide by one million to get milliseconds
          return (int)((mach_absolute_time() * s_timebase_info.numer) / (kOneMillion * s_timebase_info.denom));
        }


и если я передаю этот тайм штамп при постоянном 30 FPS то все нормально и видео пишется синхронно с аудио. Но как только процессор нацинает не справляться с поставленными ему задачеми (слишком большое изображение, или еще что то), [AVAssetWriterInputPixelBufferAdaptor начинает пропускать фреймы и не пишет их в файл, после чого получается, что видео дорожка длиннее чем аудио дорожка.
По этой причине я принял решение корректировать видео тайм штампы согласно нагрузке процессора и скорости обработки одного фрейма AVAssetWriterInputPixelBufferAdaptor'ом.
Но к сожалению моя логика по исправлению тайм штампов приводит к тому, что либо AVAssetWriterInputPixelBufferAdaptor совсем отказывается писать фреймы(и запись останавливается вообще), или же рассинхронизация аудио и видео ведет себя очень нехорошо(то видео бежит в перед то аудио).

Вот логика по корректеровке видео тайм штампов:
timestamp = CMTimeMake(getUptimeInMilliseconds() * 6, 6000);
float koef = (float)_targetTimeScale / _baseTimescale;
            if (koef != 1)
            {
                timestampValue *= koef;
                _baseTimescale = _targetTimeScale;
            }
            
            CMTime timestamp = CMTimeMake(timestampValue, _targetTimeScale);
            if (previousFrameTime.timescale != 0 )
            {
                NSLog(@"value / timescale = %f, duration = %f _frameCount = %lu", (float)timestamp.value / timestamp.timescale, ABS(((float)previousFrameTime.value / previousFrameTime.timescale) - ((float)timestamp.value / timestamp.timescale)), (unsigned long)_frameCount);
            }
            
            if (pixelBuffer)
            {
                if (_assetWriterVideoInput.readyForMoreMediaData && ![droppedFramesList containsObject:[NSNumber numberWithLong:_frameCount]])
                {
                    [self processVideoBuffer:pixelBuffer andTimestamp:timestamp];
                    _droppedFramesCount = 0;
                }
                else
                {
                    NSLog(@"Drop Frame");
                    _droppedFramesCount++;
                    _targetTimeScale--;
                }
            }
            else
            {
                NSLog(@"Pixel Buffer Error");
            }

где _baseTimescale = всегда равен 30, _targetTimeScale = изменяется в зависимости от количества пропущенных фреймов.
Также мотод записи видео на старте всегда вызывается 30 раз в секунду, и зависит от _targetTimeScale -- чем меньше _targetTimeScale тем долше будем ждать:
pthread_create( & thread, & attr, (void *)TimerRoutine, (__bridge void *)(self)) ;
...
static void  TimerRoutine(void * arg)
{
    pthread_detach( pthread_self() ) ;
    
    int done = false ;
    while( !done )
    {
        // do background task, i.e.:
        
        NSDate *methodStart = [NSDate date];
        
        [(__bridge MTVPVideoWriter *)arg doWriting2]; // здесь происходит запись
        
        NSDate *methodFinish = [NSDate date];
        NSTimeInterval executionTime = [methodFinish timeIntervalSinceDate:methodStart] * 1000000;
        int timeToWaitFrame = (1.0 / [(__bridge MTVPVideoWriter *)arg targetTimeScale]) * 1000000;
        NSLog(@"timeToWaitFrame = %f, targetTimeScale = %d", timeToWaitFrame - executionTime, [(__bridge MTVPVideoWriter *)arg targetTimeScale]);
        usleep(timeToWaitFrame - executionTime);

        
        pthread_testcancel( ) ;
    }
}


Можете ли мне помочь в сложившейся ситуации. Может есть какое-то другое решение для синхронизации аудио и видео?
Буду премного благодарен за любую помощь.

Спасибо.
mac os osx video
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.