【问题标题】:How to Skip frames while decoding H264 stream?如何在解码 H264 流时跳过帧?
【发布时间】:2019-02-17 18:48:48
【问题描述】:

我正在使用 FFMPEG 解码 H264(或 H265)RTSP 流。

我的系统有两个软件:服务器和客户端

Server: Read frames from RTSP stream --> Forward frames to Client    
Client: Receive frames from Server --> Decode --> Render

我已经实现并且工作正常,但是有一个案例使我的系统工作不好。那是当服务器 - 客户端的互联网速度很慢时,帧无法实时传输到客户端。

目前,当队列达到计数限制时,我通过跳过一些帧(不发送到客户端)来处理这个问题。以下是我的总结代码

//At Server Software (include 2 threads A and B)
//Thread A: Read AVPacket and forward to Client
while(true)
{
    AVPacket packet;
    av_init_packet(&packet);
    packet.size = 0;
    packet.data = NULL;
    int ret = AVERROR(EAGAIN);
    while (AVERROR(EAGAIN) == ret)
        ret = av_read_frame(pFormatCtx, &packet);
    if(packet.size > 0)
    {
        if(mySendQueue.count < 120) //limit 120 packet in queue
            mySendQueue.Enqueue(packet); ////Thread B will read from this queue, to send packets to Client via TCP socket
        else
            ;//SkipThisFrame ***: No send
    }
}
//Thread B: Send To Client via TCP Socket
While(true)
{
    AVPacket packet;
    if(mySendQueue.Dequeue(packet))
    {
        SendPacketToClient(packet);
    }
}

//At Server Software : Receive AVPacket from Server --> Decode --> Render
While(true)
{
    AVPacket packet;
    AVFrame frame;
    ReadPacketFromServer(packet);
    if (av_decode_asyn(pCodecCtx, &frame, &frameFinished, &packet) == RS_OK)
    {
        if (frameFinished)
        {
            RenderFrame(frame);
        }
    }           
}
UINT32 __clrcall av_decode_asyn(AVCodecContext *pCodecCtx, AVFrame *frame, int *frameFinished, AVPacket *packet)
{
    int ret = -1;
    *frameFinished = 0;
    if (packet) 
    {
        ret = avcodec_send_packet(pCodecCtx, packet);
        // In particular, we don't expect AVERROR(EAGAIN), because we read all
        // decoded frames with avcodec_receive_frame() until done.
        if (ret < 0 && ret != AVERROR_EOF)
            return RS_NOT_OK;
    }

    ret = avcodec_receive_frame(pCodecCtx, frame);
    if (ret < 0 && ret != AVERROR(EAGAIN))
    {
        return RS_NOT_OK;
    }
    if (ret >= 0)
        *frameFinished = 1;

    return RS_OK;
}

我的问题是关注SkipThisFrame ***这行代码,这个算法连续跳帧,可能导致Client端的解码器出现意外错误或者Crash?

而当这样跳帧时,使Client Render 帧不正常?

有人打电话告诉我在我的情况下跳帧的正确算法吗?

非常感谢!

【问题讨论】:

  • 这个逻辑不应该是Server: Store "incoming" frames from RTSP stream --&gt; Forward "existing" frames to Client吗?
  • @VC.One:谢谢!我明白你说的,但是当互联网很慢时,我必须跳过帧才能在客户端上进行实时渲染。我认为哪种跳帧算法最好
  • 正如其他人已经说过的那样,您不能跳过 h264 流中的解码帧,因为 P 帧(预测)只包含与最后重建的帧的差异。但是,您可以做的是跳过在有问题的显示器上显示帧并限制感知的 fps。

标签: c++ ffmpeg decode h.264


【解决方案1】:

我简要阅读了AVPacket 的文档,上面写着:

对于视频,它通常应包含一个压缩帧。

理论上,您不能跳过压缩视频流的帧,因为大多数帧不包含有关该帧图像的完整信息,而仅包含与某些先前帧相比的变化。因此,如果您跳过一帧,很可能许多尾随解码帧不会包含正确的结果(直到下一个关键帧刷新整个图像)。

【讨论】:

  • 谢谢!我明白你说的,但是当互联网很慢时,我必须跳过帧才能在客户端上进行实时渲染。我认为哪种跳帧算法最好
【解决方案2】:

“我的问题是代码SkipThisFrame ***的焦点,这个算法 连续跳帧,可能会导致客户端出现解码 意外错误或崩溃?”

我注意到一件事是错误的......
您的While(true) 语句也需要break; 才能停止,否则它们将永远运行,阻塞其他功能并导致系统崩溃。想一想,你说“当循环为真时执行 XYZ 指令”,但你从来没有说什么时候停止(例如:break 退出这个 While 循环下一条说明)。计算机被困在第一个 While 循环中,并且一直重复到无穷大......

尝试这样设置:

//At Server Software (include 2 threads A and B)
//Thread A: Read AVPacket and forward to Client
while(true)
{
    AVPacket packet;
    av_init_packet(&packet);
    packet.size = 0;
    packet.data = NULL;
    int ret = AVERROR(EAGAIN);

    while (AVERROR(EAGAIN) == ret) { ret = av_read_frame(pFormatCtx, &packet); }

    if(packet.size > 0)
    {
        if(mySendQueue.count < 120) //limit 120 packet in queue
        {
            mySendQueue.Enqueue(packet); ////Thread B will read from this queue, to send packets to Client via TCP socket
        }
        //else {  } //no need for ELSE if doing nothing... //SkipThisFrame ***: No send
    }

    break; //stop this part and move to "Thead B"
}

//Thread B: Send To Client via TCP Socket
While(true)
{
    AVPacket packet;
    if( mySendQueue.Dequeue(packet) )
    { SendPacketToClient(packet); break; }
}

//At Server Software : Receive AVPacket from Server --> Decode --> Render
While(true)
{
    AVPacket packet; AVFrame frame;
    ReadPacketFromServer(packet);
    if (av_decode_asyn(pCodecCtx, &frame, &frameFinished, &packet) == RS_OK)
    { 
        if (frameFinished) { RenderFrame(frame);  break; }
    }           
}

UINT32 __clrcall av_decode_asyn(AVCodecContext *pCodecCtx, AVFrame *frame, int *frameFinished, AVPacket *packet)
{
    int ret = -1;
    *frameFinished = 0;
    if (packet) 
    {
        ret = avcodec_send_packet(pCodecCtx, packet);
        // In particular, we don't expect AVERROR(EAGAIN), because we read all
        // decoded frames with avcodec_receive_frame() until done.
        if (ret < 0 && ret != AVERROR_EOF)
            return RS_NOT_OK;
    }

    ret = avcodec_receive_frame(pCodecCtx, frame);
    if (ret < 0 && ret != AVERROR(EAGAIN))
    {
        return RS_NOT_OK;
    }
    if (ret >= 0)
        *frameFinished = 1;

    return RS_OK;
}

希望对您有所帮助。让我知道结果/错误。

【讨论】:

  • 一:这个不用担心,这只是一个大家可以看懂的摘要代码,不是完整的代码,这些是基本的,我知道控制它们。我想专注于我的问题。非常感谢!
猜你喜欢
  • 2013-09-22
  • 1970-01-01
  • 2013-12-03
  • 2012-07-05
  • 2014-10-25
  • 2018-06-09
  • 2015-12-30
  • 2018-08-24
  • 2011-08-26
相关资源
最近更新 更多