Опыт использования NewTek NDI® C SDK
NDI® — это royalty free стандарт, разработанный компанией NewTek, предназначенный для обмена видео в локальной сети. NDI® позволяет нескольким видеосистемам находить друг друга и взаимодействовать в пределах локальной сети, кодировать, передавать и принимать множество аудио и видео потоков с низкой задержкой в режиме реального времени. Может работать в двунаправленном режиме со множеством видеопотоков через общее соединение. Алгоритмы кодирования работают независимо от частоты кадров, поддерживается 4К видео, 16 и более каналов аудио с плавающей точкой. Стандарт включает средства реализации прав доступа к видео, группировку, отправку и получение метаданных и команд.
В этой заметке я расскажу о внедрении этой технологии для отправки аудио и видео потока в существующий пайплайн, а так же о некоторых неочевидных на первый взгляд подводных камнях.
Технология NDI®
Технология NewTek NDI® предоставляет возможность на основе существующей инфраструктуры Gigabit Ethernet создавать студии видеомонтажа в реальном времени профессионального уровня. Используя программное обеспечение с поддержкой ввода-вывода NDI потоков, применяя дополнительные видеоэффекты, микширование видео и аудио, используя режимы Picture in Picture, Lower Thirds, эффекты бегущей строки, существует возможность создавать и доставлять уникальный контент своим зрителям в режиме реального времени. Все перечисленные видеоэффекты можно воспроизвести используя пайплайн — настраиваемый конвейер покадровых графических преобразований, реализованный внутри мультимедиа-приложения. Источниками видеопотоков внутри локальной сети могут являться другие компьютеры, широко доступные платы захвата и конвертации SDI, HDMI в NDI, IP-камеры с поддержкой такой функциональности, Skype во время звонка, и даже обычные смартфоны (захват экрана либо использование основной или фронтальной камеры).
Вот некоторые примеры использования:
- запись и трансляция конференций, во время которой можно осуществить real-time монтаж захваченной презентации с экрана, нескольких видеокамер в зале, стриминг в интернет и запись на носитель;
- проведение онлайн вебинаров со множеством участников и real-time монтажом;
- захват экрана/окна с игрой, объединение с изображением видеокамеры, добавление виджетов комментариев, уведомлений и пожертвований с последующей отправкой на популярные стриминговые сервисы (twitch, youtube и т.п.).
Скачать NDI® SDK можно бесплатно по адресу https://www.ndi.tv/sdk/, требуется регистрация.
Получение данных
Создание Find Instance
Для получения данных из какого-либо источника NDI, этот источник сначала необходимо обнаружить. Для этого нужно создать find instance типа NDIlib_find_instance_t
, для чего предлагается функция NDIlib_find_create_v2
:
NDIlib_find_instance_t NDIlib_find_create_v2(const NDIlib_find_create_t* p_create_settings);
В функцию создания можно передать указатель на структуру NDIlib_find_create_t
, в которой указать следующие поля:
bool show_local_sources
— необходимо ли отражать в списке доступных источников локальные;const char* p_groups
— список групп источников для поиска;const char* p_extra_ips
— дополнительный список IP-адресов для поиска источников, разделенных запятой.
Периодическим вызовом функции (допустим 1 раз в секунду) NDIlib_find_get_current_sources
на созданном инстансе NDIlib_find_instance_t
получим указатель на массив источников типа NDIlib_source_t
:
const NDIlib_source_t* NDIlib_find_get_current_sources(
NDIlib_find_instance_t p_instance,
uint32_t* p_no_sources);
Источник NDI
Объект источника типа NDIlib_source_t
содержит в себе следующие поля:
const char* p_ndi_name
— уникальное название источника в локальной сети вида MACHINE_NAME (NDI_SOURCE_NAME);const char* p_url_address
— адрес источника, хранится для внутреннего использования.
Выбрав необходимый источник по его названию, существует возможность создания receiver instance типа NDIlib_recv_instance_t
функцией NDIlib_recv_create_v3
:
NDIlib_recv_instance_t NDIlib_recv_create_v3(
const NDIlib_recv_create_v3_t* p_create_settings);
где p_create_settings
указатель на структуру NDIlib_recv_create_v3_t
, содержащую следующие поля:
NDIlib_source_t source_to_connect_to
— полученный ранее объект источника;NDIlib_recv_color_format_e color_format
— предпочитаемый формат хранения цвета;NDIlib_recv_bandwidth_e bandwidth
— настройка ширины канала.
Цветовой формат
enum NDIlib_recv_color_format_e
позволяет определить предпочитаемый цветовой формат для изображений, как содержащих, так и не содержащих альфа-канал. Это достигается выбором пары форматов: например, NDIlib_recv_color_format_UYVY_BGRA
— для не содержащих альфа-канала изображений будет использован формат UYVY, а для содержащих — BGRA. NDI SDK предлагает возможность автоматического выбора формата для достижения лучшей производительности/лучшего качества:
NDIlib_recv_color_format_fastest
;NDIlib_recv_color_format_best
.
Соответственно, для внедрения поступающих кадров в свой пайплайн необходимо предусмотреть конвертацию такого формата во внутренний.
Настройка ширины канала
Для настройки ширины канала предусмотрен enum NDIlib_recv_bandwidth_e
со следующими вариантами:
NDIlib_recv_bandwidth_metadata_only
— только получение метаданных от источника;NDIlib_recv_bandwidth_audio_only
— получение метаданных и аудио кадров;NDIlib_recv_bandwidth_lowest
— получение метаданных, аудио кадров и видео кадров в низком качестве;NDIlib_recv_bandwidth_highest
— получение метаданных, аудио кадров и видео кадров в исходном разрешении.
Объект синхронизации
Чтобы получить аудио и видео кадры, синхронизированные по времени, необходимо создать объект синхронизации типа NDIlib_framesync_instance_t
. Для этого достаточно вызвать функцию NDIlib_framesync_create
, передав туда параметр указатель на NDIlib_recv_instance_t
:
NDIlib_framesync_instance_t NDIlib_framesync_create(NDIlib_recv_instance_t p_receiver);
Получение аудио и видео кадров возможно с помощью следующих функций:
void NDIlib_framesync_capture_audio(NDIlib_framesync_instance_t p_instance,
NDIlib_audio_frame_v2_t* p_audio_data,
int sample_rate, int no_channels, int no_samples);
void NDIlib_framesync_capture_video(NDIlib_framesync_instance_t p_instance,
NDIlib_audio_frame_v2_t* p_video_data);
Соответственно нужно создать объекты типов NDIlib_audio_frame_v2_t
и NDIlib_audio_frame_v2_t
, которые просто создаются на стеке и, в дальнейшем, освобождаются. После копирования кадров в пайплайн NDI кадры необходимо освободить:
void NDIlib_framesync_free_audio(NDIlib_framesync_instance_t p_instance,
NDIlib_audio_frame_v2_t* p_audio_data);
void NDIlib_framesync_free_video(NDIlib_framesync_instance_t p_instance,
NDIlib_video_frame_v2_t* p_video_data);
Окончание работы
Для окончания работы с получением аудио и видео кадров достаточно освободить объект синхронизации и receiver instance вызовом следующих функций:
void NDIlib_framesync_destroy(NDIlib_framesync_instance_t p_instance);
void NDIlib_recv_destroy(NDIlib_recv_instance_t p_instance);
Отправка данных
Создание Sender Instance
Для отправки аудио/видео кадров создателями NDI® SDK предлагается использование объекта типа NDIlib_send_instance_t
, который создается с помощью метода NDIlib_send_create
:
NDIlib_send_instance_t NDIlib_send_create(const NDIlib_send_create_t* p_create_settings);
Создание объекта можно параметризировать, передав указатель на структуру типа NDIlib_send_create_t
. В ней можно установить параметры отправителя:
const char* p_ndi_name
— имя NDI® источника;const char* p_groups
— частью какой группы этот источник является;bool clock_video, clock_audio
— параметры, указывающие на способ синхронизации аудио/видео в случае отправки из разных потоков приложения.
Сразу после создания происходит отправка метаданных источника с использованием функции NDIlib_send_add_connection_metadata
:
void NDIlib_send_add_connection_metadata(NDIlib_send_instance_t p_instance,
const NDIlib_metadata_frame_t* p_metadata);
Эта функция позволяет указать производителя, версию, модель и название оборудования источника. Для ее использования необходимо заполнить структуру NDIlib_metadata_frame_t
, в которой в поле p_data
записать указатель на строку с заполненной XML-структурой:
<ndi_product long_name="Stream Name"
short_name="Short Stream Name"
manufacturer="Manufacturer"
version="1.0.0"
session="default"
model_name="device model"
serial="device serial"/>
Отправка видео кадров
Для отправки видео кадров NDI® SDK предлагает использование функций с ранее созданным sender instance:
void NDIlib_send_send_video_v2(NDIlib_send_instance_t p_instance,
const NDIlib_video_frame_v2_t* p_video_data);
void NDIlib_send_send_video_async_v2(NDIlib_send_instance_t p_instance,
const NDIlib_video_frame_v2_t* p_video_data);
В документации к NDI® SDK рекомендуется использовать NDIlib_send_send_video_async_v2
, т.к. происходит мгновенный возврат из функции, а обработка и отправка кадра происходит асинхронно. Для отправки необходимо заполнить структуру типа NDIlib_video_frame_v2_t
:
int xres, yres
— разрешение кадра;NDIlib_FourCC_video_type_e FourCC
— тип кадра;int frame_rate_N
иframe_rate_D
— числитель и знаменатель количества кадров в секунду;uint8_t* p_data
— указатель на данные;int64_t timecode
— таймкод в 100 наносекундных интервалах;int line_stride_in_bytes
— количество байт в одной строке кадра (страйд).
FourCC
рекомендуется NDIlib_FourCC_type_UYVY
либо NDIlib_FourCC_video_type_UYVA
(в случае наличия альфа-канала), однако, если в пайплайне не предусмотрена возможность произвести color conversion аппаратно, то NDI® может (и рекомендует) произвести преобразование самостоятельно с использованием векторных инструкций современных процессоров.
Также, при использовании этой функции, необходимо проследить, чтобы данные, переданные через указатель p_data
не были переиспользованы либо удалены до следующего "синхронизирующего события". Такими событиями могут являться вызов следующих функций:
NDIlib_send_send_video
;NDIlib_send_send_video_async
с другим кадром для отправки;NDIlib_send_send_video
сp_video_data=NULL
;NDIlib_send_destroy
.
Отправка аудио кадров
Для отправки аудио кадров NDI® SDK предлагает использование одной из множества функций из заголовочного файла Processing.NDI.Send.h
:
void NDIlib_send_send_audio_v2(NDIlib_send_instance_t p_instance,
const NDIlib_audio_frame_v2_t* p_audio_data);
void NDIlib_send_send_audio_v3(NDIlib_send_instance_t p_instance,
const NDIlib_audio_frame_v3_t* p_audio_data);
А также Processing.NDI.utilities.h
:
void NDIlib_util_send_send_audio_interleaved_16s(
NDIlib_send_instance_t p_instance,
const NDIlib_audio_frame_interleaved_16s_t* p_audio_data);
void NDIlib_util_send_send_audio_interleaved_32s(
NDIlib_send_instance_t p_instance,
const NDIlib_audio_frame_interleaved_32s_t* p_audio_data);
void NDIlib_util_send_send_audio_interleaved_32f(
NDIlib_send_instance_t p_instance,
const NDIlib_audio_frame_interleaved_32f_t* p_audio_data);
Для отправки использована функция NDIlib_util_send_send_audio_interleaved_32f
, т.к. именно такой формат аудио используется в нашем пайплайне. Соответственно для ее использования заполняется структура типа NDIlib_audio_frame_interleaved_32f_t
:
int sample_rate
— частота дискретизации (48000 или 44100 сэмплов в секунду);int no_channels
— количество каналов (1 — моно, 2 — стерео и т.д.);int no_samples
— количество сэмплов в буфере данных;int64_t timecode
— временной код начала аудио кадра;float* p_data
— указатель на буфер аудио кадра.
Параметр clock_audio
работает неочевидным из документации способом: если он установлен в true
, то при отправке кадра аудиоданных внутри функции отправки происходит задержка на всю длительность этого кадра, что может сыграть злую шутку при использовании в приложении этого же кадра в других целях. Допустим, что в приложении есть поток получения аудиоданных, и внутри этого потока вызывается метод renderAudioFrame
:
void renderAudioFrame(float * data, size_t sampleCount) {
for (auto &output : _outputs) {
output->renderAudioFrame(data, sampleCount);
}
}
В таком случае, при использовании параметра clock_audio == true
, другие получатели аудиоданных будут получать их с недопустимой задержкой. Выходом из данной ситуации может являться отказ от использования этого параметра, либо реализация многопоточной очереди.
Таким образом для синхронизации аудио и видео кадров было использовано явное указание временных кодов соответствующих кадров.
Окончание работы
Для прекращения отправки кадров в NDI® необходимо вызвать функцию NDIlib_send_send_video_v2
c p_video_data=NULL
для инициации синхронизирующего события и последующим удалением sender instance вызовом функции NDIlib_send_destroy
.
Заключение
Использование NDI® SDK позволяет относительно простым способом добавить возможность отправки и получения видеопотока через локальную сеть в свой пайплайн, что позволит встроить мультимедиа-приложение в этот технологический стек и обогатить его дополнительной функциональностью.