2012-06-12 49 views
4

我正在构建一个相当简单的Android应用程序(sdk revision 14:ICS),它允许用户一次选择两个音频剪辑(全部为RIFF/WAV格式,很少-endian,签名的PCM-16位编码)并以各种方式组合它们以创建新的声音。我使用该组合中的最基本的方法是如下:使用Android的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]); 
     } 

} 

的组合和重放的结果:

//...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类正是如此被播放使用上面的代码接近我想要的(这两个样本在所产生的混合声音中仍然是可辨别的),但也存在很多裂缝,爆裂声和其他噪声。

所以,三个问题:首先,我正确使用AudioTrack?其次,AudioTrack配置中的排序在哪里?这些声音本身播放得很好,听起来几乎就像我所期望的那样,因此RIFF/WAV格式的小端特性似乎在某处传达,但我不确定它在哪里。最后,对于有符号的16位PCM编码,我应该看到什么字节值范围?我期望在上面的Log.d(...)调用中看到logcat中-32768到32767之间的值,但是结果往往在-100到100的范围内(除了一些异常值外)。也许,组合字节值超出16位范围可能会造成噪声?

感谢, CCJ

UPDATE:主要得益于比约内罗氏公司和威廉Coderer!我现在读音频数据为short []结构,DataInputStream的字节序使用来自William的EndianInputStream(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; 
} 

混合音频输出质量与这些改进是真棒!

回答

4

我对android音频不熟悉,所以我不能回答你所有的问题,但我可以告诉你什么是根本问题:逐字节地添加音频数据将不起作用。由于它的工作原理,查看你的代码以及它最常见的事实,我假定你有16位PCM数据。然而在任何地方,你都在处理字节。字节不适合处理音频(除非音频恰好是8位)

字节是aprox +/- 128.你说:“我希望看到Logcat中从-32768到32767的值从Log .d(...)调用,但是结果往往在-100到100的范围内(除了一些以外的异常值)“那么,当你打印一个值时,你怎么可能去那个范围?字节数组? 16位有符号数据的正确数据类型很短,而不是字节。如果您打印的是短值,则会看到您预期的范围。

您必须将您的字节转换为短裤并将短裤求和。这会照顾你听到的大部分噪音。既然你正在阅读文件,但是,为什么要转换?为何不看看它关闭该文件作为短期使用这样的事情 http://docs.oracle.com/javase/1.4.2/docs/api/java/io/DataInputStream.html#readShort()

下一个问题是,你必须处理超出范围的值,而不是让他们“环绕”。最简单的解决方案是简单地将求和作为整数,“剪辑”到短距离内,然后存储剪裁的输出。这将消除你的点击和流行。

在伪代码,整个过程将是这个样子:

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); 
} 

你会从“剪辑”的步骤变得有点扭曲,但有周围没有简单的方法,裁剪是MUCH好于环绕。 (也就是说,除非你的音轨非常“热”,并且在低频率下很重,那么失真不应该太明显,如果这是一个问题,你可以做其他事情:例如乘以0.5,跳过剪辑,但是你的输出会更安静,这在手机上可能不是你想要的)。

+0

哎呦......感谢您指出需要使用短数组而不是字节数组;它实际上是16位的PCM,所以我不知道为什么我认为逐字节的存储和处理工作。也许是因为我无法真正找到脉冲编码调制在内部工作的良好解释......您是否有任何关于了解数字音频编码/处理的低级细节的建议参考? – CCJ

+0

另外值得注意的是,由于我的RIFF/WAV音频文件是使用little-endian字节排序进行编码的,因此我需要使用DataInputStream的修改版本来正确读取短值(股票java版本假设为大端)。幸运的是,我发现了一个很好的实现必要的按位操作来完成此任务在这里:http://stackoverflow.com/questions/8028094/java-datainputstream-replacement-for-endianness – CCJ

+0

是的,你需要处理字节序,作为好。你可能会考虑一个类似于RandomAccessFIle的包装器。对于参考资料,你可以从这里开始:http://blog.bjornroche.com/2011/11/slides-from-fundamentals-of-audio.html还有一本书叫数字音频与Java它现在已经过时,并有一些不准确的地方,但它有工作代码,这是你不会在很多地方找到的东西。在我的第一个链接更多的参考。 –

相关问题