Как транслировать видео H.264 по UDP с помощью аппаратного кодера NVidia NVEnc?
Это будет вопрос с самостоятельным ответом, потому что он сводил меня с ума в течение целой недели, и я хочу избавить коллег-программистов от разочарования, которое я пережил.
Ситуация такова: вы хотите использовать аппаратный кодер NVidia NVEnc (доступный на картах Kepler и Maxwell, то есть GT(x) 7xx и GT(x) 9xx соответственно) для потоковой передачи выходных данных вашего графического приложения через UDP. Это не простой путь, но он может быть очень эффективным, поскольку позволяет обойти необходимость "загружать" кадры из видеопамяти в системную память до окончания этапа кодирования, поскольку NVEnc имеет возможность прямого доступа к видеопамяти.
Мне уже удалось заставить эту работу постольку генерировать файл.h264, просто записывая в него выходные буферы NVEnc, кадр за кадром. У VLC не было проблем с воспроизведением такого файла, за исключением того, что было отключено время (я не пытался это исправить, так как мне нужен был только этот файл для целей отладки).
Проблема возникла, когда я попытался транслировать закодированные кадры через UDP: ни VLC, ни MPlayer не смогли воспроизвести видео. Оказалось, что для этого есть две причины, которые я объясню в своем ответе.
1 ответ
Как я уже говорил в этом вопросе, было две (ну, на самом деле, три) причины, по которым MPlayer не мог воспроизвести мой поток UDP.
Первая причина связана с упаковкой. NVEnc заполняет свои выходные буферы блоками данных, называемыми NALU, которые он разделяет "начальными кодами", в основном предназначенными для синхронизации битового потока. (Перейдите к превосходному ответу szatmary SO, если вы хотите узнать больше о Приложении B - и его конкуренте AVCC).
Проблема сейчас в том, что NVEnc иногда доставляет более одного такого NALU в одном выходном буфере. Хотя большинство NALU содержат закодированные видеокадры, иногда бывает необходимо (и обязательно в начале потока) также посылать некоторые метаданные, такие как разрешение, частота кадров и т. Д. NVEnc помогает в этом, генерируя также эти специальные NALU (более подробно). на что дальше вниз).
Как выясняется, программное обеспечение плеера, однако, не поддерживает получение более одного NALU в одном UDP-пакете. Это означает, что вам нужно запрограммировать простой цикл, который ищет стартовые коды (два или три байта "0", за которыми следует байт "1"), чтобы разделить выходной буфер и отправить каждый NALU в своем собственном UDP-пакете. (Однако обратите внимание, что пакеты UDP должны по-прежнему включать эти стартовые коды.)
Другая проблема с пакетированием заключается в том, что IP-пакеты, как правило, не могут превышать определенный размер. Опять же, ответ SO дает ценную информацию о том, каковы эти ограничения в различных контекстах. Здесь важно то, что, хотя вам не нужно обрабатывать это самостоятельно, вы должны сказать NVEnc "разрезать" свой вывод, задав следующие параметры при создании объекта кодировщика:
m_stEncodeConfig.encodeCodecConfig.h264Config.sliceMode = 1;
m_stEncodeConfig.encodeCodecConfig.h264Config.sliceModeData = 1500 - 28;
(с m_stEncodeConfig
являясь структурой параметра, которая будет передана NvEncInitializeEncoder()
1500 - MTU пакетов Ethernet, а 28 - добавленные размеры заголовка IP4 и заголовка UDP).
Вторая причина, по которой MPlayer не может воспроизводить мой поток, связана с природой потокового видео, а не с сохранением его в файле. Когда программное обеспечение плеера начинает воспроизведение файла H.264, оно найдет необходимые значения NALU метаданных, содержащие разрешение, частоту кадров и т. Д., Сохранит эту информацию и, следовательно, никогда больше не понадобится. Принимая во внимание, что когда его просят воспроизвести поток, он пропустит начало этого потока и не сможет начать воспроизведение, пока отправитель не отправит метаданные повторно.
И вот в чем проблема: если не указано иное, NVEnc будет генерировать NALU метаданных только в самом начале сеанса кодирования. Вот параметр конфигурации энкодера, который необходимо установить:
m_stEncodeConfig.encodeCodecConfig.h264Config.repeatSPSPPS = 1;
Это говорит NVEnc о необходимости периодически генерировать NALU SPS/PPS (я думаю, что по умолчанию это означает каждый кадр IDR).
И вуаля! После устранения этих препятствий вы сможете оценить мощь генерации сжатых видеопотоков, практически не нагружая процессор.
РЕДАКТИРОВАТЬ: я понимаю, что этот вид ультра-простой потоковой передачи UDP не рекомендуется, так как он на самом деле не соответствует ни одному стандарту. Mplayer будет воспроизводить такой поток, но VLC, который в противном случае способен воспроизводить практически все, не сможет. Основная причина в том, что в потоке данных нет ничего, что бы указывало бы даже на тип отправляемого носителя (в данном случае видео). В настоящее время я занимаюсь исследованиями, чтобы найти самый простой способ, который будет соответствовать принятым стандартам.