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

0

I currently can use my raspberry pi 3 to redirect USB Audio input to an icecast stream. Currently it works fine in small tests when I pipe arecord from USB audio hw input to avconv (ffmpeg equivalent) on Raspbian Jessie Lite.

Arecord has a built in text vu meter for audio levels when you use the verbose setting.

I think the code is located in the attached function. I'm wondering if it's possible to rewrite this function to output to Raspberry Pi 3 RGB LED - to somehow send the red/yellow/green based on volume levels - using the function's peak variable?

I've include the whole function - and the print function. If it could be done, I think the code could probably replace print_vu_meter(perc, maxperc);

Is it possible to modify arecord to get the Raspi 3 to handle the processing during the pipe? Is there a way to use another thread?

Way out of my league here - just looking for a start, or some ideas to get the idea out of my head or to say it's possible.

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  "));
}
c
debian
raspbian
alsa
raspberry-pi3
asked on Stack Overflow Jul 3, 2016 by dbmitch

2 Answers

0

Doesn't look like there's much interest in this, but I thought I'd post my answer for future, in case anyone else tries to delve into this type of project.

Findings:

  • arecord is just a linked copy of aplay
  • you can use the incredible wiringPi c library to add a thread to the C code
  • by adding a static volatile int variable to the code - it becomes shared with between thread and main program
  • setting the variable to the perc value means it's updated immediately in the threaded program

I was able to simulate a 6 level audio level meter with LEDs using this code:

I used a button on the breadboard to toggle between LED levels being displayed and not.

setAudioLEDS threaded function:

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
        }
    }
}
answered on Stack Overflow Jul 12, 2016 by dbmitch
0

I have something working as a ALSA scope plugin so any audio played will start my led vu meter. All you have to do is install the plugin and edit /etc/asound.conf to include the plugin.

I found pivumeter (https://github.com/pimoroni/pivumeter) which is based on ameter (http://laugeo.free.fr/ameter.html) and created my own devices for a 2 channel stereo 16 LED using GPIO pins and another that uses two shift registers and SPI.

My fork can be found here: https://github.com/linuxgnuru/pivumeter

To add your own device, you'll need to edit Makefile.am, src/pivumeter.c (at the bottom add your function), src/devices/all.h, and your code in src/devices/YOURDEVICE.c and src/devices/YOURDEVICE.h

I plan on going through the ALSA C library for the scope plugin and making my own plugin from scratch.

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

answered on Stack Overflow May 24, 2018 by linuxgnuru

User contributions licensed under CC BY-SA 3.0