字符编码转换时发生内存越界引发的摄像头切换失败问题的排查

目录

1、问题说明

2、初步分析

3、字符串字符编码说明

4、进一步分析

5、为啥在日常测试时没有遇到切换摄像头失败的问题呢?

6、华为MateBook笔记本使用高通的CPU

7、最后


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章正在更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html开源组件及数据库技术(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_2276111.html       最近在项目中遇到了一个因字符串编码转换时发生内存越界引发摄像头切换失败的问题,今天就来分享这个问题的排查过程,以供借鉴或参考。字符编码转换时发生内存越界引发的摄像头切换失败问题的排查

1、问题说明

       最近在某个项目中,将软件部署到客户的环境中后,有台华为MateBook笔记本(后经证实这是华为新出的Pad平板电脑)在使用我们客户端音视频软件时发现切换笔记本前置摄像头时有问题。打开软件时,默认使用的是后置摄像头,客户在设置中将摄像头由后置摄像头切换到前置摄像头时:

发现没有反应,显示的还是后置摄像头图像,应该是摄像头切换失败了。

2、初步分析

       系统当前的摄像头列表的获取以及摄像头的切换,最终都是程序底层的音视频模块去执行的,所以优先从音视频模块查起。

这里的摄像头包括机器内置的摄像头以及USB摄像头(通过USB接口接到系统中的摄像头)。

1)台式机电脑一般使用的都是外接的USB摄像头;

2)笔记本电脑使用的是内置摄像头,也可以使用外界的USB摄像头(更清晰一点,角度可所以调整);

3)Pad平板一般会像手机一样,内置两个摄像头,一个前置摄像头,一个后置摄像头。

       经过客户允许,我们使用远程桌面软件远程连接到出问题的客户笔记本上。首先,查看音视频模块的打印日志,发现调用音视频模块切换摄像头时,上层传下来的摄像头id是空的:(日志打印是排查问题最重要、最常用的手段之一!

正常的摄像头id是类似于这样的字符串:\\?\usb#vid_046d&pid_0825&mi_00#6&259ee562&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\{bbefb6c7-2fc4-4139-bb8b-a58bba724083}

       音视频模块是通过上层传下来的设备id去匹配对应的摄像头设备,找到目标摄像头后,将摄像头源切换过去。根据打印,上层在调用音视频模块切换摄像头的接口时传入的是摄像头id为空,肯定匹配不到摄像头设备,所以没法切换到目标摄像头,摄像头切换失败了。

       客户端UI层模块,也会保存摄像头列表数据(包含摄像头名和id),UI层调用切换摄像头的接口时是从UI层的内存中去找对应的设备id,然后将要切换到的目标摄像头id传递给底层。难道UI层内存中保存的摄像头列表中的id有问题?id为空?于是去查看UI层的打印日志,通过日志得知,UI层保存的摄像头列表数据是正常的,id也不为空,是有效的!

3、字符串字符编码说明

       本案例中的问题与字符编码转换有关,所以此处给大家大概地介绍一下字符编码及相互转换的相关内容。

很多人在处理字符串编码及转换时可能会有不解或疑惑,甚至有多年工作经验的朋友也搞不清楚,所以这个地方很有必要给大家介绍一下字符编码相关的内容!

       有字符串的地方,就会涉及到字符编码,是对字符串的编码。常用的字符编码有ANSI窄字节编码UNICODE宽字节编码UTF8可变长度编码,其中UNICODE编码在Windows系统中普遍的使用。

        ANSI编码是本地语言编码,不是全球统一编码,不同国家语言文字的ANSI编码可能是相同的。中文字符的ANSI编码也被成为GBK编码(是对GB2312编码的扩展)。

       UNICODE编码则是全球统一的双字节编码(1个字符使用两个字节编码来表示),不同国家文字字符的UNICODE编码是全球唯一的,不重复的。在Windows平台上开发的软件,要支持多国语言,则软件中的字符要使用UNICODE编码。用Visual Studio开发的软件,如果要使用UNICODE编码,一般需要工程属性中需要字符集设置为“使用 Unicode 字符集”:(VS会在工程中自动添加UNICODE宏)

       涉及到字符串参数的系统API函数,一般都有两个版本,一个ANSI窄字节版本,一个UNICODE宽字节版本,比如获取窗口文字的API函数GetWindowText的声明如下:

int
WINAPI
GetWindowTextA(
    _In_ HWND hWnd,
    _Out_writes_(nMaxCount) LPSTR lpString,
    _In_ int nMaxCount);
_Ret_range_(0, nMaxCount)

WINUSERAPI
int
WINAPI
GetWindowTextW(
    _In_ HWND hWnd,
    _Out_writes_(nMaxCount) LPWSTR lpString,
    _In_ int nMaxCount);

#ifdef UNICODE
#define GetWindowText  GetWindowTextW
#else
#define GetWindowText  GetWindowTextA
#endif // !UNICODE

如果有定义UNICODE,则指向宽字节版本。如果在Visual Studio的字符集选项中选择“使用多字节字符集”,则对应ANSI窄字节编码。

       UTF8编码,是可变长字符编码,也是一种全球统一编码,之所以称之为可变长编码,因为不同类型的字符的编码长度是不同的,比如一个数字或英文字母的编码只占1个字节,一个中文文字的编码则占3个字节。

UTF8编码一般用于客户端和服务器之间交互的字符数据的统一编码格式,比如服务器中的Linux系统支持直接操作UTF8编码的字符数据,对于Windows客户端程序,在UI界面展现字符串数据时,需要先将服务器给过来的UTF8编码的字符转换成ANSI或UNICODE编码的字符,然后再将字符串拿到UI界面上显示。

       软件中可能会涉及到不同字符编码的转换,关于常用字符编码的详细说明以及编码之间转换的源码实现,可以参见我之前写的文章:

一文带你弄懂C++中的ANSI、Unicode和UTF8三种字符编码及相互转换https://blog.csdn.net/chenlycly/article/details/121070073VC++中ANSI、UNICODE与UTF-8字符编码之间的转换(附源码)https://blog.csdn.net/chenlycly/article/details/123589349

4、进一步分析

       这就奇怪了,UI层的摄像头列表数据没问题,音视频模块中的摄像头列表数据也没问题,为啥上层调用音视频模块的切换摄像头接口时传入的目标摄像有id为空呢?其实,在UI层与音视频模块之间还有个业务组件模块,难道是中间的业务组件模块的代码有问题?

       音视频模块库是业务组件层封装起来的,即音视频模块的接口是业务组件层调用的,于是到业务组件层中查看调用音视频模块的切换摄像头接口的函数,如下所示:

因为上层与组件层之间交互涉及到字符串的,都统一使用UTF8字符编码,而音视频模块切换摄像头的接口声明如下:

// 选择视频源
unsigned short SelectVideoSource(const TVideoCapParam  *ptVideoCapParam, const wchar_t *pszDevName = NULL, const wchar_t *pszDevGUID = NULL);

该函数的第二个参数pszDevName(摄像头名称)和第三个参数pszDevGUID(摄像头id)都是wchar_t类型宽字节字符,在Windows平台上就是上面讲的UNICODE。而上层传过来的摄像头名称和id字符串都是UTF8编码的,所以在调用上述SelectVideoSource之前,需要将UTF8编码的字符串转换成UNICODE编码的字符串,代码中确实调用了UTF8转换到UNICODE的接口Utf8Tolnicode

       看到这里突然想到,是不是摄像头id比较长,在进行字符编码转换时存放转换后的字符串的buffer长度不足,导致内存越界了?(我们经常与字符编码打交道,对这方面的问题比较敏感)所以导致底层音视频模块的切换摄像头接口中收到的id为空的问题?于是将之前在日志中看到的目标摄像头的id拷贝到notepad++中查看到字符串的长度:

全选拷贝进来的摄像头id字符串,然后再notepad++状态栏显示的字符个数,得知摄像头id字符串的字符个数为130个,这些字符是键盘上的数字、字母等字符的组合,在UTF8编码中保存的是ASCII码,只占用1个字节。

       如果将上述字符串转成UNICODE编码的字符串,在UNICODE编码中一个字符占两个字节,对于数字和字母,其编码值就是ACSII码,只需要一个字节就能存放了,但还是要固定的占两个字节(高位填充0)。所以,上述130个字符的字符串,转成UNICODE编码后需要130*2 = 260字节,如果要包含字符串\0结尾符,那就需要262字节了!而编码转换的代码中却使用了256字节的buffer去接收转换出来的UNICODE编码:

所以,在调用Utf8Tolnicode接口转换时肯定发生内存越界了!所以引发了音视频模块的切换摄像头的接口内部收到的id为空的问题。

按讲此处即使有越界,按讲传入的id不应该为空,因为在打印字符串时会直到找到字符串\0结尾符才会结束,此处就没再深究了,反正是存放转换出的UNICODE字符串的buffer长度不够导致的问题,修改后就正常了。

       解决办法很简单,直接将接收UNICODE字符串的buffer长度由256改成512就可以了。

       此外,在Windows平台上主要是调用API函数MultiByteToWideChar实现的,其实我们在调用MultiByteToWideChar接口时第五个参数(接收转码后的字符串buffer地址)传入NULL,MultiByteToWideChar返回的就是转码后字符串的长度,然后再调用一次MultiByteToWideChar进行转换,相关代码如下所示:

int nTmpLen = MultiByteToWideChar( CP_UTF8, 0, pchSrc, -1, NULL, 0 );
WCHAR* pWTemp = new WCHAR[nTmpLen+1];
memset( pWTemp, 0, (nTmpLen+1)*sizeof(WCHAR) );
MultiByteToWideChar( CP_UTF8, 0, pchSrc, -1, pWTemp, nTmpLen+1 );

5、为啥在日常测试时没有遇到切换摄像头失败的问题呢?

       遇到问题时,我们要多思考,要搞清楚问题的来龙去脉,甚至可以和正常的场景做比较。本案例中的问题以前从来没遇到过,这个到底有什么不一样的地方呢?我们的音视频软件需要使用到摄像头,不管是内置的摄像头,还是外插的USB摄像头。我们的软件已经有很多年了,为啥到现在才暴露出这个问题呢?

       于是到使用USB摄像头的测试电脑上,看看USB摄像头id和客户的摄像头id有什么不同。通过打印日志,拿到USB摄像头id的打印:(当前用的是外接的USB摄像头,id中包含usb字样)

\\?\usb#vid_046d&pid_0825&mi_00#6&259ee562&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\{bbefb6c7-2fc4-4139-bb8b-a58bba724083}

把这个id串拷贝到notepad++中:

选中所有字符,看到一共有127个字符,那么将这个字符串转成UNICODE编码,需要127*2 = 254字节(如果要包含\0结尾符,则需要256个字节),所以上述代码中给出256字节是够用的,没有问题。

       但在本案例中的客户笔记本电脑上,出问题的摄像头id为:(笔记本自带摄像头,是内置的,id中包含了display字样)

\\?\display#qcom_avstream_8180#3&1716c7e7&2&uid32768#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\{4faeafd4-041b-4e46-85fd-400473891182}

比较两个摄像头id字符串得知,id字符串的前半部分是不同的。

6、华为MateBook笔记本使用高通的CPU

       在我们的印象中,高通主要是做手机CPU芯片的,没想到华为的MateBook笔记本也使用高通的Snapdragon骁龙CPU,相关截图如下:

高通CPU是基于ARM架构的,Windows系统也是支持ARM架构的CPU的。

       笔记本电脑的CPU一般使用Intel或AMD的CPU,第一次见到用高通CPU的,很是好奇!于是以Snapdragon 8cx Gen 2型号到网上搜索这款CPU的说明,在CPUBenchmark测评工具的官网上看到了该CPU的说明:

这款CPU 8核8线程,支持平台有移动端、嵌入式和笔记本电脑(Laptop)。

       这款笔记本型号是MateBook E Go,后来到华为商城上搜了一下,这个是类似微软Surface的平板电脑,屏幕和键盘是可以分开的,本质上还属于Pad平板电脑,和一般意义上的笔记本还是有些不一样的。

7、最后

       这个问题其实相对比较简单,但其中涉及到的字符编码及编码转换的部分内容,正好趁此机会给大家普及一下,希望能给大家提供一定的借鉴或参考。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/123527.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

推荐大学生考研党都来使用的白板笔记软件!上岸卷王必备!

考研这条路,对于很多大学生来说,是一条漫漫长路。相信很多人都有这样的体会:看了大量的书籍,记了大量的笔记,但是到了临近考试的时候,却发现复习的内容和思路都不是很清晰,效率不高。 针对这个…

Nginx实现tcp代理并支持TLS加密实验

Nginx源码编译 关于nginx的搭建配置具体参考笔者之前的一篇文章:实时流媒体服务器搭建试验(nginxrtmp)_如何在线测试流媒体rtmp搭建成功了吗-CSDN博客中的前半部分;唯一变化的是编译参数(添加stream模块并添加其对应ss…

骨传导蓝牙耳机推荐,2023骨传导耳机选购攻略

相信大家佩戴入耳式耳机时间长后,都会出现耳朵痛的情况,这也是这类耳机的一个通病了,为了缓解这一问题,骨传导耳机出现了,并且凭借佩戴舒适,并且不会耳痛等优点迅速成为当下最受欢迎的耳机款式,…

Android 内存泄漏分析思路和案例剖析

分析思路 内存泄漏是指 Android 进程中,某些对象已经不再使用,但被一些生命周期更长的对象引用,导致其占用的内存资源无法被GC回收,内存占用不断增加的一种现象;内存泄漏是导致我们应用性能下降、卡顿的一种常见因素&…

鸿蒙开发工具的汉化

1、下载汉化包 汉化插件下载地址:Chinese (Simplified) Language Pack / 中文语言包 - IntelliJ IDEs Plugin | Marketplace 百度网盘下载地址:链接:百度网盘 请输入提取码 DevEco Studio是基于IDEA223版本,下载汉化包时请注意…

Hadoop原理,HDFS架构,MapReduce原理

Hadoop原理,HDFS架构,MapReduce原理 2022找工作是学历、能力和运气的超强结合体,遇到寒冬,大厂不招人,可能很多算法学生都得去找开发,测开 测开的话,你就得学数据库,sql&#xff0c…

【数据结构】二叉树的遍历递归算法详解

二叉树的遍历 💫二叉树的结点结构定义💫创建一个二叉树结点💫在主函数中手动创建一颗二叉树💫二叉树的前序遍历💫调用栈递归——实现前序遍历💫递归实现中序和后序遍历 💫二叉树的结点结构定义 …

在微信小程序中怎么做投票活动

在当今社交媒体时代,微信小程序已经成为一种广泛使用的互动营销工具。通过各种活动,企业可以吸引用户的关注,提升品牌影响力。其中,投票活动是一种特别受欢迎的形式。本文将为你详细介绍如何在微信小程序中创建投票活动。 一、微信…

Doc as Code (4):使用Git做版本管理,而不是使用目录做版本管理

▲ 搜索“大龙谈智能内容”关注GongZongHao▲ 在引入版本管理工具之前,文档工程师使用文件系统提供的功能来管理文件。大家是这样工作的: 文件按照分类放在不同的目录里,使用编辑器(如:MS Word)打开文档进…

如何评价现在的CSGO游戏搬砖市场

如何评价现在的csgo市场? 其实整个搬砖市场,现在已经变得乌烟瘴气,散发着“恶臭”。我个人非常鄙视那些虚有其表,大小通吃的做法,那些甚至连搬砖数据都看不懂的人,也出来吹嘘着“实力强大,经验丰…

sql学习

因为之前sql学的太烂了,想整理一下 一.什么是 SQL? SQL 是用于访问和处理数据库的标准的计算机语言。 SQL 指结构化查询语言SQL 使我们有能力访问数据库SQL 是一种 标准计算机语言 二.SQL 能做什么? SQL 面向数据库执行查询SQL 可从数据库…

vue的双向绑定的原理,和angular的对比

目录 前言 Vue的双向绑定用法 代码 Vue的双向绑定原理 Angular的双向绑定用法 代码 Angular的双向绑定原理 理解 效率: 虽然Vue和Angular的双向绑定原理不同,但它们都致力于提供高效的数据更新机制。但是,由于Vue使用的是数据劫持,其…

linux驱动之等待队列

阻塞和非阻塞 IO 是 Linux 驱动开发里面很常见的两种设备访问模式,在编写驱动的时候一定要考虑到阻塞和非阻塞。 一.阻塞和非阻塞 IO (1)阻塞访问 阻塞操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足…

048-第三代软件开发-数据回放

第三代软件开发-数据回放 文章目录 第三代软件开发-数据回放项目介绍数据回放 关键字: Qt、 Qml、 Data、 play back、 数据 项目介绍 欢迎来到我们的 QML & C 项目!这个项目结合了 QML(Qt Meta-Object Language)和 C 的…

2023.11.9 IDEA 配置 Lombok

目录 什么是 Lombok 如何使用 Lombok Lombok 的 Data 注解 什么是 Lombok Lombok 是一个 Java 库,能自动插入编译器并构建工具,简化 Java 开发它通过注解实现这一目的,可用来帮助开发人员消除 Java 的冗长代码,尤其是对于简单…

华为取消6000万订单影响在扩大,高通嘴硬强调不受影响

高通公布了2023年第三季度的业绩,业绩显示营收下滑24%,净利润下滑36%,不过高通强调预计今年四季度业绩将回升,意思是说华为取消订单带来的影响较小。 一、高通处境不利已延续4年时间 2019年美国对华为采取措施,众多中国…

go-sync-mutex

Sync ​ Go 语言作为一个原生支持用户态进程(Goroutine)的语言,当提到并发编程、多线程编程时,往往都离不开锁这一概念。锁是一种并发编程中的同步原语(Synchronization Primitives),它能保证多…

Django之三板斧的使用,全局配置文件介绍,request对象方法,pycharm链接数据库,Django链接数据库,ORM的增删改查

【1】三板斧(3个方法)的使用 Httpresponse() 括号内写什么字符串,返回的就是什么字符串返回的是字符串 render(request, 静态文件 ) request是固定的静态文件是写在templates文件夹里面的,如,HTML文件 redirect( 重定向的地址 ) 重…

利用三次样条插值调整鱼眼扭曲程度

本文利用三次样条插值算法,改变鱼眼扭曲程度。效果如下图所示: 源码下载地址:利用三次样条插值算法更改鱼眼特效的扭曲程度资源-CSDN文库 (说明:源码基于QT和opencv ) 主要代码 鱼眼扭曲 void fisheye(…

在Windows 10上安装单机版的hadoop-3.3.5

1、Hadoop是一个由Apache基金会所开发的分布式系统基础架构。用户可以不需要了解分布式底层细节的情况下,开发分布式程序。充分利用集群进行高速运算和存储。 2、下载Hadoop,我们在清华大学的镜像站下载 Index of /apache/hadoop/core/hadoop-3.3.6 (t…