修改 ALSA arecord 函数以将音频电平输出到 Raspberry Pi 3 RGB LED

Modify ALSA arecord function to output audio levels to Raspberry Pi 3 RGB LED

我目前可以使用我的 raspberry pi 3USB Audio input 重定向到 icecast stream。目前,当我在 Raspbian Jessie Lite 上 pipe arecord from USB audio hw input to avconv(相当于 ffmpeg)时,它在小型测试中运行良好。

当您使用详细设置时,Arecord 有一个内置的文本 vu 表用于音频级别。

我认为代码位于附加函数中。我想知道是否可以重写此函数以输出到 Raspberry Pi 3 RGB LED - 以某种方式根据音量级别发送 red/yellow/green - 使用函数的峰值变量?

我已经包含了整个函数 - 以及打印函数。如果可以的话,我认为代码可能会取代 print_vu_meter(perc, maxperc);

是否可以修改arecord让Raspi 3来处理pipe过程中的处理?有没有办法使用另一个线程?

这完全超出了我的范围 - 只是在寻找一个开始,或者一些想法来摆脱我的想法,或者说这是可能的。

peak handler

static void compute_max_peak(u_char *data, size_t count)
{
    signed int val, max, perc[2], max_peak[2];
    static  int run = 0;
    size_t ocount = count;
    int format_little_endian = snd_pcm_format_little_endian(hwparams.format);   
    int ichans, c;

    if (vumeter == VUMETER_STEREO)
        ichans = 2;
    else
        ichans = 1;

    memset(max_peak, 0, sizeof(max_peak));
    switch (bits_per_sample) {
    case 8: {
        signed char *valp = (signed char *)data;
        signed char mask = snd_pcm_format_silence(hwparams.format);
        c = 0;
        while (count-- > 0) {
            val = *valp++ ^ mask;
            val = abs(val);
            if (max_peak[c] < val)
                max_peak[c] = val;
            if (vumeter == VUMETER_STEREO)
                c = !c;
        }
        break;
    }
    case 16: {
        signed short *valp = (signed short *)data;
        signed short mask = snd_pcm_format_silence_16(hwparams.format);
        signed short sval;

        count /= 2;
        c = 0;
        while (count-- > 0) {
            if (format_little_endian)
                sval = __le16_to_cpu(*valp);
            else
                sval = __be16_to_cpu(*valp);
            sval = abs(sval) ^ mask;
            if (max_peak[c] < sval)
                max_peak[c] = sval;
            valp++;
            if (vumeter == VUMETER_STEREO)
                c = !c;
        }
        break;
    }
    case 24: {
        unsigned char *valp = data;
        signed int mask = snd_pcm_format_silence_32(hwparams.format);

        count /= 3;
        c = 0;
        while (count-- > 0) {
            if (format_little_endian) {
                val = valp[0] | (valp[1]<<8) | (valp[2]<<16);
            } else {
                val = (valp[0]<<16) | (valp[1]<<8) | valp[2];
            }
            /* Correct signed bit in 32-bit value */
            if (val & (1<<(bits_per_sample-1))) {
                val |= 0xff<<24;    /* Negate upper bits too */
            }
            val = abs(val) ^ mask;
            if (max_peak[c] < val)
                max_peak[c] = val;
            valp += 3;
            if (vumeter == VUMETER_STEREO)
                c = !c;
        }
        break;
    }
    case 32: {
        signed int *valp = (signed int *)data;
        signed int mask = snd_pcm_format_silence_32(hwparams.format);

        count /= 4;
        c = 0;
        while (count-- > 0) {
            if (format_little_endian)
                val = __le32_to_cpu(*valp);
            else
                val = __be32_to_cpu(*valp);
            val = abs(val) ^ mask;
            if (max_peak[c] < val)
                max_peak[c] = val;
            valp++;
            if (vumeter == VUMETER_STEREO)
                c = !c;
        }
        break;
    }
    default:
        if (run == 0) {
            fprintf(stderr, _("Unsupported bit size %d.\n"), (int)bits_per_sample);
            run = 1;
        }
        return;
    }
    max = 1 << (bits_per_sample-1);
    if (max <= 0)
        max = 0x7fffffff;

    for (c = 0; c < ichans; c++) {
        if (bits_per_sample > 16)
            perc[c] = max_peak[c] / (max / 100);
        else
            perc[c] = max_peak[c] * 100 / max;
    }

    if (interleaved && verbose <= 2) {
        static int maxperc[2];
        static time_t t=0;
        const time_t tt=time(NULL);
        if(tt>t) {
            t=tt;
            maxperc[0] = 0;
            maxperc[1] = 0;
        }
        for (c = 0; c < ichans; c++)
            if (perc[c] > maxperc[c])
                maxperc[c] = perc[c];

        putchar('\r');
        print_vu_meter(perc, maxperc);
        fflush(stdout);
    }
    else if(verbose==3) {
        printf(_("Max peak (%li samples): 0x%08x "), (long)ocount, max_peak[0]);
        for (val = 0; val < 20; val++)
            if (val <= perc[0] / 5)
                putchar('#');
            else
                putchar(' ');
        printf(" %i%%\n", perc[0]);
        fflush(stdout);
    }
}

print_vu_meter

static void print_vu_meter_mono(int perc, int maxperc)
{
    const int bar_length = 50;
    char line[80];
    int val;

    for (val = 0; val <= perc * bar_length / 100 && val < bar_length; val++)
        line[val] = '#';
    for (; val <= maxperc * bar_length / 100 && val < bar_length; val++)
        line[val] = ' ';
    line[val] = '+';
    for (++val; val <= bar_length; val++)
        line[val] = ' ';
    if (maxperc > 99)
        sprintf(line + val, "| MAX");
    else
        sprintf(line + val, "| %02i%%", maxperc);
    fputs(line, stdout);
    if (perc > 100)
        printf(_(" !clip  "));
}

看起来人们对此并不感兴趣,但我想我会 post 我未来的答案,以防其他人试图深入研究此类项目。

调查结果:

  • arecord 只是 aplay 的链接副本
  • 你可以使用令人难以置信的 wiringPi c 库为 C 代码添加线程
  • 通过向代码中添加静态 volatile int 变量 - 它在线程和主程序之间共享
  • 将变量设置为 perc 值意味着它会在线程程序中立即更新

我能够使用以下代码模拟带有 LED 的 6 级音频电平表

我使用面包板上的按钮在显示和不显示的 LED 级别之间切换。

setAudioLEDS线程函数:

PI_THREAD (setAudioLEDs)
{
    // Only update LEDS if button is pressed
    // Gets Audio Level from global var: globalAudioLevel

    // Wiring Pi Constants for led and button
    // Pin number declarations. We're using the Broadcom chip pin numbers.

#define CYCLE_UPDATE '0'
#define CYCLE_STEADY '1'

    int last_button;
    int last_cycle;
    int this_cycle;

    // Button is released if this returns 1
    // HIGH or LOW (1 or 0)
    last_button = digitalRead(butPin);
    last_cycle = CYCLE_STEADY;
    this_cycle = last_cycle;

    while (1)
    {
        if (digitalRead(butPin) != last_button) {
            if (last_cycle == CYCLE_UPDATE)
                this_cycle = CYCLE_STEADY;
            else
                this_cycle = CYCLE_UPDATE;
            last_cycle = this_cycle;
        }

        switch (this_cycle) {

        case CYCLE_UPDATE:

            // Set LEDS based on audio level
            if (globalAudioLevel > 20)
                digitalWrite(led1, HIGH); // Turn LED ON
            else
                digitalWrite(led1, LOW); // Turn LED OFF

            if (globalAudioLevel > 40)
                digitalWrite(led2, HIGH); // Turn LED ON
            else
                digitalWrite(led2, LOW); // Turn LED OFF

            if (globalAudioLevel > 60)
                digitalWrite(led3, HIGH); // Turn LED ON
            else
                digitalWrite(led3, LOW); // Turn LED OFF

            if (globalAudioLevel > 70)
                digitalWrite(led4, HIGH); // Turn LED ON
            else
                digitalWrite(led4, LOW); // Turn LED OFF

            if (globalAudioLevel > 80)
                digitalWrite(led5, HIGH); // Turn LED ON
            else
                digitalWrite(led5, LOW); // Turn LED OFF

            if (globalAudioLevel > 90)
                digitalWrite(led6, HIGH); // Turn LED ON
            else
                digitalWrite(led6, LOW); // Turn LED OFF

            break;

        default:
            /* Button hasn't been pressed */
            digitalWrite(led1, LOW); // Turn LED OFF
            digitalWrite(led2, LOW); // Turn LED OFF
            digitalWrite(led3, LOW); // Turn LED OFF
            digitalWrite(led4, LOW); // Turn LED OFF
            digitalWrite(led5, LOW); // Turn LED OFF
            digitalWrite(led6, LOW); // Turn LED OFF
        }
    }
}

我有一些东西可以作为 ALSA 示波器插件使用,所以播放的任何音频都会启动我的 led vu 表。您所要做的就是安装插件并编辑 /etc/asound.conf 以包含插件。

我找到了 pivumeter (https://github.com/pimoroni/pivumeter) which is based on ameter (http://laugeo.free.fr/ameter.html) 并为使用 GPIO 引脚的 2 通道立体声 16 LED 和另一个使用两个移位寄存器和 SPI 的 LED 创建了自己的设备。

我的叉子可以在这里找到:https://github.com/linuxgnuru/pivumeter

要添加您自己的设备,您需要编辑 Makefile.am、src/pivumeter.c(在底部添加您的函数)、src/devices/all.h 和您的代码在 src/devices/YOURDEVICE.c 和 src/devices/YOURDEVICE.h

我计划为范围插件浏览 ALSA C 库并从头开始制作我自己的插件。

https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m___scope.html