Потоковое видео по сети с помощью AVSampleBufferDisplayLayer заикается, но потоковые онлайн-сервисы работают без нареканий - PullRequest
0 голосов
/ 21 января 2019

Я иногда испытываю заикание различной степени тяжести при реализации потокового видео по сети. Я протестировал свое приложение на 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

Я попытался отобразить индекс текущего кадра выше буфера визуализированных пикселей.При этом я могу проверить, не запаздывает ли отображение или плохие буферы пикселей.Пошагово просматривая видео, я мог видеть, что индекс рендеринга увеличивается, даже когда поток отстает.Теперь я думаю, что проблема в декодировании или кодировании видеопотока.

1 Ответ

0 голосов
/ 30 января 2019

Проблема связана с кодированием перед отправкой пакетов на устройство IOS.Я попытался записать поток перед отправкой, и это также заикалось.Таким образом, ответ таков: дисплей мог бы отставать раньше, но, вероятно, нет.И то, что использование CADisplayLink и рендеринг пиксельных буферов с металлическим каркасом определенно не отстает, проблема была за пределами территории IOS.

...