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

news/2024/11/16 19:29:45/

最近在调试一个基于十年前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.ppmy.cn/news/19650.html

相关文章

Linux中如何给普通用户提权

引言&#xff1a; 北京时间2023/1/26/11:00 &#xff0c;看到这个日期&#xff0c;我第一时间想到的是还有十几天就要开学啦&#xff01;开学我是向往的&#xff0c;但是我并不怎么向往开学的考试&#xff0c;比如什么毛概和什么信息技术&#xff0c;可能是我深知自己在这些课…

day4 24. 两两交换链表中的节点 ● 19.删除链表的倒数第N个节点 ●160 链表相交 ● 142.环形链表II

两两交换链表中的节点 链表的指针&#xff0c;要仔细画图&#xff0c;搞清楚cur和cur的next在哪里 这个题还是有点绕的&#xff0c;两两一组交换&#xff0c;还有奇偶数要考虑 class Solution { public:ListNode* swapPairs(ListNode* head) {ListNode* dummyhead new ListNo…

pytorch深度学习基础(十一)——常用结构化CNN模型构建

结构化CNN模型构建与测试前言GoogLeNet结构Inception块模型构建resNet18模型结构残差块模型构建denseNet模型结构DenseBlocktransition_block模型构建结尾前言 在本专栏的上一篇博客中我们介绍了常用的线性模型&#xff0c;在本文中我们将介绍GoogleNet、resNet、denseNet这类…

【MySQL进阶】MySQL视图详解

序号系列文章6【MySQL基础】MySQL单表操作详解7【MySQL基础】运算符及相关函数详解8【MySQL基础】MySQL多表操作详解9【MySQL进阶】MySQL事务详解文章目录前言1&#xff0c;视图1.1&#xff0c;视图概述1.2&#xff0c;视图使用环境1.3&#xff0c;视图创建格式1.4&#xff0c;…

常见DEM数据汇总

常见DEM数据汇总ALOS-2 PALSAR DEM数据 [12.5m]Shuttle Radar Topography Mission (SRTM) [30m, 90m]Advanced Spaceborne Thermal Emission and Reflection Radiometer (ASTER) [30m]USGS 30 ARC-second Global Elevation Data, GTOPO30 [1km]火星轨道器激光高度计&#xff08…

恶意代码分析实战 16 Shellcode分析

16.1 Lab19-01 将程序载入IDA。 一堆ecx自增的操作。到200是正常的代码段。 shellcode的解码器也是从这里开始的&#xff0c;一开始的xor用于清空ecx&#xff0c;之后将18dh赋给cx&#xff0c;jmp来到loc_21f,而在下图可以看到loc_21调用sub_208,在call指令执行后&#xff0…

【数据结构】7.2 线性表的查找

文章目录7.2.1 顺序查找&#xff08;线性查找&#xff09;顺序查找算法设置监视哨的顺序查找顺序查找算法分析7.2.2 折半查找&#xff08;二分或对分查找&#xff09;折半查找算法折半查找性能分析 - 判定树7.2.3 分块查找分快查找算法分块查找算法分析及比较查找方法比较7.2.1…

【进击的算法】基础算法——怎么优雅地控制边界范围

学习范围 &#xff1a; ✔️数组 ✔️边界控制本文作者 &#xff1a; 蓝色学者i 边界控制的艺术前言一、为什么需要控制边界&#xff1f;二、怎么优雅地控制边界&#xff1f;三、令人抓狂的二分查找3.1 题目概述3.2解题思路3.3 解决方案方案一&#xff1a;边界都有效方案二&…