Android Audio — Странное поведение потокового генератора синусоидальных тонов

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

Что я пытаюсь сделать: Я создаю простой синтезатор звука для 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 байта на выборку).

9
задан Community 23 May 2017 в 02:33
поделиться