在上一篇文章中提到过关于对于某些机型的安卓手机使用mediacodec时会出现绿条,需要解决绿条的方法或许有很多,同时在网上很多文章都是讲解在解码端来解决这个问题的,在解码端处理YUV数据来实现裁剪来将绿条去掉。

当然本文讲的方法是跟之前网络上不同的,本文的方法是从发送数据端的数据进行处理的,即直接处理源数据的分辨率来适配接收端的mediacodec分辨率,使其绿条部分被自动裁减掉了。核心思想如下:

(1)由于mediacodec对于某些机型而言,当解码1920*1080时,需要分辨率其实是1920*1088,因此我们如果再次发送1920*1080的数据给mediacodec进行解码时,然后再使用surface渲染时会底部数据出现绿条或者异常的情况。

(2)由于(1)的问题出现,因此可以考虑将原来1920*1080的图像转换为1920*1088的图像,但我发现如果转为这个分辨率时,我使用ffmpeg时会发现编码出来的凸显出现红线,这个应该是跟分辨率不是常规的分辨率有关。如下图所示:

android 视频去绿幕 手机视频绿幕怎么去掉_ide

 图1920*1088编码后的h264图像(出现红线)

(3)考虑到(2)出现的情况,就再次尝试,将1920*1080转换为1920*1100,此时发现使用mediacodec解码出来的数据就没有了红线和绿边,但是对于底部会少了一点点图像,这种效果就有点类似于从源数据进行裁剪的效果了。如下图所示,可以看到使用了1920*1100图像时底部会有一部分缺失:

android 视频去绿幕 手机视频绿幕怎么去掉_ide_02

图1920*1100使用mediacodec解码后显示的图像(底部有缺失)

android 视频去绿幕 手机视频绿幕怎么去掉_h.264_03

图1920*1080原来的图像

(1)接下来是给出采集发送端关键代码相关的部分:

H264编码器分辨率设置:

//针对1920*1080分辨率
        if (in_w == 1920)
        {
                if (in_w % 16 != 0)
                {
                        pCodecCtx->width = (in_w / 16 + 2) * 16;
                        printf("111111111pCodecCtx->width:%d\n", pCodecCtx->width);
                }
                else
                {
                        pCodecCtx->width = in_w;
                }

                if (in_h % 16 != 0)
                {
                    pCodecCtx->height = (in_h / 16 + 3) * 16;
                    printf("111111111pCodecCtx->height:%d\n", pCodecCtx->height);
                }
                    else
                {
                    pCodecCtx->height = in_h;
                }
        }

使用libyuv进行转码的关键代码:

if (pFrameTmpChangeStatus->width == 1920)
{
        //进行分辨率转换
        ret = libyuv::I420Scale(
                (const uint8_t*)pFrameTmpChangeStatus->data[0], pFrameTmpChangeStatus->linesize[0],
                (const uint8_t*)pFrameTmpChangeStatus->data[1], pFrameTmpChangeStatus->linesize[1],
                (const uint8_t*)pFrameTmpChangeStatus->data[2], pFrameTmpChangeStatus->linesize[2],
                1920, 1080,
                (uint8_t*)pFrame->data[0], pFrame->linesize[0],
                (uint8_t*)pFrame->data[1], pFrame->linesize[1],
                (uint8_t*)pFrame->data[2], pFrame->linesize[2],
                1920, 1100, libyuv::kFilterBox);
}

(2)关于解码端mediacodec设置的相关代码,针对1920*1080

int width = 1920;
 int height = 1080;
//设置渲染类型
                whlglSurfaceView.getWlRender().setRenderType(WhlRender.RENDER_MEDIACODEC);
                //whlglSurfaceView.getWlRender().setRenderType(WhlRender.RENDER_YUV);


                String mime = WHLVideoSupportUtil.findVideoCodecName(codecName);//寻找对应的mediacodec的名字
                mediaFormat = MediaFormat.createVideoFormat(mime, width, height);
                //mediaFormat = MediaFormat.createVideoFormat(mime, 1920, 900);

                mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, width*height);//设置最大输入解码器的数据大小
                mediaFormat.setByteBuffer("csd-0", ByteBuffer.wrap(csd_0));//设置sps信息
                mediaFormat.setByteBuffer("csd-1", ByteBuffer.wrap(csd_1));//设置pps信息
                MyLog.d(mediaFormat.toString());
                mediaCodec = MediaCodec.createDecoderByType(mime);

                info = new MediaCodec.BufferInfo();//创建info
                //mediaFormat = MediaFormat.createVideoFormat(mime, 1920, 1080);
                //mediaCodec.configure(mediaFormat, null, null, 0);//获取相应的解码后的数据,不直接使用surface进行渲染
                if(isFirstBindSurface != 1)
                {
                    mediaCodec.configure(mediaFormat, surface, null, 0);//直接进行渲染
                    isFirstBindSurface = 1;
                }
                //mediaCodec.configure(mediaFormat, null, null, 0);//获取相应的解码后的数据,不直接使用surface进行渲染
                mediaCodec.start();

补充说明,本文mediacodec的初始化是使用的是1920*1080的参数,同时本文目前还没调试好其他分辨率的情况,如果使用其他分辨率的话,本文提供一种方法:

(1)将其他分辨率先转换为1920*1100分辨率的

(2)送入到mediacodec进行解码,就可以消除绿边;

(3)当然考虑到效率的问题的话,可以再尝试自行调通其他分辨的参数来实现去除绿边。

最后,本文的方法是一种类似穷举的办法,虽然可行但通用性不是很强,将来如果想到更好的解决方法我也会更新分享出来,如果你们也有更好的方法希望也能留言给我,相互学习交流。