ALSA子系统(十八)------指纹解锁动画提示声卡顿问题解析

news2025/1/6 17:31:20

你好!这里是风筝的博客,

欢迎和我一起交流。


很久没写kernel相关的东西了,主要是来到手机厂之后,大部分还是在Android上,Kernel虽然也有涉及,但毕竟只是有所涉及,主要业务逻辑还是在HAL之上,kernel的修改除了项目bringup,之后基本甚少修改。

最近倒是碰到一个Kernel的问题,简单记录下~

【前提条件】【Prerequistes】手机录入指纹 声音与振动——触感与提示音开启指纹动画音, 指纹样式选择旋涡样式
【操作步骤】【Operation steps】播放QQ音乐,按power键息屏亮屏指纹解锁
【实际结果】【Actual results】指纹动画声音响起有卡顿声
【期望结果】【Expected results】不应该有卡顿声音
【对比的上个版本编号】【Number of previous version for comparison】低概率问题

以我多年工作经验来看,外放问题是最好解决的了,相对好处理些。
ivdump

查看AudioDspStreamManager.xx.TaskPlayback_ivdump.pcm文件,这是PA的VI反馈信号,可以反馈出喇叭的真实状态。从频谱上看,确实出现了卡顿,在解锁动画提示音响起的时候。

同样查看AudioDspStreamManager.xx.TaskPlayback_datain.pcm,发现确实Kernel里的数据就有问题了,但是查看streamout.pcm.xx.AudioALSAPlaybackHandlerDsp.flag8.xx.48000.8_24bit.2ch_20230411_092109.wav
是没有问题的,说明问题出现在了Kernel底层,没有出现在HAL。

因为提示音是短音,走的fast通路,buf又小,确实容易出现问题。

查看kernel log:

	<6>[83150.878119][T600541] mtk_dsp_check_exception() deep adsp underflow
	<6>[83150.926100][T516896] snd_audio_dsp snd_audio_dsp: mtk_dsp_start() deep just underflow
	<6>[83178.409440][T700541] mtk_dsp_check_exception() fast adsp underflow
	<6>[83178.419408][T712585] snd_audio_dsp snd_audio_dsp: mtk_dsp_start() fast just underflow

确实发现了异常,出现了underflow,这表明写数据慢了,没有数据可以播放,所以出现了underflow!
同步查看ADSP的log:

	 [83137.343]<A-22>[D]audio_dsp_hw_write_op(), HW_STATE_UNDERFLOW, return
	 [83157.758]<A-11>[D] enter_write_cond, underflow, written_size[2048] datacount[0] task_name[fast_playback]
	 [83157.758]<A-11>[W] write_data_loop() ADSP_DL_CONSUME_UNDERFLOW
	 [83166.654]<A-11>[D] enter_write_cond, underflow, written_size[2048] datacount[0] task_name[fast_playback]
	 [83166.654]<A-11>[W] write_data_loop() ADSP_DL_CONSUME_UNDERFLOW

确实同样可以看到underflow。

因为underflow是没有数据可播,属于写数据不及时,所以修改增大buffer size是没有用的。
所以我们只能另想它法!

这里简单描述下MTK的播放:
dsp

灵魂画手,嘿嘿,按照理解自己画了下图~

Hal往kernel写数据之后,会通过ipi核间通信,将数据送往DSP做处理,DSP上会跑我们用到的算法,处理完的数据就通过iis送到codec进行播放,kernel里面只负责管理对codec的control。

源码解析:
drivers/misc/mediatek/audio_ipi/common/adsp_ipi.c

static int __init audio_ipi_init(void)
{
        ipi_queue_init();

        audio_task_manager_init();
        audio_messenger_ipi_init();

        init_audio_ipi_dma();
#if IS_ENABLED(CONFIG_MTK_AUDIODSP_SUPPORT)
        adsp_register_notify(&audio_ctrl_notifier);
#endif
        for (task_id = 0; task_id < TASK_SCENE_SIZE; task_id++) {
                task_info = &g_audio_task_info[task_id];

                dsp_id = audio_get_dsp_id(task_id);

                task_info->dsp_id = dsp_id;
                task_info->is_dsp_support = is_audio_dsp_support(dsp_id);
                task_info->is_adsp = is_audio_use_adsp(dsp_id);
                task_info->is_scp = is_audio_use_scp(dsp_id);
                task_info->task_ctrl = get_audio_controller_task(dsp_id);
        }

        ret = misc_register(&audio_ipi_device);
}

drivers/misc/mediatek/audio_ipi/common/adsp_ipi_queue.c

void ipi_queue_init(void)
{
        for (dsp_id = 0; dsp_id < NUM_OPENDSP_TYPE; dsp_id++) {
                if (is_audio_dsp_support(dsp_id))
                        ipi_queue_init_by_dsp(dsp_id);
        }

#if IS_ENABLED(CONFIG_MTK_AUDIODSP_SUPPORT)
        hook_ipi_queue_send_msg_handler(dsp_send_msg_to_queue_wrap);
        hook_ipi_queue_recv_msg_hanlder(dsp_dispatch_ipi_hanlder_to_queue_wrap);
#endif
}

hook_ipi_queue_send_msg_handler和hook_ipi_queue_recv_msg_hanlder主要是给ipi_queue_send_msg_handler和ipi_queue_recv_msg_handler这两个函数指针赋值的,在给dsp发送msg和接受msg的时候会用,这里没啥好说的,主要是:ipi_queue_init_by_dsp,这里会针对DSP做初始化:

int ipi_queue_init_by_dsp(uint32_t dsp_id)
{
        for (dsp_path = 0; dsp_path < DSP_NUM_PATH; dsp_path++) {
                msg_queue = &g_dsp_msg_queue[dsp_id][dsp_path];
                ret = dsp_init_single_msg_queue(msg_queue, dsp_id, dsp_path);
                if (ret != 0)
                        WARN_ON(1);
        }
}

这里会循环两次:0是AP to DSP,1是DSP to AP。

static int dsp_init_single_msg_queue(
        struct dsp_msg_queue_t *msg_queue,
        const uint32_t dsp_id,
        const uint32_t dsp_path)
{
        if (dsp_path == DSP_PATH_A2D) {
                msg_queue->dsp_process_msg_func = dsp_send_msg_to_dsp;
        } else if (dsp_path == DSP_PATH_D2A) {
                msg_queue->dsp_process_msg_func = dsp_process_msg_from_dsp;
        } else
                WARN_ON(1);

        /* lunch thread */
        msg_queue->dsp_thread_task = kthread_create(
                                             dsp_process_msg_thread,
                                             msg_queue,
                                             "%s",
                                             thread_name);
        if (IS_ERR(msg_queue->dsp_thread_task)) {
                pr_info("can not create %s kthread", thread_name);
                WARN_ON(1);
                msg_queue->thread_enable = false;
        } else {
                msg_queue->thread_enable = true;
                dsb(SY);
                wake_up_process(msg_queue->dsp_thread_task);
        }
}

重点看下这里面就好了,针对不同的情况,AP to DSP 还是 DSP to AP,会赋值不同的处理函数到dsp_process_msg_func。然后会创建线程:dsp_process_msg_thread,并启动:wake_up_process

static int dsp_process_msg_thread(void *data)
{
        while (msg_queue->thread_enable && !kthread_should_stop()) {
                /* wait until element pushed */
                retval = dsp_get_queue_element(msg_queue, &p_dsp_msg, &idx_msg);

                p_element = &msg_queue->element[idx_msg];

                /* send to dsp */
                retval = msg_queue->dsp_process_msg_func(msg_queue, p_dsp_msg);

                /* notify element if need */
                spin_lock_irqsave(&p_element->element_lock, flags);
                if (p_element->wait_in_thread == true) {
                        p_element->send_retval = retval;
                        p_element->signal_arrival = true;
                        dsb(SY);
                        wake_up_interruptible(&p_element->element_wq);
                }
                spin_unlock_irqrestore(&p_element->element_lock, flags);

                /* pop message from queue */
                spin_lock_irqsave(&msg_queue->queue_lock, flags);
                dsp_pop_msg(msg_queue);
                spin_unlock_irqrestore(&msg_queue->queue_lock, flags);
        }
        return 0;
}

线程里面处理还是比较简单的,就是等待有数据到queue里,如果有就通过之前填充的dsp_process_msg_func钩子函数,发给DSP处理即可。

好,扯了这么一大堆,回归本题,如果出现了underflow,说明写数据不及时,写数据就是通过dsp_process_msg_thread这个线程里面写到DSP的,所以,我们只需要保证这个线程的正常调度运行即可。

如何保证呢?自然是提升线程优先级!

为了使得用户能有良好的用户体验,dsp_init_single_msg_queue这里在创建线程的时候,这里直接将线程加入RT线程,RT (Real Thread)实时线程。
kthread_create线程创建之后,通过这个API:

struct sched_param param = { .sched_priority = 3 };
sched_setscheduler_nocheck(msg_queue->dsp_thread_task, SCHED_FIFO, &param);

即可加入RT线程。

加入RT线程之后,压测问题场景,没有再出现underflow的情况,卡顿问题也没有再复现了,问题完美解决~

最开始的audio_ipi_init函数里,还有一些内容就不多阐述了,DSP这块资料比较少,还是比较难去了解的,非本文重点。
audio_ipi_init后面就是配置DMA和TASK这些,每个scene占用一个Task:

        /* scene for library */
        TASK_SCENE_PHONE_CALL           = 0,
        TASK_SCENE_VOICE_ULTRASOUND     = 1,
        TASK_SCENE_PLAYBACK_MP3         = 2,
        TASK_SCENE_RECORD               = 3,
        TASK_SCENE_VOIP                 = 4,
        TASK_SCENE_SPEAKER_PROTECTION   = 5,
        TASK_SCENE_VOW                  = 6,
        TASK_SCENE_PRIMARY              = 7,
        TASK_SCENE_DEEPBUFFER           = 8,
        TASK_SCENE_AUDPLAYBACK          = 9,
        TASK_SCENE_CAPTURE_UL1          = 10,
        TASK_SCENE_A2DP                 = 11,
        //......

OK,后面就不表了,以后再说吧~

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【c语言】详解 结构体的内存对齐补齐

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c语言系列专栏&#xff1a;c语言之路重点知识整合 &#x…

计算机网络的性能指标

1.计算机网络的性能指标 笔记来源&#xff1a;湖科大教书匠&#xff1a;计算机网络的性能指标 声明&#xff1a;该学习笔记来自湖科大教书匠&#xff0c;笔记仅做学习参考 1.1 速率 注意&#xff1a;数据量中1KB 2 10 B 2^{10}B 210B、数据率中1kb/s 1 0 3 10^{3} 103b/s 1.2…

Chatgpt如何引入新的知识?我们来看下ACL2023 预训练模型能否对新注入的知识进行推理这篇文章

一、概述 title&#xff1a;Can LMs Learn New Entities from Descriptions? Challenges in Propagating Injected Knowledge 论文地址&#xff1a;https://arxiv.org/abs/2305.01651 相关代码&#xff1a; EKP数据和代码&#xff1a;GitHub - yasumasaonoe/entity_knowle…

香橙派4和树莓派4B构建K8S集群实践之四:BuildKit与LNMP

目录 1. 说明 2. 开始前的准备工作 2.1 docker 验证用户信息设置 2.2 安装BuildKit 3. 安装步骤 3.1 申请一个pvc存储区 (wwwroot-pvc.yaml) 3.2 Nginx 3.3 php-fpm 3.3.1 构建并推送镜像 4. 遇到的问题 5. 相关命令 6. 参考 1. 说明 k8s带来的灵活性&#xff0c;使…

Linux系统之安装PDF阅读器

Linux系统之安装PDF阅读器 一、PDF介绍1. PDF简介2. PDF特点3. evince介绍 二、本次实践环境介绍1. 本地环境规划2. 本次实践介绍 三、本地环境检查1. 检查操作系统版本2. 查看系统内核版本 四、安装前准备工作1. 配置yum仓库2. 检查本地yum仓库状态3. 查看evince安装包 五、安…

怎样做好一场线上研讨会?

怎样做好一场线上研讨会&#xff1f; 1-策划和准备。在开始前&#xff0c;需要仔细策划和准备。确定研讨会的主题、目标、议程和参与者&#xff0c;并为参与者提供足够的信息和资源&#xff0c;以确保他们能够充分准备并参与讨论。 2-选择合适的在线平台。选择一个适合您需求…

Ubuntu搭建VPN服务,PPTD和OpenVPN

本文提供了两种vpn方式&#xff0c;pptd移动端支持不够&#xff0c;OpenVPN跨平台能力更前&#xff0c;且安全性更好。 但pptd也不是一无是处&#xff0c;在使用midjuriney网站时&#xff0c;openvpn搭建的网络出现了无法将机器人加入服务器的情况&#xff0c;更换pptd后操作无…

基于Maven创建多模块的Spring Boot项目

使用 Spring Boot的初始化器等创建Spring Boot项目时,需要在pom.xml指定该项目的父项目是 spring-boot-starter-parent。 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><versio…

OSPF综合实验(第二部分)

目录 配置OSPF动态路由协议 重发布 减少路由条目&#xff1a;​ 特殊区域&#xff1a; 收敛配置 认证 NAT地址转换 空接口 配置OSPF动态路由协议 AR1&#xff1a; [r1]ospf 1 router-id 1.1.1.1 [r1-ospf-1]area 1 [r1-ospf-1-area-0.0.0.1]network 172.16.32.0 0.0.0.…

DeepLab V3+

Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation 分割图像的语义编码器-解码器和带孔可分离卷积的技术 摘要 对于语义分割任务&#xff0c;深度神经网络通常使用空间金字塔池化模块或编码器-解码器结构。前者通过以多种速率和多种有效视…

手把手教你安装Redis

在上一篇《Redis简介》中介绍了Redis的一特性和使用场景&#xff0c;下面我们就来动手安装一下Redis 主要分为Windows安装、Linux下安装和Docker下安装 Windows下安装Redis 很不幸&#xff0c;Redis官方不支持windows版本&#xff0c;以下是官方原话 Redis is not officiall…

【P19】JMeter CSS/JQuery提取器(CSS Selector Extractor)

文章目录 一、准备工作二、测试计划设计 一、准备工作 百度&#xff1a; https://www.baidu.com/ 进入网页后&#xff0c;右键检查或按F12&#xff0c;打开调试工具 使用CSS/JQuery提取器&#xff0c;获取六个百度热搜文本 二、测试计划设计 &#xff08;1&#xff09;、测…

供水管线 码蹄集

题目来源&#xff1a;码蹄集 题目描述&#xff1a; 解决思路&#xff1a; 首先&#xff0c;题目要求我们去掉一些管道&#xff0c;使得总的管道管理费用最小。在去掉部分管道的情况下&#xff0c;城市之间不再形成一个回路&#xff0c;即城市之间构成了一棵树。因此&#xff…

元宇宙医疗虚拟人的功能,创造哪些新体验

虚拟数字人的出现对于精准医疗及人类健康的发展将带来不可估量的作用。通过三维可视化、3D打印、大数据及人工智能等多种数字化手段&#xff0c;在医学研究方面取得新的进展。 居家/健康&#xff1a;智能虚拟管家、家庭虚拟医生、家庭虚拟陪护员 一、虚拟数字人的医学知识普及优…

ChatGPT中文指令(Prompt)角色预设大全!让你的AI更懂你!

ChatGPT的回答总是不令人满意&#xff0c;那可能是你对AI下错了指令/提示词&#xff08;Prompt&#xff09;&#xff0c;想要ChatGPT更懂你&#xff0c;回答更精准&#xff0c;就要给它下对指令。 在国外有大佬们已经整理出一些标准的问话模板&#xff0c;直接拿来使用后&#…

在Python环境中安装配置GDAL,并演示使用GDAL读取shapefile文件

GDAL是应用广泛的空间数据处理库&#xff0c;可以处理几何、栅格数据&#xff0c;Python是一门简单易学的编程语言&#xff0c;常用来编写数据处理工具、脚本。本文讲解如何在Python环境中安装、配置、使用GDAL。本文示例中使用的GDAL版本为3.4.3 一、下载GDAL的whl包 可以通过…

ComPDFKit PDF SDK for Windows crack

ComPDFKit PDF SDK for Windows crack 增加了对新文本编辑功能的支持&#xff0c;如添加其他字体、设置粗体/斜体、复制文本样式和修改文本透明度。 增加了对新级别文档加密的支持&#xff0c;包括AES-128和AES-256。 ComPDFKit PDF SDK允许开发人员在Windows(iOS和Android平台…

C高级-day(4)-(shell数组、shell中的算数、shell中的分支语句.)

一、编写一个名为myfirstshell.sh的脚本&#xff0c;它包括以下内容。 1、包含一段注释&#xff0c;列出您的姓名、脚本的名称和编写这个脚本的目的 2、和当前用户说“hello 用户名” 3、显示您的机器名 hostname 4、显示上一级目录中的所有文件的列表 5、显示变量PATH和H…

C/C++中程序数据的分类与内存分布,C++内存管理方式之new / delete 操作符与malloc / free的区别

TIPS const修饰数据类型并不会影响它在内存当中某个区域的存储位置&#xff0c;比方说原先是在栈区上面的&#xff0c;然后用const修饰了一下&#xff0c;并不就是说你现在已经变到静态区里面去了&#xff0c;你还是在栈上面指针与指针之间的等号赋值也需要考虑到权限的放大与…

利用Python程序生成字符画 让男大学生们洗脑的挖呀挖呀挖

源码地址 原教程在这里 演示效果&#xff1a;&#xff08;有点虚&#xff09; 利用Python程序生成字符画 让男大学生们洗脑的挖呀挖呀挖 使用教程&#xff08;源码在文章最后&#xff09; 打开pyhton编译器安装opencv和Pillow库把要进行字符串化的视频命名为input.mp4&#…