Всем привет! Прошу помощи в реализации RTP протокола для стриминга H.264 видео. Имею в своем распоряжении Raspberry PI(B+) и модуль камеры. В состав Raspberry входит аппаратный энкодер H.264. Энкодер кодирует "сырые" кадры поступающие от камеры в H.264. Кодированое видео хочу отстримить, завернув в RTP.
Однако, в результате плеер не может нормально воспроизвести полученый RTP стрим: рассыпается и тормозит видео. I-frame является через каждые 30 кадров. Смотря на ffplay у меня сложилось впечатление, что все P-кадры неправильно декодируются. Меньший интервал появления I-frame проблемы не решил.
Перед каждым I-frame энкодер генерирует [SPS] [PPS] кадры, которые я закидываю в отдельные пакеты RTP. Поток H.264 выгядит так: [SPS] [PPS] [I-Frame] [P-FRAME] [P-FRAME] [P-FRAME] .... [SPS] [PPS] [I-Frame] ......
Лог vlc при просмтре RTP стрима:
| Код | [h264 @ 0x90bba80] error while decoding MB 19 14, bytestream (-3) [h264 @ 0x90bba80] concealing 50 DC, 50 AC, 50 MV errors [h264 @ 0x90bb680] error while decoding MB 19 14, bytestream (-3) [h264 @ 0x90bb680] concealing 50 DC, 50 AC, 50 MV errors [h264 @ 0x90bb680] error while decoding MB 19 14, bytestream (-3) [h264 @ 0x90bb680] concealing 50 DC, 50 AC, 50 MV errors
|
Буду рад любой подсказке! Ребят, прошу только не надо мне тыкать разными либами
Работа с аппаратным энкодером осуществляется через OpenMAX. Приведу часть кода настраивающий экнодер:
| Код | /* Настройка энкодера видео */ OMX_INIT_STRUCTURE(ep->encoder_portdef); ep->encoder_portdef.nPortIndex = 201; if((r = OMX_GetParameter(ctx->encoder, OMX_IndexParamPortDefinition, &ep->encoder_portdef)) != OMX_ErrorNone) die("Failed to get port definition for encoder output port 201");
/* Конфигуарция исходящего порта энкодера */ ep->encoder_portdef.format.video.nFrameWidth = cp->camera_portdef.format.video.nFrameWidth; ep->encoder_portdef.format.video.nFrameHeight = cp->camera_portdef.format.video.nFrameHeight; ep->encoder_portdef.format.video.xFramerate = cp->camera_portdef.format.video.xFramerate; ep->encoder_portdef.format.video.nStride = cp->camera_portdef.format.video.nStride; ep->encoder_portdef.format.video.eCompressionFormat = OMX_VIDEO_CodingAVC; ep->encoder_portdef.format.video.nBitrate = dp->video.bitrate; if((r = OMX_SetParameter(ctx->encoder, OMX_IndexParamPortDefinition, &ep->encoder_portdef)) != OMX_ErrorNone) die("Failed to set port definition for encoder output port 201");
/* конфигурация битрейта */ OMX_INIT_STRUCTURE(ep->bitrate); // ep->bitrate.eControlRate = OMX_Video_ControlRateConstant; ep->bitrate.eControlRate = OMX_Video_ControlRateVariable; ep->bitrate.nTargetBitrate = ep->encoder_portdef.format.video.nBitrate; ep->bitrate.nPortIndex = 201; if((r = OMX_SetParameter(ctx->encoder, OMX_IndexParamVideoBitrate, &ep->bitrate)) != OMX_ErrorNone) die("Failed to set bitrate for encoder output port 201");
OMX_VIDEO_CONFIG_AVCINTRAPERIOD idr; OMX_INIT_STRUCTURE(idr); idr.nPortIndex = 201; if((r=OMX_GetParameter(ctx->encoder, OMX_IndexConfigVideoAVCIntraPeriod, &idr)) != OMX_ErrorNone) die("Failed to get encoder params\n");
OMX_INIT_STRUCTURE(idr); idr.nPortIndex = 201; idr.nIDRPeriod = 30; if((r=OMX_SetParameter(ctx->encoder, OMX_IndexConfigVideoAVCIntraPeriod, &idr)) != OMX_ErrorNone) die("Failed to get encoder params\n");
|
Энкодер отдает данные, пакую данные в RTP пакет и пишу в сокет. Один из фреймов выгялит след.образом: 0000 0001 2588 804f ec82 b9bf e0b7 1789 .....
До вызова функции send_data_to_rtp(приведена ниже) отсекаю 00 00 00 01 и заголовок NALU. В готовом пакете после заголовка RTP(12 байт) и еще двух байтов(заголовки для фрагментации, поскольку все фремы фреймы, кроме SPS и SPP, большие и не вмещаются в MTU) последовательность в готовом пакете выгядит так 88 804f ec82 b9bf e0b7 1789 .....
Данные фрейма в правильном порядке «растекаются» по пакетам(смотрел в вайршарке) ничего лишнего не копируется и не теряется. Следовательно вопрос: правильно ли я формирую заголовки пакетов? Смотрел RFC и кучу примеров, однако вроде правильно я делаю. Может какие-то дополнительные или специфичные параметры энкодеру передать надо? Может еще что-то надо добавить? Привожу функцию-упаковщик в RTP ниже вместе с определениями структур:
| Код | // заголовки #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)); }
|
Спасибо за проявленное внимание! Бюсь над проблемой уже несколько дней |