Вопрос: Есть ли способ использовать AudioTrack и setLoopPoints() для настройки петли с точностью, основанной на выборках/кадрах за миллисекунду?Точно синхронизировать аудио с анимацией в Android
Редактировать: Я понимаю, что идеальной точности нельзя ожидать от вычислительной мощности, которой обладают большинство устройств Android. Тем не менее, я хотел бы получить среднее время цикла, близкое к «реальному» интервалу темпа в миллисекундах, так как я основал «анимацию», которая также должна синхронизироваться с темпом (анимация - это SurfaceView, которая перерисовывает координаты линии по длительности интервала темпа).
Подробности: Я пытаюсь использовать AudioTrack с setLoopPoints для создания точного метронома. Для этого я использую два wav-файла (Tick и Tock), чтобы заполнить массив byte [] для подачи в AudioTrack. Рассмотрим пример в 4/4 раза, когда я должен заполнить байт [], один раз с тиком, начинающимся с [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-го токов [2-й и 4-й выборки в цикле]).
Статические остановки, если я:
- Не заполняющие байт [] с любыми данными Wav, но держать 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 в NDK, SoundPool или MediaPlayer быть более подходящим для создания точной петли?
Edit 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 генерирует статический, а не ожидаемый звук, и если есть какое-либо средство для этого.
К миллисекунде? Это * возможно * невозможно независимо от того, что вы делаете. Аппаратное обеспечение просто не сделано для этого. Связанное чтение: [Воспроизведение в режиме низкой латентности в Android] (http://stackoverflow.com/q/14842803/752320) – Geobits