Замена av_read_frame () для уменьшения задержки - PullRequest
0 голосов
/ 04 июня 2018

Я реализую (очень) приложение C ++ для потоковой передачи видео с низкой задержкой, используя ffmpeg.Клиент получает видео, которое закодировано с предустановкой нулевой латентности в x264, поэтому нет необходимости в буферизации.Как описано здесь , если вы используете av_read_frame () для чтения пакетов закодированного видеопотока, у вас всегда будет хотя бы одна задержка кадра из-за внутренней буферизации, выполняемой в ffmpeg.Поэтому, когда я вызываю av_read_frame () после того, как кадр n + 1 был отправлен клиенту, функция вернет кадр n.

Избавление от этой буферизации путем установки флагов AVFormatContext AVFMT_FLAG_NOPARSE |AVFMT_FLAG_NOFILLIN, как предложено в источнике , отключает синтаксический анализ пакета и, следовательно, прерывает декодирование, как отмечено в источнике .

Поэтому я пишу свой собственный приемник и анализатор пакетов.Во-первых, вот соответствующие шаги рабочего решения (включая задержку в один кадр) с использованием av_read_frame ():

AVFormatContext *fctx;
AVCodecContext *cctx;
AVPacket *pkt;
AVFrame *frm;

//Initialization of AV structures
//…

//Main Loop
while(true){

    //Receive packet
    av_read_frame(fctx, pkt);

    //Decode:
    avcodec_send_packet(cctx, pkt);
    avcodec_receive_frame(cctx, frm);

    //Display frame
    //…
}

И ниже мое решение, которое имитирует поведение av_read_frame (), насколько я мог воспроизвести его.Мне удалось отследить исходный код av_read_frame () до ff_read_packet (), но я не могу найти источник AVInputformat.read_packet ().

int tcpsocket;
AVCodecContext *cctx;
AVPacket *pkt;
AVFrame *frm;
uint8_t recvbuf[(int)10e5];
memset(recvbuf,0,10e5);
int pos = 0;

AVCodecParserContext * parser = av_parser_init(AV_CODEC_ID_H264);
parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
parser->flags |= PARSER_FLAG_USE_CODEC_TS;

//Initialization of AV structures and the tcpsocket
//…

//Main Loop
while(true){

    //Receive packet
    int length = read(tcpsocket, recvbuf, 10e5);
    if (length >= 0) {

        //Creating temporary packet
        AVPacket * tempPacket = new AVPacket;
        av_init_packet(tempPacket);
        av_new_packet(tempPacket, length);
        memcpy(tempPacket->data, recvbuf, length);
        tempPacket->pos = pos;
        pos += length;
        memset(recvbuf,0,length);

        //Parsing temporary packet into pkt
        av_init_packet(pkt);
        av_parser_parse2(parser, cctx,
            &(pkt->data), &(pkt->size),
            tempPacket->data, tempPacket->size,
            tempPacket->pts, tempPacket->dts, tempPacket->pos
            );

        pkt->pts = parser->pts;
        pkt->dts = parser->dts;
        pkt->pos = parser->pos;

        //Set keyframe flag
        if (parser->key_frame == 1 ||
            (parser->key_frame == -1 &&
            parser->pict_type == AV_PICTURE_TYPE_I))
            pkt->flags |= AV_PKT_FLAG_KEY;
        if (parser->key_frame == -1 && parser->pict_type == AV_PICTURE_TYPE_NONE && (pkt->flags & AV_PKT_FLAG_KEY))
            pkt->flags |= AV_PKT_FLAG_KEY;
        pkt->duration = 96000; //Same result as in av_read_frame()

        //Decode:
        avcodec_send_packet(cctx, pkt);
        avcodec_receive_frame(cctx, frm);
        //Display frame
        //…
    }
}

Я проверил поля полученного пакета ( pkt ) простодо avcodec_send_packet () в обоих решениях.Они, насколько я могу судить, идентичны.Единственной разницей может быть фактическое содержание pkt-> data .Мое решение прекрасно декодирует I-кадры, но ссылки в P-кадрах кажутся неработающими, что приводит к значительным артефактам и сообщениям об ошибках, таким как «неверный префикс уровня», «ошибка при декодировании MB xx» и т. П.

Буду очень признателен за любые подсказки.

PS: В настоящее время я разработал обходной путь: на видеосервере, после отправки пакета, содержащего закодированные данные кадраЯ отправляю один фиктивный пакет, который содержит только разделители, отмечающие начало и конец пакета.Таким образом, я пропускаю фактические кадры видеоданных через av_read_frame ().Я сбрасываю фиктивные пакеты сразу после av_frame_read ().

1 Ответ

0 голосов
/ 12 июня 2018

av_parser_parse2 () не обязательно потребляет ваш tempPacket за один раз.Вы должны вызвать его в другом цикле и проверить его возвращаемое значение, как в API документах .

...