dancyPi-audio-reactive-led icon indicating copy to clipboard operation
dancyPi-audio-reactive-led copied to clipboard

Exiting arecord in shell script?

Open farhandsome opened this issue 4 years ago • 72 comments

So I'm utilizing dancyPi in sync with Raspotify / Librespot. Librespot has hooks so I'm using the follow script:

#!/bin/bash
if [ "$PLAYER_EVENT" = "start" ]; then
  sudo python3 home/pi/dancyPi-audio-reactive-led/python/visualization.py scroll
elif [ "$PLAYER_EVENT" = "stop" ]; then
  sudo python3 home/pi/dancyPi-audio-reactive-led/python/off.py
else
  :
fi

However, I did not realize that the "off.py" script did not kill the mic recording. What would be the best way of freeing up the mic after Spotify stops?

farhandsome avatar Nov 02 '20 04:11 farhandsome

You might need to force kill the script along with running off.py.

Try something like this in your 'stop' logic:

sudo pkill -f visualization.py

That command should look for that script and kill it, which would kill the mic. Let me know if this helps.

naztronaut avatar Nov 02 '20 05:11 naztronaut

I'd create a systemd unit file to run the python scripts as system services. Stopping the service should kill the script. Something like this might do the trick:

#/etc/systemd/system/led-visualization.service
[Unit]
Description=LED visualization
After=network.target

[Service]
Restart=always
RestartSec=10
PermissionsStartOnly=true
ExecStart=/path/to/script/dancyPi-audio-reactive-led/python/visualization.py scroll
ExecStopPost=/path/to/script/dancyPi-audio-reactive-led/python/off.py

[Install]
WantedBy=multi-user.target

And then your event script becomes:

#!/bin/bash
if [ "$PLAYER_EVENT" = "start" ]; then
  sudo systemctl start led-visualization.service
elif [ "$PLAYER_EVENT" = "stop" ]; then
  sudo systemctl stop led-visualization.service
else
  :
fi

You'll ofc have to test it and probably tweak it to make it actually work correctly. The above is just an idea.

JasonLG1979 avatar Nov 03 '20 08:11 JasonLG1979

Not to mention the fact that every time you get a play event you'd be starting another instance of visualization.py if there's nothing preventing multiple instances. The events are basically fire and forget. With the systemd service you'd only ever have one instance, trying to start an already running service in this case doesn't do anything. It just exits. Stopping an already stopped service does the same.

JasonLG1979 avatar Nov 03 '20 09:11 JasonLG1979

@JasonLG1979 - That's a great idea!

naztronaut avatar Nov 03 '20 15:11 naztronaut

That's a great idea!

To me anything like this that has a good chance of being ran unattended on a headless system screams system service.

JasonLG1979 avatar Nov 03 '20 16:11 JasonLG1979

@JasonLG1979, @naztronaut

Once again, awesome work! Big shoutout to you both for helping me with this. That's exactly the issue I was having -

Looking at the Raspotify logs, it seems like whenever a new song plays, it actually runs the [ "$PLAYER_EVENT" = "playing" ] twice - this created a problem since the visualization.py script fired twice in quick succession which really screwed with my LEDs.

That's why I limited myself to only using the start and stop events. But with this code from @JasonLG1979 , it looks like I can fine-tune it a little bit more and run the visuals when Spotify plays a song or when Spotify pauses/stops.

If I'm getting this correctly -if visualization.py is already running and a new event comes with value playing, it won't run the visual script again? Because that would be ideal.

I'll run these scripts when I get a chance and debug!

farhandsome avatar Nov 03 '20 16:11 farhandsome

If I'm getting this correctly -if visualization.py is already running and a new event comes with value playing, it won't run the visual script again? Because that would be ideal.

Yes. There will only ever be one instance of visualization.py.

JasonLG1979 avatar Nov 03 '20 16:11 JasonLG1979

It would be better though if the LED controller could be pointed at an output instead of a mic input. Or at least able to point to a mic other than default. If we can point it to a arbitrary input we can use snd-aloop to split the output and loop it to an input.

JasonLG1979 avatar Nov 03 '20 16:11 JasonLG1979

@naztronaut, is it possible to send the audio output to some file, and use that rather than a mic input?

farhandsome avatar Nov 03 '20 16:11 farhandsome

is it possible to send the audio output to some file, and use that rather than a mic input?

I'll look at the script and see if we can't just listen to a different capture device besides default. If we can (I don't really see why not?) a loopback device would be much better than a pipe file.

JasonLG1979 avatar Nov 03 '20 16:11 JasonLG1979

If you used a loopback you wouldn't have to worry about starting and stopping it. It could just always run and it would only react to what's playing.

Basically:

Whatever makes noise ---> duplicate the audio ---> one copy goes to the sound card / one copy goes to our virtual mic that the LED controller is listening to. The LED controller would have nothing to do with your actual real mic.

JasonLG1979 avatar Nov 03 '20 16:11 JasonLG1979

    # Start listening to live audio stream
    microphone.start_stream(microphone_update)

This is at the end of the visualization code - it looks like this stream is what feeds into the GUI curve? Feedback into the led.

Also @naztronaut, sorry for hijacking a thread!! Hope it's ok that we're dissecting your code.

farhandsome avatar Nov 03 '20 16:11 farhandsome

No worries! I wish I could take credit for all that code but only a few customizations are mine :) Most of the original code is by Scott Lawson.

naztronaut avatar Nov 03 '20 17:11 naztronaut

https://sysplay.in/blog/linux/2019/06/playing-with-alsa-loopback-devices/

This may be of use. An example of how to create a virtual card to capture audio from a different output.

Perhaps we can clone the USB DAC audio output and use that file for visualization?

farhandsome avatar Nov 05 '20 02:11 farhandsome


snd-aloop is the kernel module for setting up virtual audio loopback devices.

$ sudo modprobe snd-aloop
creates two devices 0 & 1 under a new “Loopback” card for both playback & capturing, as shown below, respectively:

If we can use the "capture" loopback, it could replace the mic input for the visualization.py structure?

farhandsome avatar Nov 05 '20 02:11 farhandsome

After some time of researching I finally managed to get it work. I'm running shairport-sync and the visualizer (besides a homebridge server) on my raspberry pi 2 model b feeding the audio from shairport in the visualizer and also my speaker using alsa. The visualization runs at 25 fps smoothly (oddly tho it stutters at lower or higher framerates). I have to say, that I don't power the LEDs by the raspberry pi itself, but let it do the computing and then sent the commands to an ESP-01 to light up the LEDs. So the visualizer is running in the ESP8266 configuration.

Here is what I did to use the audio output as an input (from a fresh raspbian buster install):

First set up your usb-audio card as the default device in Alsa as described in the readme. Check whether you can play sound from it.

Set up the alsa loopback device: sudo modprobe snd-aloop

it will show up in aplay -l as "Loopback"

Then edit your /etc/asound.conf (and maybe .asoundrc although I think it gets overridden by the global asound.conf) and add:

#this will set your default output to a virtual virtual multi-channel device named CardAndLoop, which outputs to your USB-Card as well as the Loopback device
#Furthermore it sets the second Loopback device as your standard input
pcm.!default {
  type asym
  playback.pcm "CardAndLoop"
  capture.pcm "hw:Loopback,1"
}

# This is the interface you use for sound output
# It will send the output to the soundcard and loopback device
pcm.CardAndLoop {
  type plug
  slave.pcm MultiCh
  route_policy "duplicate"
}

ctl.!default {
        type hw           
        card 0 #set it to your Audio-Card
}

# Virtual multichannel device with four channels
# two the for the soundcard, two for the loopback
pcm.MultiCh {
  type multi
  slaves.a.pcm pcm.MixCard
  slaves.a.channels 2
  slaves.b.pcm pcm.MixLoopback
  slaves.b.channels 2
  bindings.0.slave a
  bindings.0.channel 0
  bindings.1.slave a
  bindings.1.channel 1
  bindings.2.slave b
  bindings.2.channel 0
  bindings.3.slave b
  bindings.3.channel 1
}

# Mixer for the soundcard
pcm.MixCard {
  type dmix
  ipc_key 1024
  slave {
    pcm "hw:PCH,0" #edit to set it to your Audio Card
#    rate 48000
    rate 44100
    periods 128
    period_time 0
    period_size 1024 # must be power of 2
    buffer_size 8192
  }
}

# Mixer for the loopback
pcm.MixLoopback {
  type dmix
  ipc_key 1025
  slave {
    pcm "hw:Loopback,0"
#    rate 48000
    rate 44100
    periods 128
    period_time 0
    period_size 1024 # must be power of 2
    buffer_size 8192
  }
}

Remember to set your USB-Audio-Card as the pcm device in the Mixer for the soundcard. Read the first article below to learn how to address audio devices in Alsa.

As everything sent to hw:Loopback,0 is also played on hw:Loopback,1, you will now have your audio output also available as an input (and on the speaker).

You can check if everything is working by opening a second terminal window and playing sound to the default output while recording audio from the default input in the other window.

speaker-test -c2 in window 1 arecord -f S16_LE -c 2 -r 44000 audio.wav in window 2

If everything works, make the Loopback device persistent: sudo echo 'snd-aloop' >> /etc/modules

Now you can install shairport-sync (or use your desired audio input). Don't hesitate if pyaudio throws some errors. Most of them are only informative and everything should work nevertheless.

These articles helped a lot: https://sysplay.in/blog/linux/2019/06/playing-with-alsa-loopback-devices/ https://unix.stackexchange.com/questions/175649/send-sound-output-to-application-and-speaker https://www.alsa-project.org/wiki/Asoundrc

Good luck!

farhandsome avatar Nov 05 '20 02:11 farhandsome

@farhandsome Are you quoting someone with that last post because it's all inside block quotes, or did you end up getting it to work?

JasonLG1979 avatar Nov 05 '20 02:11 JasonLG1979

@JasonLG1979,

Nope just quoting someone. I'll be trying this out, but I may need some help in reconfiguring the asound.conf file so I can integrate this stuff with all the custom stuff that you've put into the file.

farhandsome avatar Nov 05 '20 02:11 farhandsome

Nope just quoting someone. I'll be trying this out, but I may need some help in reconfiguring the asound.conf file so I can integrate this stuff with all the custom stuff that you've put into the file.

I'll give it a shot. I played with it a little last night but so far no luck. I'll figure it out. But yes the general idea is for the loopback to replace the real mic so the LED listens to what comes out of your speakers not what comes in the mic. that way alexa is really the only thing that needs the mic.

JasonLG1979 avatar Nov 05 '20 03:11 JasonLG1979

It's not simple. There has to be 2 instances of dmix. One for the real sound card so you can hear stuff and one for the loopback. And then 2 instances of dsnoop one for the real mic and one for the lookback if you want more than one thing to be able to access the loopback. The idea too is to skip the output splitter for the alexa output so that it's not feed back it's own voice. There's also going to be LoopbackCapture and LoopbackPCM volume controls so you can adjust the loopback levels as to not saturate (distort) the loopback.

JasonLG1979 avatar Nov 05 '20 03:11 JasonLG1979

The idea too is to skip the output splitter for the alexa output so that it's not feed back it's own voice..<

Is there a way to utilize the Raspotify hooks to only capture onto the virtual card while Raspotify is playing?

farhandsome avatar Nov 05 '20 03:11 farhandsome

Is there a way to utilize the Raspotify hooks to only capture onto the virtual card while Raspotify is playing?

It's easier to just have alexa not go though the loopback at all.

JasonLG1979 avatar Nov 05 '20 03:11 JasonLG1979

ExecStart=/path/to/script/dancyPi-audio-reactive-led/python/visualization.py scroll ExecStopPost=/path/to/script/dancyPi-audio-reactive-led/python/off.py

Do I need to run this as python3 ?

farhandsome avatar Nov 05 '20 23:11 farhandsome

Nevermind. But for some reason the code isn't running... I get this error:

● led-visualization.service - LED visualization
   Loaded: loaded (/etc/systemd/system/led-visualization.service; enabled; vendor preset: enabled)
   Active: activating (auto-restart) (Result: exit-code) since Thu 2020-11-05 17:16:59 CST; 5s ago
  Process: 1273 ExecStart=/home/pi/dancyPi-audio-reactive-led/python/visualization.py scroll -c (code=exited, status=203/EXEC)
  Process: 1274 ExecStopPost=/home/pi/dancyPi-audio-reactive-led/python/off.py (code=exited, status=203/EXEC)
 Main PID: 1273 (code=exited, status=203/EXEC)

farhandsome avatar Nov 05 '20 23:11 farhandsome

@farhandsome I haven't forgot about you, getting all this loopback stuff to work was a PITA,lol!!!. I've finally got it working so that what comes out the speakers is duplicated as the default capture device so that the LED controls won't be going off your mic but what's playing, and alexa will be listening to the mic.

I have a couple questions for you:

Does anything else need access to the mic?

Does anything else need access to the loopback?

JasonLG1979 avatar Nov 07 '20 14:11 JasonLG1979

Wow awesome! Can’t wait to test it out.

No I don’t think so. Alexa is the only voice service I’ll be using and Spotify is the only audio I want to record.

farhandsome avatar Nov 07 '20 14:11 farhandsome

I ask because in particular with the loopback that the LED controls are listening to we can get rid of the dsnoop plugin that wraps it and drop the latency of the LEDs if nothing else is going to use it. I can get down to about a 125ms buffer size at 48k (so a latency of about 1/8 of a sec) before I get dropouts and audible glitches on my pi zero so you should have no problems getting down to that but ofc the less the better. You want the lights moving to the music not after the music,lol!!! I have no idea what kind of latency controller scripts add?

JasonLG1979 avatar Nov 07 '20 14:11 JasonLG1979

If it makes any difference, the mic is also a webcam which I’d like to mess with in the future.

farhandsome avatar Nov 07 '20 14:11 farhandsome

If it makes any difference, the mic is also a webcam which I’d like to mess with in the future.

No problem I can keep the dsnoop on the mic and just name the pcm webcam so you remember which input is the webcam. The input latency isn't a huge deal it's more an issue with the loopback so the lights are more in sync to the audio.

Give me just a sec to modify and test out the script and I'll post it.

JasonLG1979 avatar Nov 07 '20 14:11 JasonLG1979

Ok 1st if you've tested out any other my asound.conf's You have to jump though a hoop to get ALSA to forget the old software volume controls before you use this one.

sudo rm /var/lib/alsa/asound.state
sudo rm /etc/asound.conf
sudo chmod -x /usr/sbin/alsactl

Reboot

sudo chmod +x /usr/sbin/alsactl

Reboot again

And then copy and paste this into /etc/asound.conf:

# /etc/asound.conf

# use aplay -l to determine the below.

# Change to the card number or name that you want to be the default control card.
# Default: 0
defaults.ctl.card 1

# Change to the card number or name that you want to be the default playback card.
# It should usually be the same as defaults.ctl.card.
# Default: 0
defaults.pcm.card 1

# Change to the device number that you want to be the default device on the default card.
# 0 or 1 is usually the correct device number.
# Default: 0
defaults.pcm.device 0

# Change to the subdevice number that you want to be the default subdevice on the default device.
# Should rarely need to be changed.
# Default: -1
defaults.pcm.subdevice -1

# To install high quality samplerate converters on Debian based systems:
# sudo apt install -y --no-install-recommends libasound2-plugins 

# To list available rate converters:
# echo "$(ls /usr/lib/*/alsa-lib | grep "libasound_module_rate_")" | sed -e "s/^libasound_module_rate_//" -e "s/.so$//"

# Uncomment and replace speexrate_medium with the rate_converter of your choice. (speexrate and speexrate_medium offer the best "bang for your buck")
# defaults.pcm.rate_converter speexrate_medium

pcm.playback {
    type hw
    nonblock {
        @func refer
        name defaults.pcm.nonblock
    }
    card {
        @func refer
        name defaults.pcm.card
    }
    device {
        @func refer
        name defaults.pcm.device
    }
    subdevice {
        @func refer
        name defaults.pcm.subdevice
    }
}

pcm.capture {
    type hw
    nonblock {
        @func refer
        name defaults.pcm.nonblock
    }
    card 2
    device 0
    subdevice -1
}

pcm.loopback0 {
    type hw
    nonblock {
        @func refer
        name defaults.pcm.nonblock
    }
    card Loopback
    device 0
    subdevice -1
}

pcm.loopback1 {
    type hw
    nonblock {
        @func refer
        name defaults.pcm.nonblock
    }
    card Loopback
    device 1
    subdevice -1
}

pcm.playbackdmixer {
    type dmix
    ipc_key_add_uid false
    ipc_perm 0666
    ipc_key 1024
    slave {
        pcm playback
        channels 2
        rate 48000
        format S16_LE
        period_size 0
        buffer_size 0
        buffer_time 0
        period_time 31250
        periods 4
    }
    bindings {
        0 0
        1 1
    }
}

pcm.loopbackdmixer {
    type dmix
    ipc_key_add_uid false
    ipc_perm 0666
    ipc_key 1025
    slave {
        pcm loopback0
        channels 1
        rate 48000
        format S16_LE
        period_size 0
        buffer_size 0
        buffer_time 0
        period_time 31250
        periods 4
    }
    bindings {
        0 0
    }
}

pcm.webcam {
    type plug
    slave.pcm {
        type softvol
        # up to 20dB of mic boost.
        max_dB 20.0
        control {
            name Capture
            card {
                @func refer
                name defaults.ctl.card
            }
        }
        slave.pcm {
            type dsnoop
            ipc_key_add_uid false
            ipc_perm 0666
            ipc_key 1026
            slave {
                pcm capture
                channels 1
                rate 16000
                format S16_LE
                period_size 0
                buffer_size 0
                buffer_time 0
                period_time 31250
                periods 4
            }
            bindings {
                0 0
            }
        }
    }
}

pcm.loopbackdsnooper {
    type plug
    slave.pcm {
        type dsnoop
        ipc_key_add_uid false
        ipc_perm 0666
        ipc_key 1027
        slave {
            pcm loopback1
            channels 1
            rate 48000
            format S16_LE
            period_size 0
            buffer_size 0
            buffer_time 0
            period_time 31250
            periods 4
        }
        bindings {
            0 0
        }
    }
}

pcm.playbacksplitter {
    type plug
    slave.pcm {
        type multi
        slaves.playback.pcm playbackdmixer
        slaves.playback.channels 2
        slaves.loopback.pcm loopbackdmixer
        slaves.loopback.channels 1
        bindings.0.slave playback
        bindings.0.channel 0
        bindings.1.slave playback
        bindings.1.channel 1
        bindings.2.slave loopback
        bindings.2.channel 0
    }
    ttable {
        0.0 1
        1.1 1
        0.2 0.5
        1.2 0.5
    }
}

# Default input and output.
pcm.!default {
    type asym
    playback.pcm playbacksplitter
    capture.pcm loopback1
}

ctl.!default {
    type hw
    card {
        @func refer
        name defaults.ctl.card
    }
}

# Output only.
pcm.bluetooth {
    type softvol
    slave.pcm playbacksplitter
    control {
        name bluetooth
        card {
            @func refer
            name defaults.ctl.card
        }
    }
}

# Output only.
pcm.spotify {
    type softvol
    slave.pcm playbacksplitter
    control {
        name spotify
        card {
            @func refer
            name defaults.ctl.card
        }
    }
}

# Output and input.
pcm.alexa {
    type asym
    capture.pcm webcam
    playback.pcm {
        type softvol
        slave.pcm playbacksplitter
        control {
            name alexa
            card {
                @func refer
                name defaults.ctl.card
            }
        }
    }
}

As usual to get the software volume controls to show up on the inputs and outputs you have to use them at least once (aplay/arecord).

All the inputs will play whatever you throw at them and as many things as you'd like can listen to the webcam and alexa mic (they're the same thing) and they will output whatever they're asked for for as far as format and sampling rate. But the default default loopback mic can only be used by on thing at a time (the LED controls) and only outputs 1 channel mono (50% of the left and 50% of the right of what's playing over the speakers) at 16 bit 48k.

You'll need to add snd-aloop to /etc/modules to make sure the loopback module is loaded at boot. You'll also need to check to make sure to use the right card and device numbers. The config above assumes that loopback is 0 (it's named so you shouldn't need to change it) your sound card is 1 and your webcam is 2. that just happened to be the way they landed on my system.

JasonLG1979 avatar Nov 07 '20 15:11 JasonLG1979