播放器实战22 解决花屏与卡顿问题

2023-11-06

1.内存对齐

1.1什么是内存对齐
在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。

为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的”对齐”. 比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除.

举个例子,理论上,32位系统下,int占4byte,char占一个byte,那么将它们放到一个结构体中应该占4+1=5byte;但是实际上,通过运行程序得到的结果是8 byte,这就是内存对齐所导致的。

#include<stdio.h>
struct{
    int x;
    char y;
}s;

int main()
{
    printf("%d\n",sizeof(s);  // 输出8
    return 0;
}

1.2为什么要内存对齐
尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的.它一般会以双字节,四字节,8字节,16字节甚至32字节为单位来存取内存,我们将上述这些存取单位称为内存存取粒度.

现在考虑4字节存取粒度的处理器取int类型变量(32位系统),该处理器只能从地址为4的倍数的内存开始读取数据。

假如没有内存对齐机制,数据可以任意存放,现在一个int变量存放在从地址1开始的联系四个字节地址中,该处理器去取数据时,要先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址),最后留下的两块数据合并放入寄存器.这需要做很多工作.

现在有了内存对齐的,int类型数据只能存放在按照对齐规则的内存中,比如说0地址开始的内存(存取粒度的整数倍)。那么现在该处理器在取数据时一次性就能将数据读出来了,而且不需要做额外的操作,提高了效率,是通过是空间来节省时间的例子。

若降低存取粒度,看上去不需要做内存对齐了,但读取速度大大减慢反而得不尝试,因此通过内存对齐技术使得存取粒度尽量大进而加快读取速度;其次对于嵌入式设备来说,可以通过使用内存对齐来充分利用其宝贵的内存资源。

在这里插入图片描述
参考:https://zhuanlan.zhihu.com/p/30007037

2.YUV与linesize

引入stride
stride:指在内存中每行像素所占的空间。为了实现内存对齐每行像素在内存中所占的空间并不一定是像素的宽度,可能要在每行末尾加一些字节的数据来做内存对齐

查看ffmpeg结构体AVFrame中的三个成员:
01.uint8_t *data[AV_NUM_DATA_POINTERS];
data存储原始的音视频数据(视频为YUV,音频为PCM)。有两种存储音视频的方式,plannar方式和packet方式
plannar方式:通道n的数据分别存储在data[n]中;拿YUV视频来说,就是data[0],data[1],data[2]分别存储Y,U,V的数据。拿双声道的音频来说,就是data[0],data[1]分别存储左声道,右声道数据;对于音频,声道数有可能大于AV_NUM_DATA_POINTERS,那么多出来的将存储在extended_data字段中
packet方式:所有数据都存储在data[0]中(RGB)

02.int linesize[AV_NUM_DATA_POINTERS];
表示每一行数据的大小;对于plannar(内存对齐1)格式和packet(内存对齐后通道数)格式,这里的取值也不一样

03.uint8_t **extended_data;
存放data存放不下的数据

比如分辨率638480的RGB24图像,我们在内存处理的时候如果要以16字节对齐,则6383/16=119.625不能整除,因此不能16字节对齐,我们需要在每行尾部填充6个字节,就是640*3/16=120.此时该图片的stride为1920字节,如下图所示:

在这里插入图片描述
什么是YUV
YUV是编译true-color颜色空间(color space)的种类,Y’UV, YUV, YCbCr,YPbPr等专有名词都可以称为YUV,彼此有重叠。“Y”表示明亮度(Luminance或Luma),也就是灰阶值,“U”和“V”表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。 [1]
planar 平面格式
指先连续存储所有像素点的 Y 分量,然后存储 U 分量,最后是 V 分量。
packed 打包模式
指每个像素点的 Y、U、V 分量是连续交替存储的。
视频编码为什么要采用YUV格式数据?
之所以采用YUV,是因为它的亮度信号Y和色度信号U、V是分离的。如果只有Y信号分量而没有U、V分量,那么这样表示的图像就是黑白灰度图像。彩色电视采用YUV空间正是为了用亮度信号Y解决彩色电视机与黑白电视机的兼容问题,使黑白电视机也能接收彩色电视信号。与RGB视频信号传输相比,YUV最大的优点在于只需要占用极少的频宽(RGB要求三个独立的视频信号同时传输)。其中“Y”表示明亮度(Luminance或Luma),也称灰阶值;“U”和“V”表示的则是色度(Chrominance或Chroma),它们的作用是描述影像的色彩及饱和度,用于指定像素的颜色。因此如果只有Y数据,那么表示的图像就是黑白的。在编码时使用YUV格式能极大去除冗余信息,因为人眼对亮点信息的敏感度远高于色度敏感度,如果压缩UV数据,人眼对其感知较弱,所以压缩算法的第一步,往往先把RGB数据转换成YUV数据,对Y压缩一点,对UV多压缩一点,以平衡图像效果和压缩率。
比如一个RGB格式的像素点R,G,B各为1比特,一共3比特,而使用YUV,(比如YUV422),每两个像素点共用1个U与V,一共2比特,节省了三分之一的存储空间
链接:https://www.zhihu.com/question/538985600/answer/2540194144

ffmpeg编码或者解码为什么非要yuv而不是rgb

h264的帧格式就是YUV, YUV的优点是可以对其中两个分量CbCr进行采样而不太破坏图像的显示, rgb就不行会导致图像严重失真, 所以设计h264的编码器的时候就考虑用YUV做帧格式。
如果你非要rgb,那么用sws_scale转换为RGB.

H.264算法,算法要对每个亮度块和色度块进行计算,并根据相邻块查找相关性.
https://www.csdn.net/tags/NtTacg3sMTUxOTgtYmxvZwO0O0OO0O0O.html

h264的帧格式就是YUV, YUV的优点是可以对其中两个分量CbCr进行采样而不太破坏图像的显示, rgb就不行会导致图像严重失真, 所以设计h264的编码器的时候就考虑用YUV做帧格式。
如果你非要rgb,那么用sws_scale转换为RGB.

H.264算法,算法要对每个亮度块和色度块进行计算,并根据相邻块查找相关性.

指每个像素点的 Y、U、V 分量是连续交替存储的。

YUV 4:4:4采样,每一个Y对应一组UV分量,一个YUV占8+8+8 = 24bits 3个字节。
YUV 4:2:2采样,每两个Y共用一组UV分量,一个YUV占8+4+4 = 16bits 2个字节。
YUV 4:2:0采样,每四个Y共用一组UV分量,一个YUV占8+2+2 = 12bits 1.5个字节。

在这里插入图片描述在存储时按照plannar格式存储,先存Y,再存U,最后存V在显示时,亮度的紫框(Y0,Y1,Y4,Y5)与色度的紫框(U0,V0)组合,Y:U:V=4:1:1

4:2:0采样有YUV420P和YUV420SP两种格式
yuv420的两种格式:I420,NV12
YUV420P(YU12和YV12)格式
YUV420P又叫plane平面模式,Y , U , V分别在不同平面,也就是有三个平面,它是YUV标准格式4:2:0,主要分为:YU12和YV12

YU12:
在这里插入图片描述
YV12
在这里插入图片描述
YUV420SP格式的图像阵列,首先是所有Y值,然后是UV或者VU交替存储,NV12和NV21属于YUV420SP格式,是一种two-plane模式,即Y和UV分为两个plane,但是UV(CbCr)为交错存储,而不是分为三个平面。

NV21
android手机从摄像头采集的预览数据一般都是NV21,存储顺序是先存Y,再VU交替存储,NV21存储顺序是先存Y值,再VU交替存储:YYYYVUVUVU,以 4 X 4 图片为例子,占用内存为 4 X 4 X 3 / 2 = 24 个字节
在这里插入图片描述
NV12
也属于YUV420SP格式,NV12存储顺序是先存Y值,再UV交替存储:YYYYUVUVUV,以 4 X 4 图片为例子,占用内存为 4 X 4 X 3 / 2 = 24 个字节
在这里插入图片描述

当轮廓不变,颜色显示错位,可能就是UV排序错位了(采用了NV12)
在这里插入图片描述
来源:https://www.bilibili.com/video/BV1kL4y1G74m?from=search&seid=2376940568801521762&spm_id_from=333.337.0.0
在这里插入图片描述
在这里插入图片描述

3.花屏与解决

播放一个.MP4文件出现如下画面:
在这里插入图片描述
通过断点可以查看AVFrame中的linesize:
在这里插入图片描述
由于采用的是yuv420p plannar格式,linesize[0]=1024存放的是Y,应该和width相等,但可以看到width为1000
在这里插入图片描述
因为为了字节对齐,假设为32字节对齐(它是根据cpu来对齐的,可能是16或32的整数倍,不同的cpu有不同的对齐方式),1000/32=31.25,因此每行补充了24个字节的无效数据补充到1024,1024/32=32,frame中的data[0]数据存放是这样的:解码器将码流解码出来后的数据保存在内存中时每存1000个真正的像素数据就补24个为了字节对齐的无效数据,此时通过以下方法进行拷贝时:

	memcpy(datas[0], frame->data[0], width * height);
	memcpy(datas[1], frame->data[1], width * height / 4);
	memcpy(datas[2], frame->data[2], width * height / 4);

将frame中的data[0]前1000个字节的数据传给显卡来显示第一行数据时没有问题,给显卡传第二行用来显示的数据时会从第1001个字节开始,此时会将24个无效字节当做第二行数据的起点,然后拼接上第二行的前976字节的数据,往后的行将会导致连锁反应的错乱,从而导致花屏

解决措施如下:

	for (int i = 0; i < height; i++)//Y
		memcpy(datas[0] + width * i, frame->data[0] + frame->linesize[0]* i, width);
	for (int i = 0; i < height / 2; i++)//U
		memcpy(datas[1] + width / 2 * i, frame->data[1] + frame->linesize[1] * i, width);
	for (int i = 0; i < height / 2; i++)//V
		memcpy(datas[2] + width / 2 * i, frame->data[2] + frame->linesize[2] * i, width);

以Y为例,因为Y的数据在一行中,每次在frame中读linesize的大小,每次只取linesize大小中width放在data中,循环height次,即可解决花屏:
在这里插入图片描述
虽然显示没问题了,但是在运行时仍然会有bug:
在这里插入图片描述
因为材质的内存空间在分配时:

	datas[0] = new unsigned char[width * height];		//Y
	datas[1] = new unsigned char[width * height / 4];	//U
	datas[2] = new unsigned char[width * height / 4];	//V

data[1]与data[2]的空间大小为堆上new出来的wh/4空间
但是在上面从frame中赋值时,datas[1]/datas[2]共赋了w
h/4+(linsize-width)*height/2,对于该媒体文件linsize>width,因此会导致有大量的数据被放在了未被定义的空间,也就是内存越界,具体的说是会导致堆上写内存越界,堆上开辟的内存空间的首尾空间是用来该堆的空间的,因此最后多出来的width/2个数据将会给写到这个存放内存管理的代码上,当要清理这块内存调用delete时,由于delete属于对堆上内存的管理代码,因此delete的调用可能会出现问题,
在这里插入图片描述

而堆中正确的代码应该如下:
内存越界一定会导致程序崩溃吗?详解内存越界

	for (int i = 0; i < height; i++)//Y
		memcpy(datas[0] + width * i, frame->data[0] + frame->linesize[0]* i, width);
	for (int i = 0; i < height / 2; i++)//U
		memcpy(datas[1] + width / 2 * i, frame->data[1] + frame->linesize[1] * i, width);
	for (int i = 0; i < height / 2; i++)//V
		memcpy(datas[2] + width / 2 * i, frame->data[2] + frame->linesize[2] * i, width);

小插曲:不小心写错了frame->data[]的下标:

	for (int i = 0; i < height; i++)//Y
		memcpy(datas[0] + width * i, frame->data[0] + frame->linesize[0]* i, width);
	for (int i = 0; i < height / 2; i++)//U
		memcpy(datas[1] + width / 2 * i, frame->data[0] + frame->linesize[1] * i, width);
	for (int i = 0; i < height / 2; i++)//V
		memcpy(datas[2] + width / 2 * i, frame->data[0] + frame->linesize[2] * i, width);

可以看到,能看到一些形状,但还是有花屏幕,并且颜色偏绿:
在这里插入图片描述
有待具体分析ing

4.解决卡顿问题

在Qt 5的GUI程序中,主线程也叫GUI线程,因为它是唯一被允许执行GUI相关操作的线程。对于一些计算量比较大的非常耗时的操作,如果放在主线程中,就是出现界面无法响应的问题。这种问题的解决一种方式是,把这些耗时操作放到次线程中,还有一种比较简单的方法:在处理耗时操作中加入一个延时,并调用QCoreApplication::processEvents()。这个函数告诉Qt去处理那些还没有被处理的各类核心事件,然后再把控制权返还给调用者。QElapsedTimer的成员函数elapsed()函数会返回自上次启动QElapsedTimer以来的毫秒数。如下代码就是加入一个25毫秒的延时去处理核心事件。

QT中线程qthread运行导致卡顿解决办法:

要把无限循环的函数放到run函数里面,同时 msleep 几毫秒。

在解封装的run中的无限循环每次循环完解锁之后msleep几(3)毫秒(在视频线程run的无限循环中也可以同样操作,但效果没有在解封装中做好)

void xdemuxthread::run()
{
	while (!isexit)
	{
		mux.lock();
		if (!demux)
		{
			mux.unlock();//若demux未打开则解锁让其他线程进来,等待5毫秒盼望demux能打开,然后再进行下次判断,判断demux是否打开
			msleep(5);
			continue;
		}
		if (vt && at)
		{
			vt->synpts = at->pts;
		}
		AVPacket* pkt = demux->readfz();
	
        if (!pkt)
		{
			//读到结尾了
			mux.unlock();
			msleep(5);
			continue;
		}
		if (demux->isvideo(pkt))
		{
			if (vt)vt->push(pkt);
		}
		else
		{
			if (at)at->push(pkt);
		}
		mux.unlock();
		msleep(3);
	}
}

即可解决问题
推测原因:
解封装线程需要获取mux,解码线程也需要这个mux,当解封装线程先获得这把锁后每次while循环完了由于CPU很难刚好调度到解码线程,于是解封装线程又立马获得了这把锁,导致解码线程很难获得这把锁,导致解码出的frame很少,于是会播放卡顿。
补充:为什么这两个线程要同一把锁?
比如当重新打开一个多媒体文件时,会调用视频音频线程的open(),open中第一步就是对缓冲队列进行清理,若不是同一把锁,解封装线程可能在清理的途中又把上一个多媒体文件中解封装出来的packet压进缓冲队列,这些packet有可能不会被清理掉,在播放新多媒体文件时就会出现问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

播放器实战22 解决花屏与卡顿问题 的相关文章

  • 大学概率论与数理统计知识点详细整理

    目录 概率论学习自述 概率论的一些基本概念 随机变量的分布 一维随机变量的分布 二维随机变量 抽样分布 数学期望 矩 方差 协方差 常见分布的数学期望与方差 一些重要的定理公式 参数估计 1 点估计 2 区间估计 假设检验 独立性 概率论学
  • 蒙皮流程1

    选中要调整权重的点 打开这个窗口 可以调整他的权重值 蒙皮里面的导出导入权重贴图可以在要对模型做修改的情况下 对已弄好的权重进行保留 或者直接用下面的替换几何体用新的替换旧的 给人物下巴绘制权重时 下巴骨骼与躯干骨骼连接处插入一个小骨骼 给
  • Unity ScrollView左右拖拽翻页

    ScrollView来实现左右拖拽的翻页 类似于微信 左右拖拽时候上下无法拖拽 上下拖拽的时候左右无法拖拽 并且左右拖拽的是时候 会有弹力进行对对齐 using System Collections using System Collect
  • C++这么难,为什么我们还要学习C++?

    文章目录 前言 1 为什么难学 2 C 的意义 3 什么时候该用C 4 如何学习C 5 学前勉言 前言 C 可算是一种声名在外的编程语言了 这个名声有好有坏 从好的方面讲 C 性能非常好 哪个编程语言性能好的话 总忍不住要跟 C 来单挑一下
  • Linux下WiFi驱动开发——WiFi基础知识解析(转)

    详见 https blog csdn net zqixiao 09 article details 51103615
  • SQL Server 命令行管理工具:SqlLocalDB.exe

    SqlLocalDB exe 是一个简单的工具 它使用户能够从命令行轻松管理 LocalDB 实例 它作为 LocalDB 实例 API 的简单包装实现 与在很多类似的 SQL Server 工具 例如 SQLCMD 中一样 参数作为命令行
  • flask框架实现文件下载功能

    传入文件名即可下载文件 from flask import Flask send file Response send from directory app Flask name app route download def downloa
  • Python编程题

    把数组 0 1 1 0 1 1 0 1 1 1 0 0 中所有的1排到左侧 0排到右侧 方法1 思路 1 首先进行可以保证0在左侧 1在右侧 2 新建一个空列表 3 把原列表中的值从最后1个复制给新建列表 直到第一个元素被复制完 list1
  • Qt 画图,void A::paintEvent(QPaintEvent *event){..}这函数怎么调用它?

    不用调用 需要用这个函数的时候调用A gt update 就可以得到调用这个函数的目的

随机推荐

  • shell中单引号、双引号、反引号的用法及区别

    单引号 这个比较暴力 不管单引号里面有什么都原样输出 无视一切变量 所见即所得 如果要用来做字符比较和输出 注意不能输出变量 也不认识通配符 命令等 even ubuntu echo a PATH aa a PATH aa 双引号 双引号感
  • Leetcode刷题总结-3.二叉树篇

    Leetcode刷题总结 二叉树刷题心得 总结 文章目录 Leetcode刷题总结 前言 一 二叉树刷题思路 二 美团面试题 2 1 第十套卷面试题 2 2 第九套卷面试题 三 华为研发工程师编程题 四 华为2016研发工程师编程题 前言
  • 【华为OD机试真题2023B卷 JAVA&JS】太阳能板最大面积

    华为OD2023 B卷 机试题库全覆盖 刷题指南点这里 太阳能板最大面积 知识点分治 时间限制 1s 空间限制 32MB 限定语言 不限 题目描述 给航天器一侧加装长方形或正方形的太阳能板 图中的红色斜线区域 需要先安装两个支柱 图中的黑色
  • 【项目总结】基于SpringBoot+Ansj分词+正倒排索引的Java文档搜索引擎项目总结

    文章目录 项目介绍 开发背景 主要用到的技术点 前端 后端 Ansj分词 实现索引模块 实现Parser类 实现Index类 完善Parser类 优化制作索引速度 实现搜索模块 实现DocSearcher类 处理暂停词 项目编写过程中遇到的
  • Hadoop系统入门之Join在MapReduce中的实现

    MapReduce Interview 描述如何使用MapReduce来实现join的功能 考察点 1 MapReduce执行流程 2 JOIN的底层执行过程 3 JOIN的多种实现方式 ReduceJoin shuffle MapJoin
  • 怎样将计算机32位换为62位,电脑32位怎么换62位

    大家好 我是时间财富网智能客服时间君 上述问题将由我为大家进行解答 电脑32位换62位的方法如下 1 备份原来的操作系统中的文件 特别注意收藏夹 我的文档 以及桌面等位置的文件 2 准备好64位操作系统的安装光盘或者ISO映像 制作操作系统
  • 关于Boolean==Boolean和Boolean=Boolean的启示

    昨晚做java面试题 一道原题是这样的 来自 JAVA最最最 基本的面试题 慕课手记 http www imooc com article 13754 6 给出以下代码 请问该程序的运行结果是什么 class Example public
  • C++中将csv文件中的数据存储到数组中

    ifstream fin filename c str 以输入方式打开文件存到缓冲空间fin中 string line int i 0 int comma 0 while getline fin line 读取fin中的整行字符存在line
  • 责任链中的嵌套使用

    1 按照之前的业务 用户建档后 挂号 生成挂号费用 根据挂号费 根据用户的身份生成不同的金额 儿童或者特殊人群挂号免费 2 具体实现 我们需要增加几个符合过滤器 public class CompositeFreeFilter extend
  • Winform ListView控件用法

    用法一 1直接从工具箱里把ListView拖到控件上 2可在窗体的Load 事件里 写如下代码 设置一些常用属性 listView1 View View Details listView1 LabelEdit true listView1
  • javascript全量匹配屏蔽词

  • iptables官方手册整理

    iptables官方手册整理 目录 1 简介 2 首先 什么是包过滤 3 快速入门指南 4 数据包过滤流程 5 具体如何使用Iptables命令实现过滤功能 6 地址转换 NAT 7 排除建议 1 简介 读者们 大家好 在这里我们假设你已经
  • 安装golang项目的 GVM

    GITHUB地址 https github com moovweb gvm bash lt lt curl s S L https raw githubusercontent com moovweb gvm master binscript
  • keras fine-tune方法

    https blog csdn net jdzwanghao article details 80697104
  • lzma算法分析

    lzma算法分析 这几天在公司主要在做压缩相关 记录一下所得 目前业界主流的压缩算法感觉并不多 好用的就Huffman lz系列 其他的像差分编码 vlq编码 感觉只能做个数据预处理 或者一种小范围的压缩 lz系列有很多 主要有lz77 l
  • 禁用系统【快应用】,停止【快应用】自动弹出

    快应用 九大厂商同时宣布建立即时应用生态发展联盟 通过统一标准让开发者低成本接入 快应用 在研发接口 场景接入 服务能力和接入方式上建设标准平台 以平台化的生态模式对个人开发者和企业开发者全品类开放 此次九大厂商共建 快应用 标准和平台 最
  • C语言:操作符以及部分表达式介绍

    目录 1 操作符 1 1 算数操作符 1 2 移位操作符 1 3 位操作符 1 4 1 赋值操作符 1 4 2 复合赋值符 1 5 单目操作符 1 6 关系操作符 1 7 逻辑操作符 1 8 条件操作符 1 9 逗号操作符 1 10 下标引
  • udp发包结合tkinter

    import socket import tkinter from tkinter import filedialog from tkinter filedialog import def action 获取输入框内容 date entry
  • 深度学习双显卡配置_更新深度学习装备:双(1080Ti)显卡装机实录

    前言 之前一直在装有一张1080Ti的服务器上跑代码 但是当数据量超过10W 图像数据集 的时候 训练时就稍微有点吃力了 速度慢是一方面 关键显存存在瓶颈 导致每次训练的batch size不敢调的过高 batch size与训练结果存在一
  • 播放器实战22 解决花屏与卡顿问题

    1 内存对齐 1 1什么是内存对齐 在C语言中 结构是一种复合数据类型 其构成元素既可以是基本数据类型 如int long float等 的变量 也可以是一些复合数据类型 如数组 结构 联合等 的数据单元 在结构中 编译器为结构的每个成员按