Всем привет! Может подскажете как решить такую досадную проблему? Пишу небольшой RTP сервер для стриминга H.264 видео.
Предстала неизвестная проблема: через некторое время(20-30 секунд) перестает корректно воспроизводится стрим(картинка рассыпается). Я подозреваю, что как-то не так упаковываю кадры H.264 в RTP.
Кадры формируются аппаратным кодировщиком Raspberry Pi, работа с которым осуществляется через OpenMAX. Источник данных для кодировщика - камера. Ну, это собственно не так важно.
Полученыей кадр передаются небольшой нижележащей функции(send_data_to_rtp), которая пакует его в RTP. Принимает аргументы: data - указатель на изображение в памяти, len - размер изображения, framerate - колличество кадров в секунду. Оставил достаточное количество комментариев к коду.
// заголовки
#define BUF_SIZE 1500
#define RTP_PAYLOAD_MAX_SIZE 1400
/* RTP ЗАГОЛОВОК. 12 байт */
typedef struct{
/* первый байт */
uint8_t csrc_len: 4;
uint8_t extension: 1;
uint8_t padding: 1;
uint8_t version: 2;
/* второй байт */
uint8_t payload_type: 7;
uint8_t marker: 1;
/* третий-четвертый байты */
uint16_t seq_no;
/* пятый-восьмой байты */
uint32_t timestamp;
/* девятый-двенадцатый байт */
uint32_t ssrc;
}__attribute__ ((packed)) rtp_header;
typedef struct {
uint8_t type: 5;
uint8_t nri: 2; /* */
uint8_t f: 1; /* */
}__attribute__ ((packed)) nalu_header;
typedef struct {
uint8_t type: 5;
uint8_t nri: 2;
uint8_t f: 1;
} __attribute__ ((packed)) fu_indicator;
typedef struct {
uint8_t type: 5;
uint8_t r: 1;
uint8_t e: 1;
uint8_t s: 1;
} __attribute__ ((packed)) fu_header;
.......
// Формируем пакет
static void send_data_to_rtp(uint8_t *data, int len, int framerate)
{
static uint8_t sendbuf[BUF_SIZE];
static uint32_t ts_current = 0;
static uint16_t seq_num = 0;
static uint16_t pack_num, last_pack_size, current_pack;
uint8_t *nalu_playload;
/* заголовок RTP */
rtp_header *rtp_hdr;
/* Заголовок NALU */
nalu_header *nalu_hdr;
/* Заголовк NALU фрагментированых данных */
fu_indicator *fu_ind;
/* заголовк NALU фрагментированых данных. Идентифицирует фрагмент */
fu_header *fu_hdr;
ts_current += (90000 / framerate);
memset(sendbuf, 0, sizeof(sendbuf));
rtp_hdr = (rtp_header*)&sendbuf[0];
rtp_hdr->version = 2;
rtp_hdr->marker = 0;
rtp_hdr->csrc_len = 0;
rtp_hdr->extension = 0;
rtp_hdr->padding = 0;
rtp_hdr->ssrc = htonl(SSRC_NUM);
rtp_hdr->payload_type = TYPE_H264;
rtp_hdr->timestamp = htonl(ts_current);
if (len <= RTP_PAYLOAD_MAX_SIZE) {
rtp_hdr->marker = 1;
rtp_hdr->seq_no = htons(++seq_num);
nalu_hdr = (nalu_header*)&sendbuf[12];
/* тип фрагмента */
nalu_hdr->type = data[0] & 0x1f;
nalu_hdr->f = data[0] & 0x80;
nalu_hdr->nri = data[0] & 0x60 >> 5;
nalu_playload = (uint8_t*)&sendbuf[13];
/* Копирую буфер без ЗАГОЛОВКА(т.е. первого байта, являющийся заголовком NALU)! */
memcpy(nalu_playload, data + 1, len-1);
send_data_client(sendbuf, len + 13);
} else {
/* пакет не помещается в MTU, значит будем фрагментировать.
* Начало s = 1, e = r = 0
* Середина s = 0, e = 0 r = 0
* Конец s = 0, e = 1 r = 0
*/
/* определяю требуемое колличество пакетов */
pack_num = (len % RTP_PAYLOAD_MAX_SIZE) ? (len / RTP_PAYLOAD_MAX_SIZE + 1) : (len / RTP_PAYLOAD_MAX_SIZE);
/* размер данных в последнем пакете */
last_pack_size = (len % RTP_PAYLOAD_MAX_SIZE) ? (len % RTP_PAYLOAD_MAX_SIZE) : (RTP_PAYLOAD_MAX_SIZE);
current_pack = 0;
/* описываю заголовки, которые используются на каждой иттерации */
fu_ind = (fu_indicator *)&sendbuf[12];
fu_ind->f = data[0] & 0x80;
fu_ind->nri = (data[0] & 0x60) >> 5;
/* говорим, что пакет у нас фрагментирован */
fu_ind->type = 28;
fu_hdr = (fu_header *)&sendbuf[13];
fu_hdr->type = data[0] & 0x1f;
while (current_pack < pack_num) {
rtp_hdr->seq_no = htons(++seq_num);
/* первый пакет */
if(current_pack == 0) {
/* начало фрагментированого блока */
fu_hdr->s = 1, fu_hdr->e = 0, fu_hdr->r = 0;
rtp_hdr->marker = 0;
nalu_playload = (uint8_t*)&sendbuf[14];
memset(nalu_playload, 0, RTP_PAYLOAD_MAX_SIZE);
/* Копирую буфер без ЗАГОЛОВКА(т.е. первого байта, являющийся заголовком NALU)! */
memcpy(nalu_playload, data + 1, RTP_PAYLOAD_MAX_SIZE);
send_data_client(sendbuf, RTP_PAYLOAD_MAX_SIZE + 14);
/* середина */
} else if(current_pack < pack_num - 1){
fu_hdr->s = 0, fu_hdr->e = 0, fu_hdr->r = 0;
rtp_hdr->marker = 0;
nalu_playload = (uint8_t*)&sendbuf[14];
memset(nalu_playload, 0, RTP_PAYLOAD_MAX_SIZE);
memcpy(nalu_playload, data + (current_pack * RTP_PAYLOAD_MAX_SIZE) + 1, RTP_PAYLOAD_MAX_SIZE);
send_data_client(sendbuf, RTP_PAYLOAD_MAX_SIZE + 14);
/* последний пакет */
} else {
rtp_hdr->marker = 1;
nalu_playload = (uint8_t*)&sendbuf[14];
fu_hdr->s = 0, fu_hdr->e = 1, fu_hdr->r = 0;
memset(nalu_playload, 0, RTP_PAYLOAD_MAX_SIZE);
memcpy(nalu_playload, data + (current_pack * RTP_PAYLOAD_MAX_SIZE) + 1, last_pack_size - 1);
send_data_client(sendbuf, last_pack_size - 1 + 14);
}
current_pack += 1;
}
}
}
// Тут пишу в сокет.
static void send_data_client(uint8_t *send_buf, size_t len_sendbuf)
{
sendto(socket_fd, send_buf, len_sendbuf, 0, (struct sockaddr *)&addr,sizeof(addr));
}
Один из фреймов выгялит след.образом: 0000 0001 2588 804f ec82 b9bf e0b7 1789 .....
До вызова функции send_data_to_rtp отсекаю 00 00 00 01. После заголовка RTP(12 байт) и еще двух байтов(заголовки для фрагментации) последовательность в готовом пакете выгядит так 88 804f ec82 b9bf e0b7 1789 .....
Данные фрейма в правильном порядке «растекаются» по пакетам(смотрел в вайршарке) ничего лишнего не копируется и не теряется. Следовательно вопрос: правильно ли я формирую заголовки пакетов?
При воспроизведении появляются ошибки, вида(проигрывал стрим в mplayer):
V: 22.3 0/ 0 10% 1% 0.0% 0 0
[h264 @ 0xb6497640]error while decoding MB 1 14, bytestream (-5)
[h264 @ 0xb6497640]concealing 68 DC, 68 AC, 68 MV errors
V: 22.3 0/ 0 10% 1% 0.0% 0 0
[h264 @ 0xb6497640]error while decoding MB 16 14, bytestream (-9)
[h264 @ 0xb6497640]concealing 53 DC, 53 AC, 53 MV errors
V: 22.6 0/ 0 10% 1% 0.0% 0 0
[h264 @ 0xb6497640]error while decoding MB 17 3, bytestream (-3)
[h264 @ 0xb6497640]concealing 272 DC, 272 AC, 272 MV errors
V: 22.7 0/ 0 10% 1% 0.0% 0 0