我正在使用ffmpeg实现一个(非常)低延迟的视频流C ++应用程序。客户端接收使用x264的zerolatency预设编码的视频,因此无需缓冲。如上所述here,如果您使用 av_read_frame ()来读取编码视频流的数据包,则由于在ffmpeg中完成内部缓冲,您将始终至少有一个帧延迟。因此,当我将第n + 1帧发送到客户端之后调用 av_read_frame ()时,该函数将返回第n帧。
通过设置AVFormatContext标志去除此缓冲AVFMT_FLAG_NOPARSE | source中建议的AVFMT_FLAG_NOFILLIN禁用数据包解析,因此会中断解码,如source中所述。
因此,我正在编写自己的数据包接收器和解析器。首先,以下是使用 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
//…
}
}
我在两个解决方案中的 avcodec_send_packet ()之前检查了结果数据包的fields( pkt )。就我所说的而言,它们完全相同。唯一的区别可能是 pkt-> data 的实际内容。我的解决方案很好地解码了I帧,但P帧中的引用似乎被破坏,导致严重的伪像和错误消息,例如“无效级别前缀”,“解码MB xx时出错”等等。
我会非常感谢任何提示。
PS:我暂时开发了一种解决方法:在视频服务器中,在发送包含帧的编码数据的数据包之后,我发送一个仅包含分隔符标记的虚拟数据包数据包的开头和结尾。这样,我通过 av_read_frame ()推送实际的视频数据帧。我在 av_frame_read ()之后立即丢弃虚拟数据包。