Я иногда испытываю заикание различной степени тяжести при реализации потокового видео по сети. Я протестировал свое приложение на Iphone 7 и Ipad 9.7, оба страдают от случайного заикания, но Iphone 7, кажется, заикается больше всего. Я хотел бы думать, что это не чисто аппаратная проблема, поскольку я могу передавать видео через YouTube без каких-либо проблем.
Моей первой реализацией потокового видео по сети была просто отправка изображений в формате jpeg с моего компьютера на мой Iphone 7, с той же проблемой. Я проверил, были ли отброшенные пакеты, увеличив число и добавив его к каждому отправленному пакету, было заикание, но не было пропущенных пакетов. Я проверил, была ли проблема в том, что не было изображений для рендеринга, потому что пакеты прибыли поздно, задерживая рендеринг и сохраняя полученные изображения в буфере, все еще заикаясь.
Я предположил, что заикание произошло из-за того, что мой планировщик событий не всегда срабатывал вовремя (чего не было), я тестировал разные планировщики событий, но мне не удавалось реализовать что-то достаточно точное. Я подумал, что если бы я мог просто передать поток битов, закодированный в h264, в какой-нибудь встроенный класс target-c, он бы вовремя управлял декодированием и рендерингом моего изображения. Вот что я пытался сделать.
Я начал с того, что следовал этому руководству по потоковой передаче видео в кодировке h264 в IOS. Мне пришлось внести несколько изменений, чтобы заставить его работать, так как мой поток битов h264 содержит несколько кадров изображения на кадр. Я добавляю каждый из них в CMBlockBuffer, вместо того, чтобы просто создавать буфер блоков, инкапсулирующий первый модуль изображения, как это делается в связанном руководстве.
Мои примеры буферных вложений также выглядят так, а не так, как они показаны в руководстве
CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
//CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
CFDictionarySetValue(dict, kCMSampleAttachmentKey_IsDependedOnByOthers, kCFBooleanTrue);
if (naluType == 1) {
// P-frame
CFDictionarySetValue(dict, kCMSampleAttachmentKey_NotSync, kCFBooleanTrue);
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DependsOnOthers, kCFBooleanTrue);
} else {
// I-frame
CFDictionarySetValue(dict, kCMSampleAttachmentKey_NotSync, kCFBooleanFalse);
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DependsOnOthers, kCFBooleanFalse);
}
Это вложения, которые использует Moonlight Streaming Service . Я не отображаю изображение сразу, потому что я установил метку времени представления каждого кадра на 1/60 секунды больше, чем предыдущий кадр с CMSampleBufferSetOutputPresentationTimeStamp.
Но я все еще страдаю от заикания. Я читал о том, как работают IOS-потоки, и что он может вовремя испортить рисование кадров, но все веб-сайты, которые транслируют видео, могут делать это на моем устройстве без заиканий. Конечно, я должен быть в состоянии сделать то же самое для моего приложения? Я также пытался собрать свое приложение с помощью сборки релиза, но это не помогло. Я знаю, что вопрос «Как исправить заикание моего видеопотока» - довольно широкий вопрос, но я надеюсь, что упоминание того, что я пытался сделать, как выглядит моя реализация и тот факт, что веб-сайты, такие как youtube, могут транслироваться без заикания на моем оборудовании, должны быть достаточно информации для кого-то, чтобы иметь возможность указать мне правильное направление. Я знаю, что могу попробовать веб-решение, такое как WebRTC, но, если возможно, я бы хотел решить возникшую проблему вместо создания чего-то совершенно нового.
Обновление 1
В моем проекте я печатаю время между прибытием двух пакетов изображений. Раньше случалось так, что поток заикался в то же время, когда пакет приходил с опозданием , даже если задержка воспроизведения потока . Чтение чего-то в Интернете заставило меня подумать, что устранение утечек памяти может решить мою проблему. Я не знаю, связано ли это, или мои предыдущие тесты были выполнены неправильно, или все это было одним большим совпадением. Но после исправления утечек в памяти мой проект теперь заикается, когда пакеты приходят с опозданием, но заикания также задерживаются, если мой поток таков. Возможно, теперь я неправильно установил PTS.
Обновление 2
Я могу правильно отложить воспроизведение с помощью PTS, и я могу ускорить или замедлить его воспроизведение в зависимости от временных меток, которые я загружаю в буфер семплов, поэтому я не думаю, что допустил ошибку при настройке PTS.Я записал свой экран, чтобы показать, как он выглядит.В этом примере данные хранятся в контейнере на 600 кадров, а затем декодируются все за один раз, чтобы убедиться, что заикание происходит не из-за опоздания пакетов.Видео также распечатывает время между прибытием пакета и прибытием предыдущего пакета, если время между ними превышает бит более 1/60 секунды.Ниже приведен соответствующий код видео и PTS для примера.
Видео: https://youtu.be/Ym5rfHwg-eM
// init function
CMTimeBaseCreateWithMasterClock(CFAllocatorGetDefault(), CMClockGetHostTimeClock(), &_controlTimebase);
_displayLayer.controlTimebase = _controlTimebase;
CMTimebaseSetTime(_displayLayer.controlTimebase, CMTimeMake(0, 60));
CMTimebaseSetRate(_displayLayer.controlTimebase, 1.0);
// f = frame. Starts at 630 to delay playback with 630 frames.
f = 630;
......................
// received packet containing frame function
[frames addObject:data]; // data is an NSData of an encoded frame
// store 600 frames worth of data then queue all of them in one go to make sure that the stutter is not because packets arrive late
if ([frames count] == 600)
{
for (int i = 0; i < 600; i++)
{
uint8_t *bytes = (uint8_t*)[frames[i] bytes];
[self->cameraView.streamRenderer decodeFrame:bytes length:frames[i].length;
}
}
......................
// decode frame function
CMSampleBufferSetOutputPresentationTimeStamp(sampleBuffer, CMTimeMake(f, 60));
f++;
Обновление 3
Теперь у меня также естьпопытался использовать CADisplayLink, чтобы получить обратные вызовы, когда я должен нарисовать свое изображение, где я затем рендерил CVPixelBuffer с платформой Metal.Я использую тот же пример, который описан в обновлении 2, и все еще страдаю от тех же проблем.Я удостоверился, что распечатал время, необходимое Metal для рендеринга моего CVPixelBuffer, и это занимает около 3 миллисекунд на CVPixelBuffer, что значительно ниже частоты обновления экрана.
Обновление 4
Я попытался отобразить индекс текущего кадра выше буфера визуализированных пикселей.При этом я могу проверить, не запаздывает ли отображение или плохие буферы пикселей.Пошагово просматривая видео, я мог видеть, что индекс рендеринга увеличивается, даже когда поток отстает.Теперь я думаю, что проблема в декодировании или кодировании видеопотока.