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 "));
}
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:
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
}
}
}
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
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.