Эта статья является четвертой в серии «Разработка устройства на основе протокола национального стандарта GB28181» и знакомит с процессом передачи видеоданных в реальном времени. Функция передачи видео на стороне устройства протокола GB28181 реализуется путем интерпретации информации SDP в сообщении INVITE, чтения и анализа видеофайлов или файлов изображений, кодирования данных, инкапсуляции h264 в формат PS и, наконец, отправки их через данные RTP. В этой статье постепенно будут подробно представлены этапы реализации и соответствующие технические моменты каждого модуля, чтобы помочь читателям понять и применить протокол GB28181 для передачи видео в реальном времени.
В протоколе GB28181 во время процесса передачи аудио и видео в реальном времени сообщения INVITE используются для передачи информации SDP (протокол описания сеанса). Информация SDP описывает атрибуты и параметры сеанса, включая тип носителя, транспортный протокол, кодек, сетевой адрес и т. д. Ниже приведено содержимое SDP примера сообщения INVITE с подробным объяснением каждого элемента:
v=0
o=34020000002000000001 0 0 IN IP4 192.168.1.10
s=Play
c=IN IP4 192.168.1.10
t=0 0
m=video 40052 RTP/AVP 96
a=recvonly
a=rtpmap:96 PS/90000
y=0358902090
f=
Указывает номер версии протокола SDP, здесь он равен 0.
Поле o идентифицирует инициатора сеанса и уникальный идентификатор сеанса.
«34020000002000000001» указывает SIP ID инициатора данной сессии.
0 0 представляет временные метки начала и окончания сеанса.
В IP4 192.168.1.10 представляет собой сетевой адрес сеанса, здесь это адрес IPv4.
Поле c указывает информацию о соединении сеанса.
IN указывает, что тип сети — Интернет.
IP4 192.168.1.10 представляет IPv4-адрес сеанса.
Поле t указывает информацию о времени сеанса.
0 0 означает, что время начала и окончания сеанса равно 0, то есть продолжительность не определена.
Поле m определяет тип носителя и связанные с ним параметры сеанса.
video указывает, что тип носителя — видео.
40052 представляет номер порта передачи медиапотока.
RTP/AVP означает, что протокол передачи — RTP и настроен с использованием AVP (аудиовизуальный профиль).
96 указывает, что медиапоток представлен номером 96.
Поле a содержит свойства медиапотока.
rtpmap:96 означает, что тип полезной нагрузки будет иметь номер 96.
PS означает использование формата MPEG-PS для инкапсуляции данных.
90000 представляет тактовую частоту, то есть количество тактов в секунду.
Поле y представляет собой строку десятичных целых чисел, представляющую значение SSRC.
Поле f: f= v/формат кодирования/разрешение/частота кадров/тип скорости передачи данных/размер скорости передачи данных a/формат кодирования/размер скорости передачи данных/частота дискретизации
Поле f здесь не задано и заполняется отправителем данных.
Чтобы передать видеоданные, нам сначала нужно прочитать и проанализировать видеофайл или файл изображения. Нам необходимо использовать соответствующие библиотеки или инструменты для чтения данных видео или изображения из файла и их анализа для получения ключевых видеокадров или данных изображения для подготовки к последующему кодированию и упаковке.
В протоколе GB28181 видеоданные обычно инкапсулируются в формате MPEG-PS (программный поток MPEG). Закодированные видеоданные необходимо инкапсулировать в формате PS, включая добавление заголовков упаковки и начальных кодов, а затем дальнейшую инкапсуляцию RTP.
Ниже приведен основной процесс использования C++ для инкапсуляции H.264 NALU в формат MPEG-PS (показана только часть кода):
// Инкапсулировать список NALU H.264 в формат MPEG-PS
void MakeMPEGPS(unsigned char* h264Data, int h264Length,
unsigned char* psData)
{
int totalPES = (h264Length + MAX_PES_LENGTH - 1) / MAX_PES_LENGTH; // Подсчитать общее количество PES-пакетов
int remainingBytes = h264Length; // Количество байт, оставшихся для обработки
// заголовок MPEG-PS
unsigned char mpegPSHeader[] = {0x00, 0x00, 0x01, 0xBA};
// Разделение и инкапсуляция данных H.264
for (int i = 0; i < totalPES; i++)
{
unsigned char* pbuf = psData;
int pesLength = (remainingBytes > MAX_PES_LENGTH) ? MAX_PES_LENGTH : remainingBytes; // Длина текущего пакета PES
remainingBytes -= pesLength; // 更新Количество байт, оставшихся для обработки
// PES Баотоу
unsigned char pesHeader[] = {0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x80, 0x00};
// Установить длину пакета PES
pesHeader[4] = (pesLength + 8) >> 8; // Старшие 8 бит
pesHeader[5] = (pesLength + 8) & 0xFF; // Младшие 8 бит
// выходзаголовок MPEG-PS и текущий PES Баотоу
memcpy(pbuf, mpegPSHeader, sizeof(mpegPSHeader));
pbuf += sizeof(mpegPSHeader);
memcpy(pbuf, pesHeader, sizeof(pesHeader));
pbuf += sizeof(pesHeader);
// Вывод данных H.264 текущего пакета PES.
memcpy(pbuf, h264Data + (i * MAX_PES_LENGTH), pesLength);
pbuf += pesLength;
int payload_len = (pbuf - psData);
// Инкапсулировать пакет RTP и отправить
MakeAndSendRTP(psData, payload_len);
}
}
Следует отметить, что когда кадр h264 относительно велик, он будет превышать длину, которую может выразить PES. В это время кадр h264 должен быть сегментирован, инкапсулирован в несколько PES, а затем синтезирован в пакет PS.
Логика отправки данных RTP относительно проста. Ниже приведена принципиальная схема кода программы.
Ниже приведен демонстрационный код инкапсуляции RTP (показана только часть кода):
struct RTPHeader
{
uint8_t version; // Номер версии протокола RTP, фиксированный — 2.
uint8_t padding: 1; // биты заполнения
uint8_t extension: 1; // бит расширения
uint8_t csrcCount: 4; // Счетчик CSRC, указывающий количество идентификаторов CSRC.
uint8_t marker: 1; // Отметить бит
uint8_t payloadType: 7; // Тип нагрузки
uint16_t sequenceNumber; // серийный номер
uint32_t timestamp; // Временная метка
uint32_t ssrc; // Идентификатор источника синхронизации
};
void MakeRTPHeader(struct RTPHeader* header, uint16_t sequenceNumber, uint32_t timestamp, uint32_t ssrc, bool isMark)
{
// Установите номер версии протокола RTP на 2.
header->version = 2;
// биты заполнения、бит расширение, счетчик CSRC и другие поля могут быть установлены в соответствии с конкретными потребностями.
header->padding = 0;
header->extension = 0;
header->csrcCount = 0;
// настраивать Отметить бит равен 0 (при необходимости установите его в 1),Затем измените его там, где вам нужно его установить)
header->marker = isMark ? 1 : 0;
// Настройки нагрузки(payload тип), устанавливается в соответствии с конкретными потребностями
header->payloadType = 96;
// настраиватьсерийный номери Временная метка
header->sequenceNumber = htons(sequenceNumber); // Требуется преобразование Endian (сетевой порядок байтов)
header->timestamp = htonl(timestamp); // Требуется преобразование Endian (сетевой порядок байтов)
// настраивать Идентификатор источника синхронизации
header->ssrc = htonl(ssrc); // Требуется преобразование Endian (сетевой порядок байтов)
}
void sendRTPPacket(const uint8_t* mpegPSData, int mpegPSLength, uint16_t sequenceNumber, uint32_t timestamp, uint32_t ssrc)
{
int offset = 0; // Смещение, используемое для прохождения пакетных данных MPEG-PS.
int remainingLength = mpegPSLength; // Оставшаяся длина используется для определения необходимости разделения сообщения RTP.
uint8_t rtpbuf[RTP_PAYLOAD_MAX_SIZE]; // Буфер данных полезной нагрузки RTP
struct RTPHeader rtpHeader; // Заголовок сообщения RTP
while (remainingLength > 0)
{
// Вычислить текущую длину полезных данных RTP (не превышающую максимальный размер полезных данных RTP)
bool is_mark = false;
int data_len = RTP_PAYLOAD_MAX_SIZE;
if (remainingLength <= RTP_PAYLOAD_MAX_SIZE)
{
data_len = remainingLength;
is_mark = true;
}
// заполнять Заголовок сообщения RTP
MakeRTPHeader(&rtpHeader, sequenceNumber, timestamp, ssrc);
// копировать заголовок RTP в буфер полезной нагрузки RTP
memcpy(rtpbuf, &rtpHeader, sizeof(RTPHeader));
// копировать данные MPEG-PS в буфер полезной нагрузки RTP
memcpy(rtpbuf + RTP_HEADER_LEN, mpegPSData + offset, data_len);
// Отправьте полный пакет RTP
if (udp_channel_)
{
udp_channel_->PostSendBuf(rtpbuf, RTP_HEADER_LEN + data_len);
}
// Обновление смещения, оставшаяся длина、серийный номер и другая информация
offset += data_len;
remainingLength -= data_len;
sequenceNumber++;
}
}
По вопросам сотрудничества просьба добавить автора hbstream (http://haibindev.cnblogs.com), при перепечатке просьба указывать автора и источник