Получение диапазонов переменной частоты с классом визуализатора Android - PullRequest
2 голосов
/ 04 марта 2020

Я хочу получить значения для определенных диапазонов частот звука, воспроизводимого смартфоном, чтобы я мог перенаправить их через Bluetooth на устройство, которое визуализирует эти диапазоны. Эти диапазоны: 0-63Hz 63-160Hz 160-400Hz 400-1000Hz 1000-2.500Hz 2.500-6.250Hz 6.250-16.000Hz

Идентификатор аудиосеанса равен 0, поэтому я могу использовать любой звук, воспроизводимый смартфоном. Я нашел класс визуализатора, и я подумал, что смогу добиться этого с помощью метода getFft. Хотя кажется, что я могу разделить частоты только на части одинакового размера со скоростью захвата? Или я совершенно не понимаю что-то здесь? Я попытался просто использовать частоту дискретизации в качестве частоты захвата, чтобы у меня было значение для каждой частоты, но он просто снова установил бы скорость захвата 1024. Или, может быть, этот класс просто не то, что я хочу? Я думаю, что я мог бы полностью упустить момент, поэтому любая помощь или объяснение (или рекомендация другой библиотеки) будут приветствоваться.

        val visualizer = Visualizer(0)
        visualizer.scalingMode = 0

        visualizer.setDataCaptureListener(object : Visualizer.OnDataCaptureListener {
            override fun onWaveFormDataCapture(
                vis: Visualizer,
                bytes: ByteArray,
                samplingRate: Int
            ) {

            }

            override fun onFftDataCapture(
                visualizer: Visualizer?,
                fft: ByteArray?,
                samplingRate: Int
            ) {
                //if frequency <=63 do something
                //else if frequency <=160 do something ...
            }

        }, Visualizer.getMaxCaptureRate() / 2, false, true)
        visualizer.enabled = true


1 Ответ

2 голосов
/ 04 марта 2020

Математике расчета БПФ присуще то, что он будет генерировать частотные «сегменты», которые имеют одинаковый размер и имеют счет, равный половине размера выборки и go до частоты, равной половине. частота дискретизации. (FFT на самом деле производит сегменты, равные размеру выборки, но Visualizer Android идет вперед и выдает вторую половину перед выдачей результатов, потому что они содержат отражение первой половины, и поэтому бесполезны для визуализации.)

Будет очень ограниченный диапазон допустимых размеров захвата и скоростей захвата, основанных на аппаратных возможностях и простой старой физике. Кроме того, эти два свойства обратно пропорциональны. Если ваш размер захвата велик, ваш коэффициент захвата должен быть маленьким. Звук воспроизводится в виде потока равномерно синхронизированных амплитуд (где расстояние составляет samplingRate). Предположим для простоты, что аудиопоток только на частоте 1024 Гц, производящий 1024 амплитуды в секунду. Если ваша скорость захвата составляет 1 в секунду, вы собираете все 1024 из этих амплитуд при каждом захвате, поэтому ваш размер захвата составляет 1024. Если ваша скорость захвата составляет 2 в секунду, вы собираете 512 амплитуд при каждом захвате, поэтому ваш захват размер 512.

Обратите внимание, я точно не знаю, если вы установите размер захвата, и он не будет обратно соответствовать вашей скорости захвата, используемой в setDataCaptureListener, будет ли он игнорировать установленный вами размер или фактически повторяет / отбрасывает данные. Я всегда использую Visualizer.getMaxCaptureRate() в качестве скорости захвата.

Что вы можете сделать (и это не будет точно), это усреднить соответствующие диапазоны, хотя я думаю, что вы захотите применить функцию log к Величина, прежде чем вы в среднем, или результаты не будут выглядеть великолепно. Вам определенно необходимо применить функцию логарифмирования к величинам в некоторой точке, прежде чем визуализировать их, чтобы визуализатор имел смысл для зрителя.

Поэтому после выбора размера захвата вы можете подготовить диапазоны для использования для сбора результатов.

private val targetEndpoints = listOf(0f, 63f, 160f, 400f, 1000f, 2500f, 6250f, 16000f)
private val DESIRED_CAPTURE_SIZE = 1024 // A typical value, has worked well for me
private lateinit var frequencyOrdinalRanges: List<IntRange>
//...

val captureSizeRange = Visualizer.getCaptureSizeRange().let { it[0]..it[1] }
val captureSize = DESIRED_CAPTURE_SIZE.coerceIn(captureSizeRange)
visualizer.captureSize = captureSize
val samplingRate = visualizer.samplingRate
frequencyOrdinalRanges = targetEndpoints.zipWithNext { a, b ->
        val startOrdinal = 1 + (captureSize * a / samplingRate).toInt()
        // The + 1 omits the DC offset in the first range, and the overlap for remaining ranges
        val endOrdinal = (captureSize * b / samplingRate).toInt()
        startOrdinal..endOrdinal
    }

А потом в вашем слушателе

override fun onFftDataCapture(
    visualizer: Visualizer,
    fft: ByteArray,
    samplingRate: Int
) {
    val output = FloatArray(frequencyOrdinalRanges.size)
    for ((frequencyOrdinalRange, i) in frequencyOrdinalRanges.withIndex) {
        var logMagnitudeSum = 0f
        for (k in ordinalRange) {
            val fftIndex = k * 2
            logMagnitudeSum += log10(hypot(fft[fftIndex].toFloat(), fft[fftIndex + 1].toFloat()))
        }
        output[i] = logMagnitudeSum / (ordinalRange.last - ordinalRange.first + 1)
    }
    // If you want magnitude to be on a 0..1 scale, you can divide it by log10(hypot(127f, 127f))
    // Do something with output
}

Я не проверял ничего из вышеперечисленного, поэтому могут быть ошибки. Просто пытаюсь донести стратегию.

...