Вопрос по audio, android, java – Android Audio - Потоковый генератор синусоидального тона

9

Первый постер здесь. Я обычно хотел бы найти ответ сам (будь то путем исследований или методом проб и ошибок), но я здесь озадачен.

What I'm trying to do: Я создаю простой аудио-синтезатор для Android. Прямо сейчас я просто играю синусоидальный сигнал в реальном времени с помощью ползунка в пользовательском интерфейсе, который изменяет частоту тона, когда пользователь настраивает его.

How I've built it: В основном у меня есть два потока - рабочий поток и выходной поток. Рабочий поток просто заполняет буфер данными синусоидальной волны каждый раз, когда вызывается его метод tick (). Как только буфер заполнен, он уведомляет выходной поток о том, что данные готовы для записи в аудиодорожку. Причина, по которой я использую два потока, заключается в том, что audiotrack.write () блокируется, и я хочу, чтобы рабочий поток мог начать обработку своих данных как можно скорее (вместо того, чтобы ожидать завершения записи звуковой дорожки). Ползунок в пользовательском интерфейсе просто изменяет переменную в рабочем потоке, так что любые изменения частоты (через ползунок) будут считываться методом tick () рабочего потока.

What works: Почти все; Потоки хорошо взаимодействуют друг с другом, в воспроизведении, похоже, нет никаких пробелов или щелчков. Несмотря на большой размер буфера (спасибо Android), отзывчивость в порядке. Изменяется частотная переменная, равно как и промежуточные значения, используемые при вычислениях буфера в методе tick () (проверено Log.i ()).

What doesn't work: По какой-то причине у меня не может быть непрерывного изменения слышимой частоты. Когда я настраиваю ползунок, частота изменяется с шагом, часто равным четвертым или пятым частям. Теоретически, я должен слышать изменения с точностью до 1 Гц, но я не слышу. Как ни странно, кажется, что изменения в ползунке заставляют синусоиду играть через интервалы в гармоническом ряду; Однако я могу убедиться, что переменная частоты НЕ привязана к целым кратным частоты по умолчанию.

Моя аудиодорожка настроена так:

<code>_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);
</code>

Буфер рабочего потока заполняется (через tick ()) следующим образом:

<code>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;     
}
</code>

Аудиоданные пишутся так:

<code>_audioTrackOut.write(fromWorker, 0, fromWorker.length);
</code>

Любая помощь будет принята с благодарностью. Как я могу получить более постепенные изменения частоты? Я вполне уверен, что моя логика в tick () правильная, поскольку Log.i () проверяет, что переменные angleIncrement и currentAngle обновляются правильно.

Спасибо!

Update:

Я нашел похожую проблему здесь:Проблемы с буферизацией Android AudioTrack Решение предполагает, что нужно иметь возможность производить сэмплы достаточно быстро для audioTrack, что имеет смысл. Я снизил частоту дискретизации до 22050 Гц и провел несколько эмпирических тестов - в худшем случае я могу заполнить свой буфер (через tick ()) примерно за 6 мс. Это более чем адекватно. На частоте 22050 Гц audioTrack дает мне размер буфера 2048 сэмплов (или 4096 байт). Таким образом, каждый заполненный буфер длится около 0,0928 секунд аудио, что намного дольше, чем требуется для создания данных (1 ~ 6 мс). Итак, я знаю, что у меня нет проблем с производством образцов достаточно быстро.

Следует также отметить, что в течение первых 3 секунд жизненного цикла приложений он работает нормально - плавное перемещение ползунка приводит к плавному развертыванию аудиовыхода. После этого он начинает становиться действительно прерывистым (звук меняется только на каждые 100 МГц), а после этого он вообще перестает реагировать на ввод слайдера.

Я также исправил одну ошибку, но я не думаю, что она дает эффект. AudioTrack.getMinBufferSize () возвращает наименьший допустимый размер буфера в байтах, и я использовал это число в качестве длины буфера в tick () - теперь я использую половину этого числа (2 байта на выборку).

Что возвращает AudioTrack.getMinBufferSize () на ваше устройство с вашими настройками? praetorian droid
В сети я нашел похожую проблему, но предложенное решение не помогло - см. Обновление выше. Tsherr
Какое событие вы используете для_freq меняется? Это действительно меняется на 1 Гц каждый раз? Извините, я не совсем понимаю вашеWhat doesn't work раздел. Не могли бы вы предоставить полный пример рабочего кода или синтезированный звуковой фрагмент, чтобы сделать возможнымhear твоя проблема. Для звукового фрагмента: формат исходных звуковых данных в порядке - просто запишите свой буфер в файл, а не на звуковую карту. Например, 16 бит / со знаком / моно / 44100 Гц. praetorian droid
Чтобы изменить _freq, я использую OnSeekBarChangeListener, который записывает новое значение int в _freq через onProgressChanged (). Однако я также пытался использовать кнопки (вверх / вниз), которые увеличивают частоту на 1 Гц, и после многих, многих приращений слышна только разница в высоте звука (такая, что звук скачет по высоте примерно на 3 или 4 части). , а не 1 Гц). Вот пример того, что происходит; Это то, что я слышу, когда медленно перемещаю ползунок вверх и вниз, увеличивая частоту с шагом 1 Гц. (от ~ 200 до ~ 1000 Гц):sendspace.com/file/tpxuwr Tsherr
Используя моно, 16-битную PCM и частоту дискретизации 22050, getMinBufferSize () возвращает 4096. В последнем редактировании моего исходного поста я упоминаю, что я обновил свой код, чтобы разделить это число на 2 при заполнении буфера сэмплами (по мере необходимости) 2048 шорт для заполнения буфера 4096 байт). Tsherr

Ваш Ответ

1   ответ
5

I've found it!

Оказывается, проблема не имеет ничего общего с буферами или многопоточностью.

Звучит нормально в первые пару секунд, потому что угол вычисления относительно невелик. По мере выполнения программы и увеличения угла Math.sin (_currentAngle) начинает выдавать ненадежные значения.

Итак, я заменилMath.sin() сFloatMath.sin().

Я тоже заменил _currentAngle = _currentAngle + _angleIncrement;

с

_currentAngle = ((_currentAngle + _angleIncrement) % (2.0f * (float) Math.PI));, поэтому угол всегда & lt; 2 * PI.

Работает как шарм! Большое спасибо за вашу помощь, преторианский дроид!

Блин, я забыл про 2 * нормализацию Пи! Я видел его в своем старом коде, но пропустил его отсутствие в вашем. Но я думал, что переполнение переменной угла должно дать эффект щелчков, и ваш образец звучит по-другому. В любом случае, я рад, что вы решили свою проблему.

Похожие вопросы