Android音频播放有杂音?原来是这个JAVA API接口惹的祸

news2025/1/4 19:39:10

最近在调试一个基于十年前Android版本的多媒体应用软件时,遇到了音频播放的问题,这里记录问题的发现、分析和处理过程。

有人可能会好奇,十年前的Android版本是什么版本?大家可以去Google网站上查查,就是目前Android网站上可以看到的最老的Android版本:

没错,就是Android4.x。再之前比较经典和流行的Android版本是2.x系列。可能很多开发者都没有见过这些古董级的机型。

当时开发采用的技术是Java+Native的方式。Native层完成音视频的传输和编解码。Java层根据官方的API,完成音频采集和播放动作。Java和Native之间通过JNI完成接口的互调。开发IDE是Eclipse,AndroidSDK最高支持到24,NDK版本为r8e。就这样一个组合,开发完成的Android应用,最近在一个Android9设备上仍然可以运行,说明Android API的兼容性还是可以的。

软件基本可以运行,但是测试发现,播放音频的时候,有掺杂的比较规律的哒哒哒哒音。因为这个设备是定制的、且是户外使用,喇叭的声音比较大,就是说即使将设备音量调的比较低,播放出来的声音也很大,所以怀疑是不是因为声音较大,导致喇叭震动产生了哒哒哒哒的杂音。

验证这个猜疑很简单,找一个音频文件,拷贝到设备里,使用原生的播放器进行播放,听是否有上述杂音。经过实际测试,发现一切正常。这就说明问题来源于应用软件本身,而跟系统、喇叭和硬件等都没有关系。

如果问题出自于应用,那么可能的原因有两个方面:1是数据本身带有杂音;2是数据的播放节奏控制的不好。其实基本可以确定,问题应该主要在数据本身。如果是节奏的问题,整个音频播放听着其实是不够流畅的,而现在的问题更像是在数据中掺杂了异常音频数据。

如何确定问题产生的具体位置呢?最直接的办法是在可能的数据流程节点上将数据录制下来,单独播放,看是否存在问题。

先在Java层录制了通过JNI接口获取的音频播放数据,使用CoolEdit单独播放,发现存在杂音。这说明播放的数据本身就有问题。因为底层的音频流有多路,存在混音操作,怀疑是不是在这个点出现了问题。录制底层混音后的数据,单独播放测试,发现一切正常。那问题就出在混音后数据到JNI获取这一段。

在这条路径上,添加了多个录音点,测试发现都是好的。甚至在Native层录制被填充音频数据的缓冲区进行测试仍然是正常的。这个缓冲区由JNI传递过来。问题查到此时,就感觉有点奇怪了,难道是Natvie和Java层互传数据有问题?决定在最靠近JNI的上下两层进行数据的保存,然后比较看看数据差异在哪里。

使用Beyond的十六进制比较工具:

发现,有不少数据被修改了:

仔细检查数据,发现基本都是很多个位置的4个字节被改为了0x00 0x00 0x00 0x00。分析这些被修改数据的间隔,发现间距基本都是缓冲区的大小。也就是说,每一包数据的头或者尾的4个字节被修改了。离找到问题根源点越来越接近了。

那这里被修改的到底是头还是尾呢?在Java层将每一包的开头4个字节和结尾4个字节打印出来,跟录制的数据中被修改的点进行比对。

这里的数据看着有点乱。这也是中间踩的一个坑。Java中将字节打印出来,采用了类似下面的接口。开始四个字节还可以,最后四个字节,代码一执行,就崩溃。

" Firt 4 bytes data is " + Integer.toHexString(mBuffer.getChar(0)) + " " + Integer.toHexString(mBuffer.getChar(1)) + " " + Integer.toHexString(mBuffer.getChar(2)) + " " + Integer.toHexString(mBuffer.getChar(3))

看了下说明,是按两个字节来处理的,所以索引的最大值是limit - 2。(是本人落伍了。Java里的char是占用两个字节的。这些内容早还给老师了。C/C++“荼毒”太深!

通过比对打印的4个字节跟录制的差异数据,发现每一包的末尾4个字节会被替换为0x00 0x00 0x00 0x00。这是什么原因?难道是接口所用的Java API :java.nio.ByteBuffer有问题?

网上找到一个资料,说是ByteBuffer的array()方法会删除前4个字节的数据,建议使用get和put方法。决定按该方法来修改测试。也就是将下面的方式:

ByteBuffer mBuffer;

mBuffer.array()

换为:

byte[] bytesArray = new byte[mBufferSize];

mBuffer.get(bytesArray);

修改后,测试,噪音消失了。

再次录音,比较数据,发现JNI上下层数据也一致了。

如此,基本确定了array()方法是存在问题的。具体是为什么呢?API本身的实现问题还是版本差异的问题?决定再找找根本原因。

写个简单的程序测试,发现,确实开头四个字节会被清零。这也就意味着,连续的包,后四个字节会被清零,跟之前的现象就可以匹配上了。

mBuffer = ByteBuffer.allocateDirect(128);

for (byte i=0; i<100; i++) {

        mBuffer.put(i);

}

for (char i=0; i<100; i++) {

        Log.i(TAG, "Index " + Integer.toHexString(i) + " byte data is " + Integer.toHexString( (mBuffer.get(i)) ) );

}

final byte[] buffer = mBuffer.array();

for (char i=0; i<100; i++) {

        Log.i(TAG, "Index " + Integer.toHexString(i) + " byte data is " + Integer.toHexString(buffer[i]));

}

为啥会将开始的4个字节清零呢?或者说为啥有4个字节的偏移?

网上搜索到了相关的一篇博文,质量比较高,基本上说清楚问题了。大家可以参考:

https://blog.csdn.net/lanlangaogao/article/details/120342954

后来,我又验证了下,实际分配的长度确实是加了7

如此,如果每次分配的地址在0、4、8对齐的位置的话,offset就是0、4、0。那出现4字节的偏移导致丢失4子节尾巴的数据,就不奇怪了。

这个问题暂时就追到这里吧。对于Java入门级水平来讲,这个坑踩得不冤。学语言还是要学精通。另外,就是要有追求完美的心境,不放过细节。只要功夫深,问题终可解!

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

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

相关文章

Android深入系统完全讲解(40)

调试 C 代码 15.1 改成 C 写法 这个没啥必要&#xff0c;但是对 C 比 C 情谊深的我&#xff0c;把它修改了。下面是修改的一部分代码&#xff0c; 把 C 的写法&#xff0c;改成 C 的&#xff0c;同时修改引入头文件。 jstring Java_hellojni_codegg_com_hellojni_MainActivity_…

Java基础41 面向对象(高级)

面向对象&#xff08;高级&#xff09;一、类变量和类方法1.1、static &#xff08;类变量&#xff09;1.1.1 关于static的存放位置1.1.2 类变量使用细节及注意事项1.2、类方法1.2.1、类方法使用细节及注意事项二、main方法2.1、深入理解main方法三、代码块3.1、代码块的基本介…

19.6、Javaweb_案例旅游路线收藏功能

旅游线路收藏功能 分析 判断当前登录用户是否收藏过该线路 当页面加载完成后&#xff0c;发送ajax请求&#xff0c;获取用户是否收藏的标记 根据标记&#xff0c;展示不同的按钮样式 编写代码 后台代码 RouteServlet&#xff1a; package cn.itcast.travel.web.servlet;…

【Typescript学习】使用 React 和 TypeScript 构建web应用(四)useReducer、扑街了的分区功能【完结了】

教程来自freecodeCamp&#xff1a;【英字】使用 React 和 TypeScript 构建应用程序 跟做&#xff0c;仅记录用 其他资料&#xff1a;https://www.freecodecamp.org/chinese/news/learn-typescript-beginners-guide/ 作者提供的源码https://github.com/piyush-eon/react-typescr…

机器学习【西瓜书/南瓜书】--- 第四章决策树

一、决策树理论分析 1.1 通俗理解 决策树是一种非常经典的机器学习算法&#xff0c;通俗理解的话我们可以举一个例子&#xff0c;比如现在别人要找你借钱&#xff0c;那么按照首先是不是要判断你和他的关系如何?如果关系不好&#xff0c;我就直接拒绝他。如果关系很好&#…

Python机器学习:一元回归

→\rightarrow→回归效果评价 &#x1f315; 一元回归 一元回归主要研究一个自变量和一个因变量之间的关系&#xff0c;而这个自变量和因变量之间的关系又可分为线性回归和非线性回归。 ⭐️ 一元线性回归分析两个变量之间的线性关系&#xff0c;如ykxbykxbykxb中xxx和yyy就是…

深度学习笔记:神经网络的学习(1)

机器学习的核心在于从数据中提取规律和特征&#xff0c;并用于分类或预测。对于识别手写数字&#xff0c;如果人工设计一个识别算法逻辑是十分困难的。一种方法是任务在数据中提取更重要的特征量&#xff0c;然后利用机器学习算法如SVM或KNN。而神经网络的方法则是完全由机器自…

ISIS的3级别(level-1、level-2、level-1-2)4大类(IIH、LSP、CSNP、PSNP)9小类与邻接关系建立LSP交互过程介绍

2.2.0 ISIS 4种报文类型IIH、LSP、CSNP、PSNP、邻居建立过程、交互LSP过程 ISIS的3级别4大类9小类 ISIS拥有3种级别的路由器&#xff0c;分别是level-1、level-2、level-1-2。 不同级别之间进行交互的报文也是有所区别的&#xff0c;常规的ISIS报文分有4大类&#xff1a;IIH、…

cubeIDE开发, stm32人工智能开发应用实践(Cube.AI).篇一

一、cube.AI简介及cubeIDE集成 1.1 cube.AI介绍 cube.AI准确来说是STM32Cube.AI&#xff0c;它是ST公司的打造的STM32Cube生态体系的扩展包X-CUBE-AI&#xff0c;专用于帮助开发者实现人工智能开发。确切地说&#xff0c;是将基于各种人工智能开发框架训练出来的算法模型&#…

Vue3商店后台管理系统设计文稿篇(六)

记录使用vscode构建Vue3商店后台管理系统&#xff0c;这是第六篇&#xff0c;从这一篇章开始&#xff0c;所有的预备工作结束&#xff0c;正式进入商店后台管理系统的开发 文章目录一、创建后台管理系统的标题栏二、安装Icon 图标三、创建Menu菜单正文内容&#xff1a; 一、创…

PowerShell 学习笔记:操作JSON文件

JSON文件&#xff08;字符串&#xff09;是有一定格式要求的文本文件。百度百科JSON&#xff08;JavaScriptObject Notation, JS对象简谱&#xff09;是一种轻量级的数据交换格式。它基于 ECMAScript&#xff08;European Computer Manufacturers Association, 欧洲计算机协会制…

初识Linux常见指令汇总

文章目录前言1.对文件或目录的常用指令1.查看当前路径下的文件或目录相关信息2.进入指定路径3.创建删除文件或者目录4.使用nano简单编辑文件查看文件属性5.复制移动重命名文件或目录6.输入输出重定(查看文件内容)向和搜索查找1.输入输出重定向2.搜索查找7.打包压缩文件2.时间相…

如何使用Maven构建Java项目?Maven的使用详细解读

文章目录1. 前言2. Maven 快速入门2.1 Maven 项目模型2.2 Maven 仓库3. Maven的安装配置3.1 安装3.2 配置环境变量3.4 Maven 配置4. Maven 的常用命令4.1 编译4.2 清理4.3 打包4.4 测试4.5 安装5. Maven生命周期6. 总结Java编程基础教程系列&#xff1a;1. 前言 在 Java 开发中…

C++初阶:list类

文章目录1 list介绍2 list的模拟实现2.1 类的定义2.2 默认成员函数2.2.1 构造函数2.2.2 析构函数2.2.3 拷贝构造2.2.4 赋值重载2.3 迭代器2.3.1 正向迭代器2.3.2 反向迭代器2.4 修改接口2.4.1 任意位置插入2.4.2 任意位置删除2.5 其他接口2.5.1 尾插2.5.2 头插2.5.3 尾删2.5.3 …

3.7-2动态规划--图像压缩(举例子和写代码)

3.7动态规划--图像压缩_昵称什么的不存在的博客-CSDN博客 问题描述&#xff08;再写一遍&#xff09; 这篇文章是接着上面这一篇写的&#xff0c;就是写一个例子方便理解&#xff0c;模拟填写数组的过程 l: l[i]存放第i段长度, 表中各项均为8位长&#xff0c;限制了相同位数的…

CGAL 点云精配准之ICP算法

文章目录 一、简介二、相关参数三、实现过程三、举个栗子四、实现效果参考资料一、简介 ICP算法总共分为6个阶段,如下图所示: (1)挑选发生重叠的点云子集,这一步如果原始点云数据量比较巨大,一般会对原始点云进行下采样操作。 (2)匹配特征点。通常是距离最近的两个点,…

如何批量增加视频的音量(ffmpeg)

问题背景 由于之前爷爷的唱戏机充不进去电&#xff0c;过年时给爷爷买了个新的。但这个新买的机子&#xff0c;它的曲目&#xff08;视频&#xff09;在U盘里&#xff0c;声音普遍较低&#xff0c;我爷爷的耳朵不好&#xff0c;声音需要比正常的声音调大一些。 在Videolouder这…

【数据结构和算法】认识线性表中的链表,并实现单向链表

本文接着上文&#xff0c;上文我们认识了线性表的概念&#xff0c;并实现了静态、动态顺序表。接下来我们认识一个新概念链表。并实现单向链表的各种操作。顺序表还有不明白的看这一篇文章 (13条消息) 【数据结构和算法】实现线性表中的静态、动态顺序表_小王学代码的博客-CSDN…

leetcode--链表

链表1.链表的基本操作&#xff08;1&#xff09;反转链表(206)&#xff08;2&#xff09; 合并两个有序链表(21)&#xff08;3&#xff09;两两交换链表中的节点(24)2.其它链表技巧&#xff08;1&#xff09;相交链表(160)&#xff08;2&#xff09;回文链表(234)3.练习&#x…

力扣 2293. 极大极小游戏

题目 给你一个下标从 0 开始的整数数组 nums &#xff0c;其长度是 2 的幂。 对 nums 执行下述算法&#xff1a; 设 n 等于 nums 的长度&#xff0c;如果 n 1 &#xff0c;终止 算法过程。否则&#xff0c;创建 一个新的整数数组 newNums &#xff0c;新数组长度为 n / 2 &…