I'm using libavcodec library and h264 codec to prepare the video stream on one end, transmit the encoded frames to the other PC and there decode it.
我使用libavcodec库和h264编解码器在一端准备视频流,将编码帧传输到另一台PC并在那里解码。
What I noticed after receiving very first packet (first encoded video frame) and feeding decoder with it, it is not possible to decode that frame. Only when I receive another frame the first one can be decoded but 'current' one not. So in the end I have constantly one frame delay on the decoder side.
我注意到,在收到第一个包(第一个编码的视频帧)并将其提供给解码器后,无法解码该帧。只有当我收到另一个帧时,第一个帧才能被解码,但“当前”帧不能。所以最后,我在解码端一直有一个帧延迟。
I was trying different preset
s (focusing rather on 'ultrafast'
), also 'zerolatency'
tune
, also whole variety of bit_rate
values of AVCodecContext
.
我尝试了不同的预置(重点是‘超快’),还有‘零度’调谐,还有AVCodecContext的各种码率值。
I also tried to flush (with nullptr packet) after injecting first frame data, just to check if it is maybe because of some internal buffers optimization - the frame still not decoded.
Experimenting with other codecs (like mpeg4) gives even worse 'dalay' in number of frames to the point when when first frames can become decodable.
我也试图在注入第一帧数据后刷新(使用nullptr包),只是为了检查是否可能是因为内部缓冲区优化-帧仍然没有解码。使用其他编解码器(如MPEG4)进行试验时,在帧数量上甚至会出现更差的延迟,直到第一帧变得可解码时。
Is it normal, unavoidable because of some internal mechanisms? Otherwise how I can achieve real zero latency.
这是正常的,是因为某些内在机制而不可避免的吗?否则我如何才能实现真正的零延迟。
Supplementary setup information:
补充设置信息:
max_b_frames
set to 0 (higher value gives even more delay)
pix_fmt
set to AV_PIX_FMT_YUV420P
edit:
编辑:
Answering some comment question:
回答一些评论问题:
(1) What is the decoder (or playback system)?
Custom decoder written using libavcodec, the decoded frames are later displayed on screen by OpenGL.
使用libavcodec编写的自定义解码器,解码后的帧将通过OpenGL显示在屏幕上。
parser_ = av_parser_init(AV_CODEC_ID_H264);
codec_ = avcodec_find_decoder(AV_CODEC_ID_H264);
context_ = avcodec_alloc_context3(codec_);
context_->width = 1024;
context_->height = 768;
context_->thread_count = 1;
if ((codec_->capabilities & AV_CODEC_CAP_TRUNCATED) == 0)
{
context_->flags |= AV_CODEC_FLAG_TRUNCATED;
}
if (avcodec_open2(context_, codec_, nullptr) < 0)
{
throw std::runtime_error{"avcodec_open2 failed"};
}
avcodec_flush_buffers(context_);
- then player periodically calls of the method of decoder that suppose to check if the another frame can be retrieved and displayed:
auto result = avcodec_receive_frame(context_, frame_);
if (!buffer_.empty())
{ // upload another packet for decoding
int used;
if (upload_package(buffer_.data(), buffer_.size(), &used))
{
buffer_.erase(buffer_.begin(), buffer_.begin() + used);
}
}
if (result == AVERROR(EAGAIN) || result == AVERROR_EOF || result < 0)
{
return false;
}
yuv_to_rgb();
return true;
boolean return value informs if the decoding succeeded, and every time the buffer where the incomming packets are stored is checked and uploaded to libavcodec decoder
布尔返回值通知解码是否成功,每次检查存储传入分组的缓冲区并将其上载到libavcodec解码器
- and that is how the method that uploads the buffer looks like:
bool upload_package(const void* data, const std::size_t size, int* used)
{
auto result = av_parser_parse2(parser_, context_, &packet_->data, &packet_->size, reinterpret_cast<const std::uint8_t*>(data), size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
if (result < 0)
{
return false;
}
*used = result;
if (packet_->size != 0)
{
result = avcodec_send_packet(context_, packet_);
if (result < 0)
{
return false;
}
}
return true;
}
(2) If possible, save each one as a .bin
file and then share the links with us for testing.
I will try to figure out something...
我会努力想办法的..。
(3) Show example C++ code of your encoder settings for H264...
codec_ = avcodec_find_encoder(AV_CODEC_ID_H264);
context_ = avcodec_alloc_context3(codec_);
context_->bit_rate = 1048576; // 1xMbit;
context_->width = 1024;
context_->height = 768;
context_->time_base = {1, 30}; // 30 fps
context_->pix_fmt = AV_PIX_FMT_YUV420P;
context_->thread_count = 1;
av_opt_set(context_->priv_data, "preset", "ultrafast", 0);
av_opt_set(context_->priv_data, "tune", "zerolatency", 0);
avcodec_open2(context_, codec_, nullptr);
frame_->format = AV_PIX_FMT_YUV420P;
frame_->width = 1024;
frame_->height = 768;
av_image_alloc(frame_->data, frame_->linesize, 1024, 768, AV_PIX_FMT_YUV420P, 32);
rgb_to_yuv();
frame_->pts = frame_num_++;
auto result = avcodec_send_frame(context_, frame_);
while (result >= 0)
{
result = avcodec_receive_packet(context_, packet_);
if (result == AVERROR(EAGAIN) || result == AVERROR_EOF)
{
return;
}
else if (result < 0)
{
throw std::runtime_error{"avcodec_receive_packet failed"};
}
// here the packet is send to the decoder, the whole packet is stored on the mentioned before buffer_ and uploaded with avcodec_send_packet
// I can also add that the whole buffer/packet us uploaded at once
stream_video_data(packet_->data, packet_->size);
}
av_packet_unref(packet_);
}
edit2:
编辑2:
I think I figured out the issue that I had.
我想我找到了我的问题所在。
For every incoming data packet (encoded frame) I was calling first av_parser_parse2
, and then I was sending the data through avcodec_send_packet
.
And I was not recalling that procedure having empty buffer_
, so for the first frame data the av_parser_parse2
was never called after uploading it through avcodec_send_packet
, for the second frame it was called and the first frame was parsed, so it could be properly decoded, but for that (second) frame the parse2 was also not called, and so on ...
对于每个传入的数据分组(编码帧),我首先调用av_parser_parse2,然后通过avcodec_end_Packet发送数据。我没有回想起缓冲区为空的过程,因此对于第一帧数据,在通过avcodec_end_Packet上传数据后,从未调用av_parser_parse2,对于第二帧,它被调用并解析第一帧,因此它可以被正确解码,但对于(第二)帧,也不调用parse2,依此类推……
So the issue in my case was wrong sequence of av_parser_parse2
and avcodec_send_packet
to handle the encoded data.
因此,在我的例子中,问题是av_parser_parse2和avcodec_end_Packet处理编码数据的顺序错误。
更多回答
There is not enough info to start helping you. (1) What is the decoder (or playback system)? (2) How can we check your first 2 packets? If possible, save each one as a .bin
file and then share the links with us for testing. (3) Show example C++ code of your encoder settings for H264. It's possible you are getting one complete frame being divided such that it needs to be sent within two packets but nobody knows what you did (your settings) to make it output like that...
信息不足,无法开始帮助您。(1)解码器(或回放系统)是什么?(2)我们如何检查您的前2个包?如果可能,请将每个链接保存为.bin文件,然后与我们共享链接以供测试。(3)显示您的H.64编码器设置的示例C++代码。您可能会得到一个完整的帧被分割,因此它需要在两个包内发送,但没有人知道您做了什么(您的设置)让它像这样输出……
@VC.One thanks for comment, I uploaded some code snippets for the completion, but I think I found the issue, my upload_package
was not well organised and the parsing procedure was not called at proper moment - I will edit to explain that.
@VC.谢谢你的评论,我上传了一些代码片段来完成,但我想我发现了问题,我的Upload_Package组织得不好,解析过程没有在适当的时候调用-我会编辑来解释这一点。
@MarekKijo - put yourself into the shoes of the dev who wants to reproduce. 1. none of your code snippet can be compiled - Give a complete minimal compliable example 2. include the sample media/stream as file Take a look at the question and my answer of stackoverflow.com/questions/73036143/… The question is technically not related to your issue but it contains a minimal example that compiles with all the data. So you can get a good idea what I expect.
@MarekKijo-设身处地为想要生育的开发人员着想。1.你的任何代码片段都不能被编译--给出一个完整的最小的可编译的例子2.包括样本媒体/流作为文件看一看stackoverflow.com/Questions/73036143/…上的问题和我的答案从技术上讲,这个问题与您的问题无关,但它包含一个包含所有数据的最小示例。这样你就能很好地了解我的期望了。
我是一名优秀的程序员,十分优秀!