在Android中精确同步循环音频和动画

5

问题:是否有办法使用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从奇数索引开始的音频数据会产生静电声而不是预期的声音,并且是否有任何解决方法。


1
到毫秒级别?无论你做什么,那都可能是不可能的。硬件就是不为此而设计的。相关阅读:Android中的低延迟音频播放 - Geobits
1个回答

1
阅读您的编辑后,我认为奇数索引导致问题的原因是您使用ENCODING_PCM_16BIT(在构造函数中传递的“2”)创建了AudioTrack。这意味着每个样本应该是16位的。如果样本为8位,请尝试使用“3”或ENCODING_PCM_8BIT

当我看到你的评论时,我想:“这一定是它!” 这将是完美的解释;然而,我尝试使用ENCODING_PCM_8BIT以及玩弄通道、采样率等,甚至修改WAV文件以适应每种情况,但最终只得到更多的静音。我认为原因可能是我正在测试的设备(Nexus 7 2012)不支持8位PCM,正如AudioTrack文档所警告的那样。我重视你的建议,但相信必须有更好的替代方案。目前,我仍然坚持向byte[]添加“%2”,这会在每个循环中增加大约0.00005毫秒。 - user3116594
我也认为应该是这样 :) 我唯一的另一个想法是,本地和OpenSL 可能 会产生更好的结果。在本地方面,AudioTrack 和 OpenSL 应该共享相同的基础机制,但我还不熟悉 AudioTrack 源代码,无法排除某些问题。至于设备不支持 8 位 PCM,可能是这样,但你不能用 16 位源替换进行测试吗?我很好奇,因为我正在进行相关项目... - Dave
另外,如果您还没有阅读提供的通用假日名称链接,请阅读。它为 OpenSL 是正确选择提供了支持。 - Dave

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接