NewTek NDI® SDK технология - опыт использования инструментов
Опыт использования 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 позволяет относительно простым способом добавить возможность отправки и получения видеопотока через локальную сеть в свой пайплайн, что позволит встроить мультимедиа-приложение в этот технологический стек и обогатить его дополнительной функциональностью.