在ffmpeg H.264解码器中修改运动向量

Posted

技术标签:

【中文标题】在ffmpeg H.264解码器中修改运动向量【英文标题】:Modifying motion vectors in ffmpeg H.264 decoder 【发布时间】:2012-03-05 10:18:03 【问题描述】:

出于研究目的,我尝试在解码过程中在运动补偿之前修改每个 P 和 B 帧的 H.264 运动矢量 (MV)。我为此目的使用 FFmpeg。修改的一个例子是将每个 MV 替换为其原始空间邻居,然后使用生成的 MV 进行运动补偿,而不是原始的。请适当地指导我。

到目前为止,我已经能够对文件/libavcodec/h264_cavlc.c中的MV进行简单的修改。在函数 ff_h264_decode_mb_cavlc() 中,修改 mxmy 变量,例如,通过增加它们的值来修改解码期间使用的 MV。

例如,如下图所示,mxmy值增加了50,从而延长了解码器中使用的MV。

mx += get_se_golomb(&s->gb)+50;
my += get_se_golomb(&s->gb)+50;

但是,在这方面,对于我在第一段中提到的空间均值分析,我不知道如何访问 mxmy 的邻居。我相信这样做的关键在于操作数组,mv_cache

我执行的另一个实验是在文件 libavcodec/error_resilience.c 中。基于 guess_mv() 函数,我创建了一个新函数 mean_mv(),它在第一个 if- 内的 ff_er_frame_end() 中执行陈述。如果其中一个条件是零错误计数 (s->error_count == 0),则第一个 if 语句退出函数 ff_er_frame_end()。但是,我决定在此时插入我的 mean_mv() 函数,以便始终在错误计数为零时执行。这个实验在一定程度上产生了我想要的结果,因为我可以开始在视频的顶部看到伪影,但它们仅限于右上角。我猜我插入的函数没有完成以满足播放截止日期之类的。

下面是修改后的 if 语句。唯一的补充是我的函数,mean_mv(s)

if(!s->error_recognition || s->error_count==0 || s->avctx->lowres ||
       s->avctx->hwaccel ||
       s->avctx->codec->capabilities&CODEC_CAP_HWACCEL_VDPAU ||
       s->picture_structure != PICT_FRAME || // we dont support ER of field pictures yet, though it should not crash if enabled
       s->error_count==3*s->mb_width*(s->avctx->skip_top + s->avctx->skip_bottom)) 
        //av_log(s->avctx, AV_LOG_DEBUG, "ff_er_frame_end in er.c\n"); //KG
        if(s->pict_type==AV_PICTURE_TYPE_P)
            mean_mv(s);
        return;

这是我基于 guess_mv() 创建的 mean_mv() 函数。

static void mean_mv(MpegEncContext *s)
    //uint8_t fixed[s->mb_stride * s->mb_height];
    //const int mb_stride = s->mb_stride;
    const int mb_width = s->mb_width;
    const int mb_height= s->mb_height;
    int mb_x, mb_y, mot_step, mot_stride;

    //av_log(s->avctx, AV_LOG_DEBUG, "mean_mv\n"); //KG

    set_mv_strides(s, &mot_step, &mot_stride);

    for(mb_y=0; mb_y<s->mb_height; mb_y++)
        for(mb_x=0; mb_x<s->mb_width; mb_x++)
            const int mb_xy= mb_x + mb_y*s->mb_stride;
            const int mot_index= (mb_x + mb_y*mot_stride) * mot_step;
            int mv_predictor[4][2]=0;
            int ref[4]=0;
            int pred_count=0;
            int m, n;

            if(IS_INTRA(s->current_picture.f.mb_type[mb_xy])) continue;
            //if(!(s->error_status_table[mb_xy]&MV_ERROR))
            //if (1)
            if(mb_x>0)
                mv_predictor[pred_count][0]= s->current_picture.f.motion_val[0][mot_index - mot_step][0];
                mv_predictor[pred_count][1]= s->current_picture.f.motion_val[0][mot_index - mot_step][1];
                ref         [pred_count]   = s->current_picture.f.ref_index[0][4*(mb_xy-1)];
                pred_count++;
            

            if(mb_x+1<mb_width)
                mv_predictor[pred_count][0]= s->current_picture.f.motion_val[0][mot_index + mot_step][0];
                mv_predictor[pred_count][1]= s->current_picture.f.motion_val[0][mot_index + mot_step][1];
                ref         [pred_count]   = s->current_picture.f.ref_index[0][4*(mb_xy+1)];
                pred_count++;
            

            if(mb_y>0)
                mv_predictor[pred_count][0]= s->current_picture.f.motion_val[0][mot_index - mot_stride*mot_step][0];
                mv_predictor[pred_count][1]= s->current_picture.f.motion_val[0][mot_index - mot_stride*mot_step][1];
                ref         [pred_count]   = s->current_picture.f.ref_index[0][4*(mb_xy-s->mb_stride)];
                pred_count++;
            

            if(mb_y+1<mb_height)
                mv_predictor[pred_count][0]= s->current_picture.f.motion_val[0][mot_index + mot_stride*mot_step][0];
                mv_predictor[pred_count][1]= s->current_picture.f.motion_val[0][mot_index + mot_stride*mot_step][1];
                ref         [pred_count]   = s->current_picture.f.ref_index[0][4*(mb_xy+s->mb_stride)];
                pred_count++;
            

            if(pred_count==0) continue;

            if(pred_count>=1)
                int sum_x=0, sum_y=0, sum_r=0;
                int k;

                for(k=0; k<pred_count; k++)
                    sum_x+= mv_predictor[k][0]; // Sum all the MVx from MVs avail. for EC
                    sum_y+= mv_predictor[k][1]; // Sum all the MVy from MVs avail. for EC
                    sum_r+= ref[k];
                    // if(k && ref[k] != ref[k-1])
                    // goto skip_mean_and_median;
                

                mv_predictor[pred_count][0] = sum_x/k;
                mv_predictor[pred_count][1] = sum_y/k;
                ref         [pred_count]    = sum_r/k;
            

            s->mv[0][0][0] = mv_predictor[pred_count][0];
            s->mv[0][0][1] = mv_predictor[pred_count][1];

            for(m=0; m<mot_step; m++)
                for(n=0; n<mot_step; n++)
                    s->current_picture.f.motion_val[0][mot_index + m + n * mot_stride][0] = s->mv[0][0][0];
                    s->current_picture.f.motion_val[0][mot_index + m + n * mot_stride][1] = s->mv[0][0][1];
                
            

            decode_mb(s, ref[pred_count]);

            //
        
    

对于如何正确处理此问题,我非常感谢一些帮助。

【问题讨论】:

【参考方案1】:

我已经很久没有在内部接触 FFMPEG 的代码了。

但是,鉴于我对 FFMPEG 内部恐怖事件的经验(您会明白我的意思),我宁愿给您一个简单实用的建议。

建议 #1 最好的可能性是,当每个块的运动矢量被识别时 - 您可以在 FFMPEG 编码器上下文(又名s)中创建自己的附加数组,该数组将存储所有这些。当您的算法运行时,它将从那里获取值。

建议 #2 我读的另一件事(我不确定我是否读对了)

mx 和我的值增加了 50

我认为 50 是一个非常的运动向量。并且通常,运动矢量编码的 F 值范围是先验限制的。如果您将事物更改 +/- 8(甚至 +/- 16)可能就可以了 - 但 +50 可能会如此之高,以至于最终结果可能正确编码事物。

我不太明白你的目标mean_mv()以及你期望从那里得到什么失败。请重新措辞一下。

【讨论】:

感谢您的评论。我会考虑你的第一个建议。至于你的第二个建议,它与我的发现有些对应。当我在h264_cavlc.c 中读取运动矢量值(mxmy)时,我得到了非常大的值,例如 500。这是非常不合理的,除非有一些缩放比例,例如您建议的 50 倍。否则,我不知道为什么运动向量的值那么大。 对于mean_mv(),考虑一个P帧。稍后将添加 B 帧支持。 P 帧包含具有运动矢量 (MV) 的宏块。让我们将这些 MV 分组到一个名为 a 的集合中。现在,mean_mv() 的工作是生成一个新的运动矢量集b,它将取代a 作为最终的 MV 集。 b 中的每个 MV 都是 a 中相应 MV 的修改版本。修改a 中的MV 的一种方法是获取a 中周围MV 的空间平均值并将结果放入b。通过这种方式,我可以研究空间均值和其他错误隐藏技术的有效性。 mean_mv() 真是一个绝妙的主意。这也可以帮助猜测全局运动矢量(MPEG4-v2 风格)。而且,鉴于您知道平均场景运动,您可以从那里开始所有预测,而不是 tan (0,0)。祝你好运ffmpeg

以上是关于在ffmpeg H.264解码器中修改运动向量的主要内容,如果未能解决你的问题,请参考以下文章

开启FFmpeg+libx264软解码的多线程特性

[ffmpeg] h.264解码所用的主要缓冲区介绍

使用 gstreamer 和 ffmpeg 进行 H.264 解码

来自 RTSP 流的 H.264 解码错误日志

自定义 h.264 流在某些硬件解码器中中断

来自RTSP流的H.264解码错误日志