первый постер здесь. Обычно мне нравится находить ответ самому (будь то с помощью исследований или методом проб и ошибок), но здесь я зашел в тупик.
Что я пытаюсь сделать: Я создаю простой синтезатор звука для Android. Прямо сейчас я просто играю синусоидальный тон в реальном времени с помощью ползунка в пользовательском интерфейсе, который изменяет частоту тона по мере того, как пользователь настраивает его.
Как я это построил: По сути, у меня есть два потока — рабочий поток и выходной поток. Рабочий поток просто заполняет буфер данными синусоиды каждый раз, когда вызывается его метод tick(). Как только буфер заполнен, он уведомляет поток вывода о том, что данные готовы к записи на звуковую дорожку. Причина, по которой я использую два потока, заключается в том, что audiotrack.write() блокирует, и я хочу, чтобы рабочий поток мог начать обработку своих данных как можно скорее (вместо ожидания завершения записи звуковой дорожки). Ползунок в пользовательском интерфейсе просто изменяет переменную в рабочем потоке, так что любые изменения частоты (посредством ползунка) будут считываться методом tick() рабочего потока.
Что работает: Почти все; Потоки общаются хорошо, в воспроизведении нет никаких пауз или щелчков. Несмотря на большой размер буфера (спасибо андроиду), отзывчивость в норме.Переменная частоты меняется, как и промежуточные значения, используемые при расчетах буфера в методе tick() (проверено Log.i()).
Что не работает: По какой-то причине я не могу получить непрерывное изменение слышимой частоты. Когда я настраиваю ползунок, частота меняется ступенями, часто шириной в четверти или пятые. Теоретически я должен слышать изменения с частотой 1 Гц, но это не так. Как ни странно, кажется, что изменения ползунка заставляют синусоиду воспроизводить интервалы в гармоническом ряду; Однако я могу убедиться, что переменная частоты НЕ привязывается к целому кратному частоте по умолчанию.
Моя звуковая дорожка настроена следующим образом:
_buffSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);
_audioTrackOut = new AudioTrack(AudioManager.STREAM_MUSIC, _sampleRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, _buffSize, AudioTrack.MODE_STREAM);
Буфер рабочего потока заполняется (посредством tick()) следующим образом:
public short[] tick()
{
short[] outBuff = new short[_outBuffSize/2]; // (buffer size in Bytes) / 2
for (int i = 0; i < _outBuffSize/2; i++)
{
outBuff[i] = (short) (Short.MAX_VALUE * ((float) Math.sin(_currentAngle)));
//Update angleIncrement, as the frequency may have changed by now
_angleIncrement = (float) (2.0f * Math.PI) * _freq / _sampleRate;
_currentAngle = _currentAngle + _angleIncrement;
}
return outBuff;
}
Аудиоданные записываются следующим образом:
_audioTrackOut.write(fromWorker, 0, fromWorker.length);
Любая помощь будет высоко ценится. Как я могу получить более плавные изменения частоты? Я вполне уверен, что моя логика в tick() верна, так как Log.i() проверяет правильность обновления переменных angleIncrement и currentAngle.
Спасибо!
Обновление:
Я обнаружил аналогичную проблему здесь: Проблемы с буферизацией Android AudioTrack Решение предполагало, что нужно иметь возможность создавать сэмплы достаточно быстро для audioTrack, что имеет смысл. Я снизил частоту дискретизации до 22050 Гц и провел несколько эмпирических тестов — в худшем случае я могу заполнить свой буфер (через тик()) примерно за 6 мс. Этого более чем достаточно. На частоте 22050 Гц audioTrack дает мне размер буфера 2048 сэмплов (или 4096 байт). Итак, каждый заполненный буфер длится ~0.0928 секунд звука, что намного дольше, чем требуется для создания данных (1~6 мс). Итак, я знаю, что у меня нет проблем с достаточно быстрым производством образцов.
Я также должен отметить, что в течение примерно первых 3 секунд жизненного цикла приложения он работает нормально — плавное перемещение ползунка приводит к плавному перемещению аудиовыхода. После этого он начинает становиться очень прерывистым (звук меняется примерно каждые 100 МГц), а после этого вообще перестает реагировать на ввод ползунка.
Я также исправил одну ошибку, но не думаю, что это повлияло. AudioTrack.getMinBufferSize() возвращает наименьший допустимый размер буфера в БАЙТАХ, и я использовал это число как длину буфера в tick() — теперь я использую половину этого числа (2 байта на выборку).