WebRTC视频帧渲染前处理——等比例填充显示窗口
来源:互联网 发布:四旋翼飞控编程 编辑:程序博客网 时间:2024/06/07 06:30
在早期的WebRTC版本中,视频帧在渲染前会经过ViERenderer::DeliverFrame()这个函数(源码位于vie_renderer.cc),我们可以在这里对传递过来的视频帧数据进行调整。比如,一般我们采集到的视频帧大小为640x480(4:3)的话,但显示视图大小是一个16:9甚至一个没有固定宽高比的尺寸,那么我们就面临该如何显示的问题。当然,如何显示,这跟不同产品的需求有关。
这里我介绍两种常见的显示模式:
- 等比例填充
- 等比例裁剪
这篇文章先介绍第1种:等比例填充。先解释一下什么叫做等比例填充:就是在不改变原有视频帧比例(如4:3)的情况下,将视频帧所有内容显示在给定视图区域中。
具体有三种情况:显示区域宽高比 (1.等于 2.大于 3. 小于)视频帧宽高比。等于就不说了,不用做任何处理。当发生显示区域宽高比大于或者小于视频帧宽高比时,我们就要进行多余部分留黑边(这里的黑边不一定是RGB颜色值0,也可以是0-255中的任何一个值)。
用一张图来表示如下:
下面来看一下两种情况的实现方法。这里说明一下,处理方法和思路有很多种,本文只是提供其中一种思路,可能不是最优方法。我相信有很多人有更多巧妙、高效的处理方法,欢迎提供。
下面代码中,省略了一些对本文所述问题无关的部分。
void ViERenderer::DeliverFrame(int id, I420VideoFrame* video_frame, int num_csrcs, const uint32_t CSRC[kRtpCsrcSize]){ //如果什么都不做,WebRTC默认的处理方法是: //不理会视频帧宽高比例,直接拉伸至充满整个显示视图 //假设显示视图大小信息存在变量 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); //修正宽值: 因为OpenGL渲染要求宽度是4的整数倍。这里按8的整数倍来计算。+7是避免取8整数倍时得到的是左侧值(如 8 16 24,接近24的话,不能取16) srcWidth = (srcWidth + 7) >> 3 << 3; //找到宽度中心 int nMidWidth = (srcWidth + 1) / 2; //计算视频帧应该显示的左偏移位置(以下为居中显示),可以根据显示要求修正这个值 int nOffset = (srcWidth - video_frame->width()) / 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); memset(tmp_buf[0], 0x00, new_frame.allocated_size(kYPlane)); //0x00 未被视频数据覆盖的部分就是黑色,如果需要其他颜色,可以修改这个默认值 memset(tmp_buf[1], 0x80, new_frame.allocated_size(kUPlane)); //0x80 = 128,灰度图里U,V都是0x80 memset(tmp_buf[2], 0x80, new_frame.allocated_size(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++) { //说明:nOffset >> 1 : 相当于 nOffset/2(奇数时不进位) //因为video_frame是4:2:0格式,4个Y点对应1个U和1个V,所以2行Y对应1/2行U及1/2行V //拷贝2行Y memcpy(tmp_buf[0]+(hStep*2)*new_frame.stride(kYPlane)+nOffset, src_buf[0]+(hStep*2)*video_frame->stride(kYPlane), video_frame->width()); memcpy(tmp_buf[0]+(hStep*2+1)*new_frame.stride(kYPlane)+nOffset, src_buf[0]+(hStep*2+1)*video_frame->stride(kYPlane), video_frame->width()); //拷贝1/2行U memcpy(tmp_buf[1]+hStep*new_frame.stride(kUPlane)+(nOffset>>1), src_buf[1]+hStep*video_frame->stride(kUPlane), (video_frame->width()+1)/2); //拷贝1/2行V memcpy(tmp_buf[2]+hStep*new_frame.stride(kVPlane)+(nOffset>>1), src_buf[2]+hStep*video_frame->stride(kVPlane), (video_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 { //上下补黑,实现思路与左右补黑相同,下面代码就不写详细注释了 int srcHeight = (int)(video_frame->width() / dstRatio); int srcWidth = (video_frame->width() + 7) >> 3 << 3; int nMidWidth = (srcWidth + 1) / 2; int nOffset = (srcHeight - video_frame->height()) / 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); memset(tmp_buf[0], 0x00, new_frame.allocated_size(kYPlane)); memset(tmp_buf[1], 0x80, new_frame.allocated_size(kUPlane)); memset(tmp_buf[2], 0x80, new_frame.allocated_size(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+nOffset)*new_frame.stride(kYPlane), src_buf[0]+(hStep*2)*video_frame->stride(kYPlane), video_frame->width()); memcpy(tmp_buf[0]+(hStep*2+1+nOffset)*new_frame.stride(kYPlane), src_buf[0]+(hStep*2+1)*video_frame->stride(kYPlane), video_frame->width()); memcpy(tmp_buf[1]+(hStep+(nOffset>>1))*new_frame.stride(kUPlane), src_buf[1]+hStep*video_frame->stride(kUPlane), (video_frame->width()+1)/2); memcpy(tmp_buf[2]+(hStep+(nOffset>>1))*new_frame.stride(kVPlane), src_buf[2]+hStep*video_frame->stride(kVPlane), (video_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应该是在m50甚至更早时候就逐渐将ViE开头的部分逐渐拿掉了,所以如果你手上的WebRTC找不到vie_renderer.cc这个文件以及ViERenderer、I420VideoFrame这些类,一点都不奇怪。
2017-10-2 于北京·家中
- WebRTC视频帧渲染前处理——等比例填充显示窗口
- WebRTC视频帧渲染前处理——视频帧裁剪
- IOS UIImageView等比例填充显示
- 处理vue渲染前的显示问题
- 视频等比例缩放
- iOS下 WebRTC 视频渲染
- iOS下 WebRTC 视频渲染
- WebRTC 之视频捕获——浏览器显示
- WebRTC视频流渲染中插入图片帧
- android图片等比例缩放 填充屏幕
- android图片等比例缩放 填充屏幕
- javafx窗口等比例缩放
- JS等比例显示图片
- 显示页面等比例缩放
- 视频渲染(显示)技术
- WebRTC视频分析:处理流程
- CSS——实现图片等比例正方形显示,宫格排列
- webrtc视频引擎之video_render(视频渲染)介绍
- Qt与MSVC中文乱码问题的解决方案
- 三、JS【JavaScript弱类型的脚本语言】03
- LeetCode:Find Largest Value in Each Tree Row
- python shelve模块的用法
- hdu 4498 自适应simpson
- WebRTC视频帧渲染前处理——等比例填充显示窗口
- 滴滴出行之地下迷宫
- 移动大脑-SpringMVc搭建RestFul后台服务(一)-环境搭建
- 第四章 对象与类
- 学习记录7(17/10/02 于隆昌)
- HTML 注释 水平线
- GDB调试器&Make工程管理器
- kk《必然》后感之'知化'
- java web 使用fliter定义权限拦截(例如:提交订单,我的订单 没有登录前不允许访问)