WebRTC视频帧渲染前处理——视频帧裁剪
来源:互联网 发布:mac怎么分盘储存文件 编辑:程序博客网 时间:2024/06/06 17:44
十一假期写了一篇《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行Y memcpy(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行U memcpy(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行V memcpy(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的视频渲染框架中制作的,实际应用中,可以根据代码思路,运用到类似场景中。
阅读全文
0 0
- WebRTC视频帧渲染前处理——视频帧裁剪
- WebRTC视频帧渲染前处理——等比例填充显示窗口
- WebRTC视频流渲染中插入图片帧
- iOS下 WebRTC 视频渲染
- iOS下 WebRTC 视频渲染
- WebRTC视频分析:处理流程
- webrtc视频引擎之video_render(视频渲染)介绍
- FaModel前处理视频
- WebRTC源码分析三:视频处理流程
- WebRTC源码分析三:视频处理流程
- WebRTC源码分析三:视频处理流程
- WebRTC源码分析三:视频处理流程
- webrtc研究-视频接收端处理
- WebRTC源码分析三:视频处理流程
- webrtc研究-视频接收端处理
- WebRTC源码分析三:视频处理流程
- 视频裁剪,长度裁剪
- IOS视频编辑,视频裁剪,视频拼接,音频处理,视频处理
- SSH实战式学习day1_1_常识性知识
- cvc-complex-type.2.4.a: Invalid content was found starting with element
- 人工智能在中国的崛起:中国将再次领导世界?
- Intellij 中的git操作
- [C# 网络编程系列] 专题二:HTTP协议详解
- WebRTC视频帧渲染前处理——视频帧裁剪
- leetcode1.Two Sum
- 算法第2篇——插入排序
- PullToRefreshListView原理解析(二)
- 字符串的连接,以及字符串大小写的转换
- getWidth和getMeasureWidth区别
- java Collections.sort()排序。List排序
- 用PL/SQL Developer创建Oracle触发器以及触发器的一点点知识与出现的问题
- Handlebars一些