问题:是否有办法使用AudioTrack和setLoopPoints()来根据每毫秒的样本/帧数配置准确的循环?
编辑:我理解大多数Android设备所拥有的处理能力无法期望完美的准确性。然而,我希望得到一个接近于节奏“真实”间隔时间的平均循环时间(以毫秒为单位),因为这是我基于一个应该与节奏同步的“动画”(这个动画是一个SurfaceView,它在节奏间隔的持续时间内重绘一条线的坐标)。
细节:我正在尝试使用AudioTrack和setLoopPoints创建精确的节拍器。为此,我使用两个wav文件(Tick和Tock)填充了一个byte []数组并提供给AudioTrack。考虑一个4/4拍的例子,我会将一个byte[]填充为Tick开始于[0],三次使用Tock(使用arrayCopy())到[length/4],[length/2]和[3*length/4],并假定wav数据不会互相重叠。
代码示例:
// read each wav file's header into its own ByteBuffer (with LITTLE_ENDIAN order)
// ... then get the size of the audio data
tickDataSize = tickHeaderBuffer.getInt(40);
tockDataSize = tockHeaderBuffer.getInt(40);
// allocate space for one loop at the current tempo into a byte[] array (to be given to AudioTrack)
// e.g. 22050hz * 2 Bytes (1 per channel) * 2 Bytes (1 per 8 bit PCM sample) = 22050*4 Bytes/second
// If my tempo were 60 BPM I'd have 88200 Bytes/second (where 1 second is the whole loop interval);
// 110 BPM = 48109.0909091 Bytes per loop interval (where 1 loop interval is 0.54545 seconds)
int tempo = 110;
int bytesPerSecond = sampleRate * 2 * 2;
int bytesPerInterval = (int)((((float)bytesPerSecond * 60.0F)/(float)tempo) * 4);
byte[] wavData = new byte[bytesPerInterval];
// ... then fill wavData[] as mentioned above with 1*wavOne and 3*wavTwo
// Then feed to an instance of AudioTrack and set loop points
audioTrack.write(wavData, 0, bytesPerInterval);
int numSamples = bytesPerInterval/4;
audioTrack.setLoopPoints(0, numSamples, -1);
audioTrack.play();
希望你已经开始看到这个问题了。在某些节奏中,我只能听到静音播放的循环声(但仅在第1和第3个Tock [循环中的第2个和第4个样本])。
静音会停止,如果我: - 不用任何wav数据填充byte [],但保持bytesPerInterval和numSamples相同(正确时长的静音循环)。 - 设置bytesPerInterval = bytesPerInterval%4(从而失去节奏准确性)
以下是有效(无静音)和无效(有静音)节奏及其所需帧数的示例(考虑一秒钟= 88200帧):
tempo: 110 (static)
wavData.length: 192436
numFrames: 48109
tempo: 120 (no static)
wavData.length: 176400
numFrames: 44100
tempo: 130 (static)
wavData.length: 162828
numFrames: 40707
tempo: 140 (no static)
wavData.length: 151200
numFrames: 37800
tempo: 150 (no static)
wavData.length: 141120
numFrames: 35280
tempo: 160 (static)
wavData.length: 132300
numFrames: 33075
tempo: 170 (static)
wavData.length: 124516
numFrames: 31129
tempo: 180 (no static)
wavData.length: 117600
numFrames: 29400
如果回答是否定的,即“不能使用setLoopPoints()来配置精确到毫秒的循环”,那么我想知道是否有其他选项。在生成精确循环方面,OpenSL ES in NDK、SoundPool或MediaPlayer哪个更合适?
编辑2:我已经缩小了引起静态问题的位置:
// Assume a tempo of 160 BPM which requires byte[132300]
wavStream1 = this.context.getResources().openRawResource(R.raw.tick);
wavStream2 = this.context.getResources().openRawResource(R.raw.tock);
ByteBuffer headerBuffer1 = ByteBuffer.allocate(44);
ByteBuffer headerBuffer2 = ByteBuffer.allocate(44);
headerBuffer1.order(ByteOrder.LITTLE_ENDIAN);
headerBuffer2.order(ByteOrder.LITTLE_ENDIAN);
wavStream1.read(headerBuffer1.array(), 0, 44);
wavStream2.read(headerBuffer2.array(), 0, 44);
int tickDataSize = headerBuffer1.getInt(40);
int tockDataSize = headerBuffer2.getInt(40);
byte[] wavData = new byte[bytesPerInterval * 4];
byte[] tickWavData = new byte[bytesPerInterval];
byte[] tockWavData = new byte[bytesPerInterval];
wavStream1.read(accentWavData, 0, tickDataSize);
wavStream2.read(normalWavData, 0, tockDataSize);
System.arraycopy(tickWavData, 0, wavData, 0, bytesPerInterval);
System.arraycopy(tockWavData, 0, wavData, 33075, bytesPerInterval);
System.arraycopy(tockWavData, 0, wavData, 66150, bytesPerInterval);
System.arraycopy(tockWavData, 0, wavData, 99225, bytesPerInterval);
// bytesPerInterval of 33075 and 99225 (or any odd number) will be
// static when wavData is played
AudioTrack audioTrack = new AudioTrack(3, 22050, 12, 2, wavData.length, 0);
audioTrack.write(wavData, 0, wavData.length);
audioTrack.setLoopPoints(0, bytesPerInterval, -1);
audioTrack.play();
最重要的是,我想了解为什么wavData从奇数索引开始的音频数据会产生静电声而不是预期的声音,并且是否有任何解决方法。