Точная синхронизация зацикленного звука с анимацией в Android

Вопрос: Есть ли способ использовать 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-го тактов [2-й и 4-й образцы в цикле]).

Статическое электричество прекращается, если я:

  • Не заполняйте byte [] какими-либо данными 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 более подходящим для создания точного цикла?

Изменить 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, генерируют статический звук вместо ожидаемого звука, и есть ли какое-либо средство от этого.


person user3116594    schedule 19.12.2013    source источник
comment
С точностью до миллисекунды? Это вероятно невозможно, что бы вы ни делали. Аппаратное обеспечение просто не предназначено для этого. Связанное чтение: Воспроизведение звука с малой задержкой в ​​Android   -  person Geobits    schedule 19.12.2013


Ответы (1)


После прочтения ваших правок я думаю, что причина, по которой нечетные индексы вызывают проблему, заключается в том, что вы создаете AudioTrack с помощью ENCODING_PCM_16BIT ("2", которое вы передаете в конструктор). Это означает, что каждая выборка должна быть 16-битной. Попробуйте использовать "3" или ENCODING_PCM_8BIT, если выборки 8-битные.

person Dave    schedule 20.12.2013
comment
Когда я прочитал ваш комментарий, я подумал ... должно быть так! Это имело бы смысл; тем не менее, я попытался использовать ENCODING_PCM_8BIT, а также поиграть с каналами, частотами дискретизации и т. д., даже модифицировав файлы WAV, чтобы они соответствовали каждому обстоятельству, но в итоге получилось больше статики. Я считаю, что причиной этого может быть устройство, на котором я тестирую (Nexus 7 2012), не поддерживает 8-битный PCM, как указано в документации AudioTrack. Я ценю ваше предложение, но считаю, что должна быть лучшая альтернатива. На данный момент я придерживаюсь добавления% 2 к байту [], что добавляет примерно 0,00005 мс к каждому циклу. - person user3116594; 21.12.2013
comment
Я думал, что это тоже должно быть :) Единственное, что мне хотелось бы - это то, что нативный и OpenSL может дать лучшие результаты. На нативной стороне AudioTrack и OpenSL должны использовать одни и те же базовые механизмы, но я еще недостаточно знаком с источником AudioTrack, чтобы исключить возможность где-то зависания. Что касается устройства, не поддерживающего 8-битный PCM, то это может быть так, но не могли бы вы заменить 16-битные источники для тестирования? Мне любопытно, потому что у меня в разработке есть связанный проект ... - person Dave; 21.12.2013
comment
Кроме того, если вы не прочитали ссылку «Общее название праздника», сделайте это. Это подтверждает идею о том, что OpenSL - лучший выбор. - person Dave; 21.12.2013