Модераторы: feodorv
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Реализация RTP протокола. 
:(
    Опции темы
batchar
  Дата 11.4.2015, 12:00 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 2
Регистрация: 11.4.2015

Репутация: нет
Всего: нет



Всем привет! Прошу помощи в реализации 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


Буду рад любой подсказке! Ребят, прошу только не надо мне тыкать разными либами  smile 


Работа с аппаратным энкодером  осуществляется через 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));
}





Спасибо за проявленное  внимание! Бюсь над проблемой уже несколько дней  smile 

Это сообщение отредактировал(а) batchar - 11.4.2015, 13:05
PM MAIL   Вверх
feodorv
Дата 12.4.2015, 01:50 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 2214
Регистрация: 30.7.2011

Репутация: 10
Всего: 45



Цитата(batchar @  11.4.2015,  12:00 Найти цитируемый пост)
    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;

Здесь точно нужно static? А то при последующих вызовах функции send_data_to_rtp() эти переменные непереинициализируются...


Далее. Здесь понятно:
Цитата(batchar @  11.4.2015,  12:00 Найти цитируемый пост)
        /* Копирую буфер без ЗАГОЛОВКА(т.е. первого байта, являющийся заголовком NALU)! */
        memcpy(nalu_playload, data + 1, len-1);
Если data+1, то len-1. Здесь не понятно:
Цитата(batchar @  11.4.2015,  12:00 Найти цитируемый пост)
/* Копирую буфер без ЗАГОЛОВКА(т.е. первого байта, являющийся заголовком NALU)! */
                memcpy(nalu_playload, data + 1, RTP_PAYLOAD_MAX_SIZE);

Почему у длины нет -1

Если один байт выпадает, то перед подсчетом числа фрагментов нужно сделать
Код
data0 = data[0];
data++;
len--;
 и далее без +1 у data, иначе можно все неверно подсчитать.


--------------------
Напильник, велосипед, грабли и костыли - основные инструменты программиста...
PM MAIL   Вверх
feodorv
Дата 12.4.2015, 02:11 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 2214
Регистрация: 30.7.2011

Репутация: 10
Всего: 45



Цитата(batchar @  11.4.2015,  12:00 Найти цитируемый пост)
        send_data_client(sendbuf, len + 13);
Тогда уж и здесь len-1+13...


--------------------
Напильник, велосипед, грабли и костыли - основные инструменты программиста...
PM MAIL   Вверх
batchar
Дата 12.5.2015, 19:05 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 2
Регистрация: 11.4.2015

Репутация: нет
Всего: нет



Цитата(feodorv @ 12.4.2015,  02:11)
Цитата(batchar @  11.4.2015,  12:00 Найти цитируемый пост)
        send_data_client(sendbuf, len + 13);
Тогда уж и здесь len-1+13...

Нет, на самом деле те "неучтенные" байты роли не играют, в силу того, что NALU установлен менее, чем "стандартный". Ну, так да - косяк небольшой признаю. Статические переменные - так и задумывалось: счетчики, да и просто какие-то объявленны, но они перед "употреблением" всегда "готовятся". 

Магия тут.
Проблемы со стримом появляется только при стриммминге "живого" потока, т.е. подхватываю кадры от OpenMAX и передаю в сеть. Что самое поразительное и непонятное, сбивающее с толку, так это при стримминге "статического" файла, с предварительным парсингом по кадрам и засовыванием их в фунцию "send_data_to_rtp", вроде того, как от OpenMAX приходит, стримм идет на "УРА" - нет ошибок. Времменые интервалы между кадрами при отправке- соизмеримые и в случае статич.файла(чаитаю данные и покадрово передаю через 33мс для 30фпс) и в случае "живого".  
Тот же самый код ломает стрим при живом вещании просто в хлам. Однако, стоит мне приписать при передачи в "send_data_to_rtp" мифический  FPS > 200(для того же живого) - стрим идет норм при живом потоке, но это КАК-ТО НЕ НОМАЛЬНО. Данные не бьются в любом случае.
PM MAIL   Вверх
feodorv
Дата 14.5.2015, 01:04 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 2214
Регистрация: 30.7.2011

Репутация: 10
Всего: 45



Цитата(batchar @  12.5.2015,  19:05 Найти цитируемый пост)
мифический  FPS > 200

Вы про эту строчку:
Цитата(batchar @  11.4.2015,  12:00 Найти цитируемый пост)
    ts_current += (90000 / framerate);

?

Гм. Честно говоря, я не знаю, насколько тут все правильно сделано... Согласно Some Frequently Asked Questions about RTP:
Цитата

For video, time clock rate is fixed at 90 kHz. The timestamps generated depend on whether the application can determine the frame number or not. If it can or it can be sure that it is transmitting every frame with a fixed frame rate, the timestamp is governed by the nominal frame rate. Thus, for a 30 f/s video, timestamps would increase by 3,000 for each frame, for a 25 f/s video by 3,600 for each frame. If a frame is transmitted as several RTP packets, these packets would all bear the same timestamp. If the frame number cannot be determined or if frames are sampled aperiodically, as is typically the case for software codecs, the timestamp has to be computed from the system clock (e.g., gettimeofday()). 

То есть стоит не прибавлять каждый раз фиксированную величину, а вычислять реальное время отправки очередной пачки пакетов в милисекундах, адаптированных к 90кГц:
Цитата

Let tSEI1, tSEI2, ..., tSEIn be the display timestamps carried in
      the SEI message of an access unit, where tSEI1 is the earliest of
      all such timestamps.  Let tmadjst() be a function that adjusts the
      SEI messages time scale to a 90-kHz time scale.  Let TS be the RTP
      timestamp.  Then, the display time for the event associated with
      tSEI1 is TS.  The display time for the event with tSEIx, where x
      is [2..n], is TS + tmadjst (tSEIx - tSEI1).



Было бы недурно увидеть и реальную временную разницу между посылкой кадров, например, через GetTickCount().


--------------------
Напильник, велосипед, грабли и костыли - основные инструменты программиста...
PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Сети | Следующая тема »


 




[ Время генерации скрипта: 0.1318 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.