Здравствуйте,
есть у меня одна задачка под 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( ) ;
}
}
Можете ли мне помочь в сложившейся ситуации. Может есть какое-то другое решение для синхронизации аудио и видео?
Буду премного благодарен за любую помощь.
Спасибо.