我正在构建一个相当简单的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;
}
混合音频输出质量与这些改进是真棒!
哎呦......感谢您指出需要使用短数组而不是字节数组;它实际上是16位的PCM,所以我不知道为什么我认为逐字节的存储和处理工作。也许是因为我无法真正找到脉冲编码调制在内部工作的良好解释......您是否有任何关于了解数字音频编码/处理的低级细节的建议参考? – CCJ
另外值得注意的是,由于我的RIFF/WAV音频文件是使用little-endian字节排序进行编码的,因此我需要使用DataInputStream的修改版本来正确读取短值(股票java版本假设为大端)。幸运的是,我发现了一个很好的实现必要的按位操作来完成此任务在这里:http://stackoverflow.com/questions/8028094/java-datainputstream-replacement-for-endianness – CCJ
是的,你需要处理字节序,作为好。你可能会考虑一个类似于RandomAccessFIle的包装器。对于参考资料,你可以从这里开始:http://blog.bjornroche.com/2011/11/slides-from-fundamentals-of-audio.html还有一本书叫数字音频与Java它现在已经过时,并有一些不准确的地方,但它有工作代码,这是你不会在很多地方找到的东西。在我的第一个链接更多的参考。 –