我想用几部安卓手机做远程音频检测器,需要采集几部不同手机的数据,用Prometheus/Grafana集中分析。
我有一个 Android 应用程序(使用 Noise)成功录制音频,并对其进行分析(使用 Kiss FFT)以计算每个频率的幅度。
噪声以
FloatArray
的形式返回 FFT,Android 应用程序使用它来将音频频率可视化为热图和直方图。
import android.media.AudioRecord
import com.paramsen.noise.Noise
import com.paramsen.noise.sample.R
import com.paramsen.noise.sample.source.AudioSource
/** Subscribe to microphone updates */
private fun start(recorder: AudioRecord) {
val src = AudioSource(recorder).stream()
val noise = Noise.real(SAMPLE_SIZE)
//FFT Views
disposable.add(src.observeOn(Schedulers.newThread())
.map {
// compute the FFT
noise.fft(it, FloatArray(SAMPLE_SIZE + 2))
}
.subscribe({ fft: FloatArray ->
// forward the FFT data to the visualisers
fftHeatMapView.onFFT(fft)
fftBandView.onFFT(fft)
}, { e -> Log.e(TAG, e.message ?: "null") })
)
}
const val RATE_HZ = 44100
const val SAMPLE_SIZE = 4096
FFT 数据按预期呈现(或多或少 - 我认为窗口函数需要调整)。
下一步是尝试将FFT数据转发给Prometheus。我为 FFT 数据创建了一个新的处理程序,并创建了一个 Prometheus 直方图指标。
object FFTMetrics : FFTView {
private val registry = PrometheusMeterRegistry(PrometheusConfig.DEFAULT)
private val fftDist = DistributionSummary
.builder("fft.dist")
.description("FFT distribution")
.baseUnit("hertz")
//.scale(100.0)
.register(registry)
override fun onFFT(fft: FloatArray) {
// TODO
}
}
然而,我唯一可用的选择是记录一次读数,在这种情况下这是不正确的。 FFT 数组的每个浮点值代表记录频率的magnitude。该数组是二维的,其中索引代表赫兹,而千分尺指标仅允许记录一维值。
override fun onFFT(fft: FloatArray) {
fft.forEach { magnitude: Float ->
// this is incorrect:
summary.record(magnitude.toDouble())
}
}
我想做的是多次增加单个桶
override fun onFFT(fft: FloatArray) {
fft.forEachIndexed { i: Int, magnitude: Float ->
val hz = computeHertzForBin(i)
// I want to record multiple readings per bin:
summary.record(hz, magnitude)
}
}
我曾尝试用 for 循环实现等价物,但它速度慢且笨拙,并且不必要地需要在数字类型之间进行转换。
override fun onFFT(fft: FloatArray) {
fft.forEachIndexed { i, magnitude ->
val hz = computeHertzForBin(i)
(0..magnitude.toLong()).forEach { j ->
summary.record(hz.toDouble())
}
}
}
是否有更好的方法在 Micrometer 或 Prometheus 中记录二维数据?
我在用
直接回答你的问题,普罗米修斯不接受多维值。为此,您应该使用标签。在你的例子中,在我看来 Hz 将是一个标签,而幅度将是它的值。例如,假设您的指标称为
signal_magnitude
,我认为您是在提议:
signal_magnitude = 10KHz, 10.11
(表示频率 10KHz 的幅度为 10.11 - 我发明的所有任意数字组成示例)
由于频率集合已知且有限,适合作为标签:
signal_magnitude{frequency=100000} = 10.11
在这种情况下,您的导出器会向 Prometheus 提供一组指标,例如:
signal_magnitude{frequency=10000} = 10.11
signal_magnitude{frequency=20000} = 5.0
signal_magnitude{frequency=30000} = 3.76
signal_magnitude{frequency=40000} = 18.27
signal_magnitude{frequency=50000} = 8.943
为了分析这一点,可以使用
frequency
标签作为热图图表的维度。
请注意,重要的是要考虑一个标签可以采用多少个不同的值,这将表示您的数据库的基数。由于度量名称和标签集的每种不同组合都会增加基数。这就是为什么标签值应该是有限的。
现在,根据其他想法,Prometheus 是否适合您的应用程序?
由于 Prometheus 每 N 秒抓取一次您提供给它的指标,如果您在不到 N 秒内多次更新指标值,您将丢失数据点。即使你愿意通过 Pushgateway 推送指标,它们也会在那里被覆盖并被 Prometheus 服务器刮掉一次。
此外,Prometheus 不允许回填,这意味着如果您正在后期处理此音频文件,则无法指定过去的数据点。数据点的时间戳由 Prometheus 服务器的抓取时间给出。
如果您需要“每个时间点的每个数据点”都在数据库中并且知道时间(抓取间隔)由 Prometheus 服务器(而不是您的应用程序)控制并且错过抓取意味着丢失数据点,我会考虑另一个此任务的时间序列数据库。也许 InfluxDB 更适合这种场景。