Я реализую (очень) приложение 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 ().