2013-12-19 5 views
5

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

+1

К миллисекунде? Это * возможно * невозможно независимо от того, что вы делаете. Аппаратное обеспечение просто не сделано для этого. Связанное чтение: [Воспроизведение в режиме низкой латентности в Android] (http://stackoverflow.com/q/14842803/752320) – Geobits

ответ

1

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

+0

Когда я прочитал ваш комментарий, я подумал: «... это будет!». Это имело бы смысл; однако я пробовал использовать ENCODING_PCM_8BIT, а также играть с каналами, частотами выборки и т. д., даже модифицируя WAV-файлы, чтобы соответствовать каждому обстоятельству, но только закончил с более статическими. Я считаю, что причиной этого может быть устройство, на котором я тестирую (Nexus 7 2012) не поддерживает 8-битный PCM, как предупреждали документы AudioTrack. Я ценю ваше предложение, но считаю, что должна быть лучшая альтернатива. На данный момент я придерживаюсь добавления «% 2» к байту [], который добавляет примерно 0,00005 мс к каждому циклу. – user3116594

+0

Я думал, что это тоже должно быть :) Мое единственное другое желание было бы в том, что native & OpenSL _might_ даст лучшие результаты. С родной стороны AudioTrack и OpenSL должны делиться одними и теми же базовыми механизмами, но я еще недостаточно знаком с источником AudioTrack, чтобы исключить, что там где-то есть зависание. Что касается устройства, не поддерживающего 8-битный PCM, это может быть так, но вы не могли бы заменить 16-разрядные источники для тестирования? Мне любопытно, потому что у меня есть связанный проект в работах ... – Dave

+0

Кроме того, если вы не прочитали ссылку Generic Holiday Name, сделайте это. Это придает уверенность в том, что OpenSL - это путь. – Dave