十一假期写了一篇《WebRTC视频帧渲染前处理——等比例填充显示窗口》,介绍了按照显示窗口,不损失原视频帧内容的前提下,左右或上下补黑的方式来构造视频帧的方法。这篇文章再说一下另外一种处理方式,那就是按照显示窗口比例,将源视频帧进行裁剪,按照比例来获取其中一部分,放到窗口中显示的方法。这种方法适合任何矩形窗口比例(如1:1正方形、4:3、16:9、16:10或其他比例)。

根据显示窗口宽高比不同,与等比例填充一样,裁剪也有三种情况:
1. 宽高比几乎相同,不做任何处理
2. 源视频帧宽高比 > 显示窗口宽高比,执行源视频帧左右裁剪
3. 第2条的反向条件,执行源视频帧上下裁剪

第1种情况我们不需要做裁剪处理,直接pass就行了,OpenGL ES 会为我们完成渲染时的自动缩放拉伸以适合显示视图。针对第2、3种情况,我们以源视频帧中央位置为基准,来分别按照宽、高进行裁剪。

下图是裁剪的示意图:

依然是在ViERenderer::DeliverFrame()中进行这个处理。关键代码如下:

void ViERenderer::DeliverFrame(int id,I420VideoFrame* video_frame,int num_csrcs,const uint32_t CSRC[kRtpCsrcSize])
{//假设显示视图大小信息存在变量 rc 中int nViewWidth = rc.right - rc.left;int nViewHeight = rc.bottom - rc.top;double srcRatio = (double)video_frame->width() / (double)video_frame->height();double dstRatio = (double)nViewWidth / (double)nViewHeight;//判断视频宽高比和显示窗口宽高比的差if( fabs(srcRatio - dstRatio) <= 1e-6 ){//由于浮点数存在精度,当差值的绝对值小于10的-6次方的时候,将差值视为0//宽高比相同,不用做任何处理}else if( srcRatio > dstRatio ){//按照显示视图比例,以源视频帧中央为基准,计算合适的宽度,超过的部分丢弃不要,相当于进行左右裁剪//按照视图的显示比例,计算适合的宽度int srcWidth = (int)(video_frame->height * dstRatio);//除8乘8,修正宽值srcWidth = (srcWidth >> 3 << 3;//找到宽度中心int nMidWidth = (srcWidth + 1) / 2;//关键的变量:计算X方向偏移位置,后面拷贝YUV数据,从这个偏移位置开始拷贝int nOffset = (video_frame->width() - srcWidth) / 2;//修正以避免出现奇数if(nOffset % 2)nOffset += 1;//new_frame是一个临时帧,可以定义一个成员变量避免重复申请内存//tmp_buf的3个元素分别指向new_frame的Y,U,V buffer起始位置//src_buf的3个元素分别指向视频帧的Y,U,V buffer起始位置unsigned char *tmp_buf[3], *src_buf[3];//CreateEmptyFrame后面2个参数是宽度的1/2,函数内部会用这个值乘以高度的1/2,得到的就是U,V的实际大小,以此来分配空间new_frame.CreateEmptyFrame(srcWidth, video_frame->height(), srcWidth, nMidWidth, nMidWidth);//准备指针tmp_buf[0] = (unsigned char*)new_frame.buffer(kYPlane);tmp_buf[1] = (unsigned char*)new_frame.buffer(kUPlane);tmp_buf[2] = (unsigned char*)new_frame.buffer(kVPlane);src_buf[0] = (unsigned char*)video_frame->buffer(kYPlane);src_buf[1] = (unsigned char*)video_frame->buffer(kUPlane);src_buf[2] = (unsigned char*)video_frame->buffer(kVPlane);//注意hStep的退出条件:因为循环体内部每次都拷贝2行Y,因此处理次数就是高度的一半for(int hStep = 0; hStep < (video_frame->height()+1)/2; hStep++){//因为video_frame是4:2:0格式,4个Y点对应1个U和1个V,所以2行Y对应1/2行U及1/2行V//拷贝2行Ymemcpy(tmp_buf[0]+(hStep*2)*new_frame.stride(kYPlane), src_buf[0]+(hStep*2)*video_frame->stride(kYPlane)+nOffset, new_frame->width());memcpy(tmp_buf[0]+(hStep*2+1)*new_frame.stride(kYPlane), src_buf[0]+(hStep*2+1)*video_frame->stride(kYPlane)+nOffset, new_frame->width());//拷贝1/2行Umemcpy(tmp_buf[1]+hStep*new_frame.stride(kUPlane), src_buf[1]+hStep*video_frame->stride(kUPlane)+(nOffset>>1), (new_frame->width()+1)/2);//拷贝1/2行Vmemcpy(tmp_buf[2]+hStep*new_frame.stride(kVPlane), src_buf[2]+hStep*video_frame->stride(kVPlane)+(nOffset>>1), (new_frame->width()+1)/2);}//OK,YUV数据复制完毕,把其他内容补上new_frame.set_render_time_ms(video_frame->render_time_ms());new_frame.set_timestamp(video_frame->timestamp());//帧交换,现在video_frame里是新构造好的左右补黑的新视频帧了video_frame->SwapFrame(&new_frame);}else{//下面是上下裁剪的情况,思路和左右裁剪相同,只是计算Offset的地方有区别,其他一样,就不写详细注释了int srcHeight = (int)(video_frame->width() / dstRatio);int srcWidth = video_frame->width() >> 3 << 3;int nMidWidth = (srcWidth + 1) / 2;//与左右裁剪的区别在这个offset的计算int nOffset = (video_frame->height() - srcHeight) / 2;if(nOffset % 2)nOffset += 1;unsigned char *tmp_buf[3], *src_buf[3];new_frame.CreateEmptyFrame(srcWidth, srcHeight, srcWidth, nMidWidth, nMidWidth);tmp_buf[0] = (unsigned char*)new_frame.buffer(kYPlane);tmp_buf[1] = (unsigned char*)new_frame.buffer(kUPlane);tmp_buf[2] = (unsigned char*)new_frame.buffer(kVPlane);src_buf[0] = (unsigned char*)video_frame->buffer(kYPlane);src_buf[1] = (unsigned char*)video_frame->buffer(kUPlane);src_buf[2] = (unsigned char*)video_frame->buffer(kVPlane);for(int hStep = 0; hStep < (video_frame->height()+1)/2; hStep++){memcpy(tmp_buf[0]+(hStep*2)*new_frame.stride(kYPlane), src_buf[0]+(hStep*2+nOffset)*video_frame->stride(kYPlane), new_frame->width());memcpy(tmp_buf[0]+(hStep*2+1)*new_frame.stride(kYPlane), src_buf[0]+(hStep*2+1+nOffset)*video_frame->stride(kYPlane), new_frame->width());memcpy(tmp_buf[1]+hStep*new_frame.stride(kUPlane), src_buf[1]+(hStep+(nOffset>>1))*video_frame->stride(kUPlane), (new_frame->width()+1)/2);memcpy(tmp_buf[2]+hStep*new_frame.stride(kVPlane), src_buf[2]+(hStep+(nOffset>>1))*video_frame->stride(kVPlane), (new_frame->width()+1)/2);}new_frame.set_render_time_ms(video_frame->render_time_ms());new_frame.set_timestamp(video_frame->timestamp());video_frame->SwapFrame(&new_frame);}//OK,接下来就交给后续流程去渲染显示了render_callback_->RenderFrame(render_id_, *video_frame);
}

OK,让我们来实际跑一下看看效果。

等比例填充,视频帧裁剪,这两种基本上都可以满足正常的显示需求了。上面是基于早期webrtc的视频渲染框架中制作的,实际应用中,可以根据代码思路,运用到类似场景中。

WebRTC视频帧渲染前处理——视频帧裁剪相关推荐

  1. RTCPeerConnection基本概念 -- 以及创建和绑定音视频以及渲染远端视频时候的作用

    RTCPeerConnection 是WebRTC的核心的 是其暴露个用户的统一接口 其由多个模块组成 · 网络处理模块 · 服务质量模块 · 音视频引擎模块 等等 最最厉害的就是根据网络情况动态调整 ...

  2. MP4 格式:最少加载多少数据就能渲染出视频首帧?优化短视频播放体验必须先了解它丨音视频基础

    (本文基本逻辑:MP4 封装格式概览 → 重要 Box 具体信息介绍 → 实战中对 MP4 Box 信息的使用) MP4 也称为 MPEG-4 第 14 部分,是继承 MPEG-4 第 12 部分的 ...

  3. 微信小程序 RTMP 音视频 通话 ffmpeg_音视频常见问题分析和解决:HLS切片丢帧引起的视频卡顿问题排查...

    问题背景: 前两天看读者留言让再写写音视频问题排查方面的思路,前面大概写几篇:<音视频播放疑难杂症分析和解决 :序篇>.<音视频常见问题分析和解决:延时和抖动>.<记一次 ...

  4. AI视频插帧 附带『视频插帧』工具

    AI视频插帧 附带『视频插帧』工具 视频插帧工具来啦! 下载链接在最下面. 前言 继视频抠图工具以来,本人又考虑制作一款视频插帧的工具,最近一直在改各种问题(头都大了- _ -),还好该来的终于来了( ...

  5. android 视频处理60帧,如何导出60帧视频,让视频画面流畅无比

    原标题:如何导出60帧视频,让视频画面流畅无比 随着技术的发展,60帧视频被广泛应用,但由于一些剪辑软件不支持导出60帧视频,不能满足广大视频制作者的需求.如果想要导出60帧视频,让视频画面流畅无比, ...

  6. python3 opencv截取视频_录制的视频,使用python opencv去截取帧数(只取某一帧)同时可裁剪图像尺寸...

    coding: utf-8 指定某一帧截取图像(不包括裁剪) import cv2 as cv import os 1.读取视频文件夹 filepath = './Input_video' # 需要读 ...

  7. CVPR 2022 | 腾讯优图实验室30篇论文入选,含场景文本语义识别、3D人脸重建、目标检测、视频场景分割和视频插帧等领域...

    关注公众号,发现CV技术之美 本文转载自腾讯优图 近日,CVPR 2022官方公布了接收论文列表(CVPR 2022 接收论文公布! 总计2067篇!),来自腾讯优图实验室共计30篇论文被CVPR收录 ...

  8. 一种用于360度全景视频超分的单帧多帧联合网络

    一种用于360度全景视频超分的单帧多帧联合网络 论文.代码地址:在公众号「3D视觉工坊」,后台回复「全景视频超分」,即可直接下载. 摘要和简介 球形视频,也称360度(全景)视频,它的捕获.存储和传输 ...

  9. opencv-python将视频帧还原成视频

    最近在做一些分割的项目,拿视频来做分割测试,在做分割时是一帧一帧的读进去的,出来的结果也是离散的视频帧.所以就有这个需求: 原始帧序列: 从第一帧到最后一帧依次存起来的.从这些分割借过帧中还原视频,o ...

最新文章

  1. WinCE设置DataGrid行高
  2. IEnumerator,IEnumerable,IEnumerableT
  3. TypeScript里对数组元素的自定义属性排序的实现原理
  4. 周鸿祎:在360新员工入职培训上的讲话
  5. centos系统安装python3.8的操作过程 亲测可行 云服务器安装过程 200327
  6. Java后端学习体系(韩顺平)
  7. C语言-01基础语法
  8. springboot初始化逻辑_详解Spring Boot中初始化资源的几种方式
  9. redhat配置caffe多核训练
  10. MySQL函数大全及用法
  11. REST服务和RESTful API是什么
  12. linux安装gt620驱动下载,Debian6安装Nvidia GT 620显卡驱动
  13. 创建物理卷报错Can‘t open /dev/sdb1 exclusively. Mounted filesystem?以及对应的解决方法
  14. django 标签verbatim的使用 200312
  15. 【u-boot】uboot代码简要分析 (u-boot 移植)
  16. 物联网智能开关平台源码
  17. [转]全球付虚拟卡申请流程~
  18. 【源码】核磁共振成像的脑部肿瘤检测与分类
  19. 清理c盘垃圾怎么清理?清理软件如何选择?
  20. 记一次c#调matlab时初始化异常的解决过程

热门文章

  1. Python 给视频添加水印源代码
  2. 数据库课程设计(书店管理系统)3
  3. 吃一堑长一智!mysql格式化查询结果
  4. Python中文数字(包含小数)转阿拉伯数字参考函数
  5. ArbExpress Application软件如何从外部导入txt转换成tfw导入到任意波形信号发生器(AFG1062任意波形信号发生器)
  6. 大厂笔试助攻王御用伴读小书童-JAVA/C++/Python
  7. 电脑优化软件测试大乐,性能优化小结(转) - 乐呵呵测试天地 - 51Testing软件测试网 51Testing软件测试网-软件测试人的精神家园...
  8. 大神教你如何在 Linux 中启用 Shell 脚本的调试模式
  9. 关于在线提供产品3D CAD模型的九问九答?
  10. 阿里云服务(一)——OSS