当前位置: 首页 > 知识库问答 >
问题:

使用Android的AudioTrack组合字节的声音样本会产生噪音

徐文斌
2023-03-14

我正在构建一个相当简单的Android应用程序(sdk修订版14:ICS),它允许用户一次选择两个音频片段(都是RIFF/WAV格式、little endian、签名PCM-16位编码),并以各种方式组合它们以创建新的声音。我对这种组合使用的最基本方法如下:

//...sound samples are read in to memory as raw byte arrays elsewhere
//...offset is currently set to 45 so as to skip the 44 byte header of basic
//RIFF/WAV files
...
//Actual combination method
public byte[] makeChimeraAll(int offset){
    for(int i=offset;i<bigData.length;i++){
        if(i < littleData.length){
            bigData[i] = (byte) (bigData[i] + littleData[i]);
        }
        else{
            //leave bigData alone
        }
    } 
    return bigData;
}

然后可以通过AudioTrack类播放返回的字节数组:

....
hMain.setBigData(hMain.getAudioTransmutation().getBigData()); //set the shared bigData
// to the bigData in AudioTransmutation object
hMain.getAudioProc().playWavFromByteArray(hMain.getBigData(), 22050 + (22050*
(freqSeekSB.getProgress()/100)), 1024); //a SeekBar allows the user to adjust the freq
//ranging from 22050 hz to 44100 hz
....
public void playWavFromByteArray(byte[] audio,int sampleRate, int bufferSize){
    int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, 
            AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);
        AudioTrack at = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, 
            AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT,
            minBufferSize, AudioTrack.MODE_STREAM);

        int i = 0;

        at.play();
        at.write(audio, 0, audio.length);     
        at.stop();
        at.release();

       for(i=0;i<audio.length;i++){
           Log.d("me","the byte value at audio index " + i + " is " + audio[i]);
       }

}

使用上面代码的组合和回放的结果接近我想要的(两个样本在产生的杂交声音中仍然可以辨别),但是也有很多裂缝、爆裂声和其他噪音。

因此,有三个问题:首先,我使用AudioTrack的方式正确吗?其次,在AudioTrack配置中,endianness占了什么位置?声音本身播放得很好,听起来几乎和我组合时的预期一样,所以RIFF/WAV格式的小端特性似乎在某个地方得到了传达,但我不确定在哪里。最后,对于有符号的16位PCM编码,我应该期望看到的字节值范围是多少?我希望在上面的Log. d(...)调用中的logcat中看到的值从−32768到32767不等,但结果往往在-100到100之间(还有一些异常值)。超过16位范围的组合字节值可能是噪声的原因吗?

谢谢CCJ

更新:主要感谢比约恩·罗奇和编码者威廉!现在我将音频数据读入short[]结构,使用William的EndianInputStream解释DataInputStream的endianness(http://stackoverflow.com/questions/8028094/java-datainputstream-replacement-for-endianness)组合方式改为:

//Audio Chimera methods!
public short[] makeChimeraAll(int offset){
    //bigData and littleData are each short arrays, populated elsewhere
    int intBucket = 0;
    for(int i=offset;i<bigData.length;i++){
        if(i < littleData.length){
            intBucket = bigData[i] + littleData[i];
            if(intBucket > SIGNED_SHORT_MAX){
                intBucket = SIGNED_SHORT_MAX;
            }
            else if (intBucket < SIGNED_SHORT_MIN){
                intBucket = SIGNED_SHORT_MIN;
            }
            bigData[i] = (short) intBucket;
        }
        else{
            //leave bigData alone
        }
    } 
    return bigData;
}

这些改进带来的混合音频输出质量太棒了!

共有1个答案

羊舌炯
2023-03-14

我不熟悉android的音频,所以我不能回答你所有的问题,但是我可以告诉你根本的问题是什么:逐个字节地添加音频数据是行不通的。因为它可以工作,而且通过查看你的代码,以及它最常见的事实,我假设你有16位的PCM数据。然而,在任何地方,你都在处理字节。字节不适合处理音频(除非音频碰巧是8位的)

字节数约为ox/-128。你说“我希望看到从−日志中的32768到32767。d(…)调用,但结果往往在-100到100的范围内(有些异常值超出了这个范围)“那么,当你从字节数组打印值时,怎么可能会进入这个范围呢?16位有符号数据的正确数据类型是短的,而不是字节。如果你打印的是短值,你会看到你期望的范围。

必须将字节转换为短字节,并对短字节求和。这将解决你听到的大部分杂音。既然你正在阅读文件,为什么还要费心转换呢?为什么不用这样的方式把它作为一个短片来读呢http://docs.oracle.com/javase/1.4.2/docs/api/java/io/DataInputStream.html#readShort()

下一个问题是你必须处理范围外的值,而不是让它们“环绕”。最简单的解决方案是简单地将求和作为整数,“剪辑”到短范围内,然后存储剪辑后的输出。这将摆脱你的点击和弹出。

在psuedo-code中,整个过程看起来像这样:

file1 = Open file 1
file2 = Open file 2
output = Open output for writing

numSampleFrames1 = file1.readHeader()
numSampleFrames2 = file2.readHeader()
numSampleFrames = min( numSampleFrames1, numSampleFrames2 )
output.createHeader( numSampleFrames )

for( int i=0; i<numSampleFrames * channels; ++i ) {
    //read data from file 1
    int a = file1.readShort();
    //read data from file 2, and add it to data we read from file 1
    a += file2.readShort();
    //clip into range
    if( a > Short.MAX_VALUE )
       a = Short.MAX_VALUE;
    if( a < Short.MIN_VALUE )
       a = Short.MIN_VALUE;
    //write it to the output
    output.writeShort( (Short) a );
}

在“剪裁”步骤中会有一点失真,但没有简单的方法可以解决这个问题,而且剪裁比环绕要好得多。(也就是说,除非你的音轨非常“热”,并且在低频段很重,否则失真应该不会太明显。如果这是一个问题,你可以做其他事情:例如,将a乘以.5,跳过剪辑,但这样你的输出会安静得多,在手机上,这可能不是你想要的)。

 类似资料:
  • 我正在开发webRTC,我正在本地网络上的两个Android设备之间进行实时流,它对我来说工作得很好,除了音质问题,声音中有噪音和回声。如果我在一端使用免提,它会变得更好,但我不想使用免提。 那么我该如何提高音质,有什么技术可以提高音质。它还表示,webRTC内置了回声消除功能,如果这是回声仍然存在的原因。

  • 我从两个来源获得了两个不同的音频样本。 > 对于麦克风声音: 对于内部声音: 对于从audioBook对象读取,我们创建单独的帧对象(自定义对象称为帧)- 我们创建了两个单独的LL(链接列表)来添加我们从读函数中获得的这些帧。 private LinkedList internalAudioQueue=新建LinkedList 每次我们在各自的LL中添加一个帧时,我们调用下面的checkAndPo

  • 我正在使用这个方法将WAV文件读到字节数组(如下所示)。现在我已经将它存储在字节数组中,我想改变声音的音量。 编辑:根据要求提供音频格式的一些信息:

  • 问题内容: 我创建了一个pong克隆,当发生碰撞时,我想添加一些声音效果。我的问题是,考虑到整个应用程序只有90行代码,我发现的每个有关合成声音的示例都需要约30行代码。我正在寻找一种更简单的方法。有没有简单的方法来创建不同音调的提示音?持续时间无所谓。我只想要一系列不同音调的蜂鸣声。 问题答案: 这是从Java Sound 提取(并简化)的一个小示例-示例:生成音频的代码

  • 问题内容: 我可以使用javasound标签Wiki页面上的“正在播放”解决方案来读取和播放声音。但是,对于经常播放的声音(例如,快速的激光枪声,脚步声等),每次创建新文件时打开流并重新读取文件对我来说是不舒服的。因此,我尝试将读取的文件缓存到,然后从缓存加载它们。 加载部分很容易: 但是,最初将文件内容放入字节数组是一个挑战。问题是我正在尝试从 .jar中 包含的文件中读取声音- 因此使用不是一

  • 当用户单击应用程序WebView上的标记时,我试图播放来自assets文件夹的声音。我发现我可以使用一个扩展WebViewClient的新类来检测链接的扩展,如果它是mp3文件,它可以通过默认的音频播放器播放它。但我希望它在活动中发挥,而不开始一个新的活动。 我使用以下链接作为参考:Webview中的声音和在资产中使用Uri或文件创建MediaPlayer的错误 下面是我的完整代码: MainAc