Во-первых, давайте посмотрим на формат потока PS I-кадра. Здесь необходимо отметить, что заголовок PES должен быть добавлен перед SPS и PPS. Как показано на рисунке ниже, зеленая часть — это полученные нами данные чистого потока H.264, которые необходимо разделить на три сегмента и добавить с заголовком PES впереди. Этот момент не подробно описан в стандарте GB28181, и его можно увидеть только путем анализа потока Hikvision IPC.
В обычных обстоятельствах кадр IDR очень велик и превышает предел длины полезной нагрузки RTP (1400 байт), поэтому указанный выше I-кадр необходимо разделить на несколько пакетов и отправить по RTP несколько раз. Структура первого пакета показана на рисунке выше. После второго пакета структура RTP намного проще.
Вышеупомянутое относится к I-кадру. По сравнению с ним формат кадра P/B действительно прост, поскольку он не имеет ни SYS, PSM, ни SPS, PPS:
Размер кадра P/B обычно не превышает 1400 байт. Если он превышает 1400 байт, его также необходимо разделить на несколько пакетов данных RTP для передачи. Вторая структура пакета RTP для части, превышающей 1400:
1). Инкапсуляция ключевых кадров видео. RTP + PS header + PS system header + PS system Map + PES header +h264 data
2) Инкапсуляция неключевых кадров видео. RTP +PS header + PES header + h264 data
3). Инкапсуляция аудиокадра: RTP + PES header + G711
Взяв Hikvision DS-IPC-B12H2-I в качестве примера для оптимизации
80 60 00 00 00 00 00 00 00 00 04 00 00 00 01 ba
44 f0 4f 69 64 01 02 5f 03 fe ff ff 00 01 11 0c 00 00 01 bb
00 12 81 2f 81 04 e1 7f e0 e0 80 c0 c0 08 bd e0 80 bf e0 80 00 00 01 bc
00 5e f8 ff 00 24 40 0e 48 4b 01 00 14 14 40 16 6b bf 00 ff ff ff 41 12 48 4b 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 30 1b e0 00 1c 42 0e 07 10 10 ea 05 00 02 d0 11 30 00 00 1c 21 2a 0a 7f ff 00 00 07 08 1f fe a0 5a 90 c0 00 0c 43 0a 01 40 fe 00 7d 03 03 e8 03 ff f6 53 94 03 00 00 01 e0
00 1e 8c 80 08 21 3c 13 da 59 ff ff fc 00 00 00 01 67
4d 00 1f 96 35 40 a0 0b 74 dc 04 04 04 08 00 00 01 e0 00 0e 8c 00 03 ff ff fc 00 00 00 01 68
ee 3c 80 00 00 01 e0 00 0e 8c 00 02 ff fc 00 00 00 01 06 e5 01 d5 80 00 00 01 e0 c2 06 8c 00 05 ff ff ff ff f8 00 00 00 01 65
b8 00 00 0a 77 80 00 00 4f e7 e5 34 0f f3 41 4b b9 58 a9 4e 4c f4 ea 04 0e 32 a5 f9 51 df cc 4c b8 99 f2 cf 16 3e 32 19 ed 86 df 05 6b fc 21 5e 0f 87 90 20 c3 16 02 03 73 0f a3 d2 9b 52 1b b1 a7 7c b4 61 6d d9 aa f4 5d 34 f6 49 d4 f6 72 af b6 c7 11 c0 ff 3d 1b fd e3 5d 41 db 32 3a c7 9f f4 f2 c0 99 e6
Анализ данных, это I-кадр
rtp header: 80 60 00 00 00 00 00 00 00 00 04 00
—— 12-байтовое видео фиксированной длины
ps header: 00 00 01 ba ...
——00 00 01 ba 44 f0 4f 69 64 01 02 5f 03 fe
ff ff 00 01 11 0c Первые 14 байт фиксированы, первые 14 байт 0xfe & 0x07 = 0x0e
То есть позже он будет расширен до 6 байт.
ps system header: 00 00 01 bb ...
—— Следующие 00 12 — это длина, то есть длина системного заголовка ps = 4+18 байт.
ps system map 00 00 01 bc ...
—— Следующие 00 5e — это длина, то есть длина системной карты ps = 4+94 байта
pes header: 00 00 01 e0/c0 ...
——e0 — видео, c0 — аудио, а следующие 00 — 1e — длина, которая представляет собой длину заголовка pes (4+30 байт), а остальное — данные полезной нагрузки ps.
ps payload SPS 00 00 00 01 67 ...
ps payload PPS 00 00 00 01 68 ...
ps payload I 00 00 00 01 65 ...
ps payload P 00 00 00 01 61/41 ...
Камера Hikvision, PS payloadНачальный идентификатор00 00 00 01 61
,У некоторых производителей есть00 00 00 01 41
,После пятого байта & 0x1F = 1, это правильный PS идентификатор начала полезной нагрузки
кроме того:
0x000001BD
личные данные,такой же0x000001E0
Пропустить напрямую.
Для конкретного формата PS вы можете обратиться к другой информации в Интернете.
Кроме того, если данные содержат 0x000001, они будут экранированы в соответствии с протоколом h264 и станут 0x00000301. Включает в себя 3 побега 0x000001 -> 0x00000301 0x000002 -> 0x00000302 0x000003 -> 0x00000303
Процесс получения и разбора udp--->rtp--->ps--->h264
while ((pack = sess.GetNextPacket()) != NULL) {
loaddata = pack->GetPayloadData();
len = pack->GetPayloadLength(); /* payload type: ps */
if(pack->GetPayloadType() == 96) { /*the last packet*/
if(pack->HasMarker()) {
if(pos + len < PS_BUFFER_SIZE){
memcpy(&buff[pos],loaddata,len);
printf("!!! GetPayload len = %ld !!!! \n ",pos+len);
size_t r = ps_demuxer_input(ps, buff, pos+len);
pos = 0;
lasttime = nowtime;
}
} else {
if(pos + len < PS_BUFFER_SIZE){
memcpy(&buff[pos],loaddata,len);
pos = pos + len;
}
}
} else {
printf("!!! GetPayloadType = %d !!!! \n ",pack->GetPayloadType());
} sess.DeletePacket(pack);
}
Заполните проанализированное видео h264 и аудио g711 в очередь для обработки.
vedio
/* SPS frame 00 00 00 01 67 PPS frame 00 00 00 01 68 I frame 00 00 00 01 65 P slice 00 00 00 01 41/61 */
if(FindStartCode(p)) {
data_t d;
if(rb_numitems(task->buffer) < DATA_ITEM_NMAX){
if(task->pos < ITEM_BUFFER_SIZE) {
memcpy(d.buf,task->buf, task->pos);
d.size = task->pos; d.type = H264;
rb_put(task->buffer, &d);
fflush(stdout);
}
}
task->pos = 0;
if((task->pos + bytes) < TASK_BUFFER_SIZE){
memcpy(&(task->buf[task->pos]),p,bytes);
task->pos = task->pos + bytes;
}
} else {
if((task->pos + bytes) < TASK_BUFFER_SIZE){
memcpy(&(task->buf[task->pos]),p,bytes);
task->pos = task->pos + bytes;
}
}
audio
data_t d;
if (rb_numitems(task->buffer) < DATA_ITEM_NMAX) {
if(bytes < ITEM_BUFFER_SIZE){
memcpy(d.buf, p, bytes);
d.size = bytes;
d.type = G711A;
rb_put(task->buffer, &d);
fflush(stdout);
}
}
Анализ кадров данных h264 nalu может относиться к обработке live555
Ссылка: H264VideoRTPSink.cpp.
Boolean H264VideoRTPSource::processSpecialHeader(BufferedPacket* packet, unsigned& resultSpecialHeaderSize) {
unsigned char* headerStart = packet->data();
unsigned packetSize = packet->dataSize();
unsigned numBytesToSkip; // Check the 'nal_unit_type' for special 'aggregation' or 'fragmentation' packets:
if (packetSize < 1)
return False;
fCurPacketNALUnitType = (headerStart[0]&0x1F);
switch (fCurPacketNALUnitType) {
case 24: { // STAP-A
numBytesToSkip = 1; // discard the type byte
break; }
case 25:
case 26:
case 27: { // STAP-B, MTAP16, or MTAP24
numBytesToSkip = 3; // discard the type byte, and the initial DON
break;
}
case 28:
case 29: { // // FU-A or FU-B // For these NALUs, the first two bytes are the FU indicator and the FU header. // If the start bit is set, we reconstruct the original NAL header into byte 1:
if (packetSize < 2)
return False;
unsigned char startBit = headerStart[1]&0x80;
unsigned char endBit = headerStart[1]&0x40;
if (startBit) {
fCurrentPacketBeginsFrame = True;
headerStart[1] = (headerStart[0]&0xE0)|(headerStart[1]&0x1F);
numBytesToSkip = 1;
} else { // The start bit is not set, so we skip both the FU indicator and header:
fCurrentPacketBeginsFrame = False;
numBytesToSkip = 2;
}
fCurrentPacketCompletesFrame = (endBit != 0);
break;
}
default: { // This packet contains one complete NAL unit:
fCurrentPacketBeginsFrame = fCurrentPacketCompletesFrame = True;
numBytesToSkip = 0;
break;
}
} resultSpecialHeaderSize = numBytesToSkip; return True; }
H264VideoRTPSink.cpp
H264VideoRTPSink* H264VideoRTPSink ::createNew(UsageEnvironment& env, Groupsock* RTPgs, unsigned char rtpPayloadFormat, char const* sPropParameterSetsStr) {
u_int8_t* sps = NULL;
unsigned spsSize = 0;
u_int8_t* pps = NULL;
unsigned ppsSize = 0;
unsigned numSPropRecords;
SPropRecord* sPropRecords = parseSPropParameterSets(sPropParameterSetsStr, numSPropRecords);
for (unsigned i = 0; i < numSPropRecords; ++i) {
if (sPropRecords[i].sPropLength == 0)
continue; // bad data
u_int8_t nal_unit_type = (sPropRecords[i].sPropBytes[0])&0x1F;
if (nal_unit_type == 7/*SPS*/) {
sps = sPropRecords[i].sPropBytes;
spsSize = sPropRecords[i].sPropLength;
} else if (nal_unit_type == 8/*PPS*/) {
pps = sPropRecords[i].sPropBytes;
ppsSize = sPropRecords[i].sPropLength;
}
}
H264VideoRTPSink* result = new H264VideoRTPSink(env, RTPgs, rtpPayloadFormat, sps, spsSize, pps, ppsSize);
delete[] sPropRecords;
return result;
}
Или h264bitstream также имеет анализ h264 nalu
https://www.cnblogs.com/dong1/p/10149980.html
Разделение атрибутов кадра
SPS:
0x67 header & 0x1F = 7
PPS:
0x68 header & 0x1F = 8
SEI:
0x66 header & 0x1F = 6
I Frame:
0x65 header & 0x1F = 5
P Frame:
0x41 header & 0x1F = 1
Анализ h264 nalu не по теме, я кстати об этом упомянул. Основной упор здесь делается на анализ потока ps.
05 84 80 60 6d ee 00 b5 62 60 00 00 a5 f6
00 00 01 ba
44 76 55 85 74 01 02 5f 03 fe ff ff 00 00 86 24 00 00 01 e0
21 ba 8c 80 0a 21 1d 95 61 5d ff ff ff ff f8 00 00 00 01 61
e0 40 00 59 13 ff 01 23 44 a1 02 38 33 0f 99 df 89 95 01 9e 6d 31 00 2a 8f 05 a5 fb 96 67 38 b8 7f c5 73 bb 25 b6 96 3d 0c 15 0e a4 ed 95 30 6b 43 35 51 9a 04 a1 89 26 6a 6a fc 64 c6 44 37 2a 32 6d 16 12 41 83 53 42 d7 66 e3 51 6b 8e bc 8f 40 73 2a 22 9d a0 d7 b9 c1 ed f8 a5 14 91 2d 8e 90 07 0e b4 2e 4a 0e cb 03 4b 73 f4 1a 49 0a d3 1f bb 72 c5 28 13 b7 9b 35 a0 18 3a c0 91 73 99 1d 4c dd 3b fd eb ce 8e 73 79 34 8a 05 6d 98 d6 a9 20 d3 43 44 d9 b3 cd be 5b f6 74 86 f4 67 26 2f a1 be fb 5c 2c aa 81 4d 51 85 06 c7 65 82 52 47 05 5b ae 76 93
анализ данных, Это кадр P
Длина: 05 84
rtp header
80 60 6d ee 00 b5 62 60 00 00 a5 f6
ps header
00 00 01 ba ...
pes header
00 00 01 e0 ...
ps payload
P 00 00 00 01 61/41 ...
В режиме RTP через TCP имеется больше полей длины, чем в режиме RTP через UDP. Информация о длине может использоваться для группировки пакетов для формирования полного пакета TCP. Полный пакет TCP без информации о длине является пакетом RTP.
Поскольку нижний уровень TCP оптимизирует распаковку и привязку пакетов, уровень приложения требует специальной обработки. Вы можете обратиться к режиму TCP в jrtplib. Библиотека jrtplib уже выполнила распаковку и привязку пакетов.
Процесс получения и разбора tcp--->rtp--->ps--->h264
Предоставьте пример кода: пример RTP через TCP/UDP в jrtplib.
https://www.cnblogs.com/dong1/p/12179996.html
Теперь, когда данные, полученные от устройства, полностью проанализированы, как их по очереди упаковать, фрагментировать и отправить?
Самое хлопотное это упаковка ps, можно скопировать ffmpeg-4.1/doc/examples/muxing.c
Aac+h264 (также поддерживаются некоторые другие форматы аудио и видео) можно напрямую инкапсулировать в поток PS.
Для запечатанного ps buf следуйте формату FU-A и разрежьте его на фрагменты каждые 1400 байт. Добавьте 12-байтовый rtp-заголовок перед каждым фрагментом. Установите для последнего фрагмента значение «Маркер» и отправьте его.
В режиме rtp over tcp перед заголовком rtp необходимо добавить 2 байта, поэтому заголовок rtp over tcp составляет 14 байт, а заголовок rtp over udp — 12 байт.
Подробную информацию о различных типах шардинга см. в rfc3984: https://datatracker.ietf.org/doc/rfc3984/.
Оригинальный текст: https://my.oschina.net/u/4293666/blog/3494983