Thursday, 2 January 2025

Installing mplayer on dietpi on Raspberry Pi

In a previous article, I explained how to use mplayer to record Internet radio broadcast non-interactively.

I got it working on my workhorse PC, but it suffered from using 100% of one core as mentioned in that article. I have 12 cores so this wasn't a disaster. But I thought I could shove the job to a less important computer and also have a backup means of recording. I have a very old Raspberry Pi 2B which was idle.

I tried installing the latest Raspberry Pi OS, but I couldn't write the entire image on the micro SD card. I think the USB to micro SD adaptor got too hot and caused sector errors towards the end. Maybe I should have limited the writing rate. Anyway I decided to use a lighter RPi distro: dietpi.

This installed and booted up fine. I had an issue with the default NTP server until I specified a regional pool. Then I encountered a series of problems:

Apt repos need to be signed

It couldn't update from the default Debian Bookworm archive because the signing key wasn't present. Normally this is provided by the distro but since this is dietpi they only provided keys for the repos they used. Or they provided an old key.

Cut to the chase: Install all the relevant Bookworm repos, you can find a definitive list of them at at various sites. If possible use a local mirror for the repos. Don't forget bookworm-security, but this will come from security.debian.org, not a mirror.

When you do an apt update it will complain about various unsigned repos. Note down the key fingerprints for the next stage.

Apt-key is deprecated

Ignore any tutorials that talk about using apt-key to install the required keys. For security reasons, apt-key is deprecated. The new way of doing it is:

Download the GPG keys for all the repos missing keys. You'll need to find a suitable keyserver with the Debian keys.

Feed each through gpg to dearmor the keys and write the output to a suitably named file in /etc/apt/trusted.gpg.d/ Here's what I have:

root@DietPi:/etc/apt/trusted.gpg.d# ls
bookworm-security.gpg       debian-bookworm-archive.gpg  dietpi.asc
bullseye-security.gpg       debian-bookworm-stable.gpg   raspberrypi-archive-stable.gpg
deb-multimedia-keyring.asc  debian-bullseye-archive.gpg  raspbian-archive-keyring.gpg

I haven't given the details to avoid duplication and because I could have forgotten some bits. You can find them in up-to-date tutorials.

You need Deb multimedia

Mplayer uses some codecs that are not supplied in Debian, so you have to get them from Deb Multimedia. Use a mirror if you can. You need to install the signing key for this in the same manner shown above.

Finally install mplayer

Do an apt update and then apt install mplayer. If you have any issues at the update step, fix those. I came across issues like mirrors no longer existing, or didn't specify their Debian repo domain in their list of alternate domain names which caused failure on verification.

Also any other packages with problems could block the installation. For example I had issues with libgomp1 where it had a spurious dependency. I actually hacked /var/lib/dpkg/status with a text editor to bypass this.

What made this exercise worthwhile

When I use mplayer on the RPi to record an Internet radio station I was surprised to find that it didn't eat up 100% of a core like on my workstation. So I ran a strace on mplayer on my workstation and saw that it was looping on reading file descriptor 0 (stdin). Recalling that mplayer reads single keystrokes from the controlling terminal to control the playback, I reasoned that it must be doing that in non-interactive mode and looping on failure. So I found the -noconsolecontrols and -slave options to mplayer and adding these to the command made the CPU usage normal again.

Recording Internet radio with mplayer

It's not widely known, but mplayer can be used to listen to Internet radio stations.

If you just want to listen, my recommendation is to install pyradio which is a curses based command player. For a widget I can recommend radiotray-ng. For Plasma desktops there is plasma5-radiotray. They use mplayer and other programs like vlc to do the heavy lifting.

But the subject of this blog article is recording, and I usually do this from a cronjob or crontab entry for unattended recording of periodic programs. Cutting to the chase, this is the command line you need, with explanations below.

mplayer -prefer-ipv4 -noconsolecontrols -slave -vo null -vc null -endpos "$1" -dumpaudio -dumpfile "$2" -really-quiet -profile pyradio "$stream"

-prefer-ipv4 is because I have a DNS client that returns IPv6 entries but I have only IPv4 connectivity

-noconsolecontrols -slave prevent mplayer from reading for commands and polling for single keypresses for commands. I think only the first is needed, but the second can't hurt. In non-interactive mode, there is no terminal and mplayer goes into a busy loop, using up 100% of a CPU core

-vo null -vc null disable the video output and codecs

-endpos is followed by the number of seconds to record. It's the first argument to the shell script this command is in

-dumpaudio -dumpfile are followed by the file to write the raw audio data to, typically it's AAC format. It's the second argument to the shell script

-really-quiet suppresses pretty much all messages

-profile pyradio specifies a profile in ~/.mplayer/config. It consists of this stanza:

[pyradio]
softvol=1
softvol-max=300
volstep=1
volume=80

"$stream" is the URL the station broadcasts on. A site like https://streamurl.link/ could be useful for finding this for the station you are interested in.

Some stations use a playlist URL, in which case "$stream" should be replaced by -playlist "$playlist"

I've found that typically the audio data is about 8 kB/s for AAC.

Wednesday, 26 June 2024

An interesting anomaly in my car player re MP3 and AAC

I discovered by accident that my 10-year old car's entertainment system can accept .aac suffixed files on USB flash memory sticks to play. But when I tried to play an ISO9660 data CD containing AAC files instead of MP3 files, it said it could not find any MP3 files on the CD.

Since it's the same entertainment unit which also accepts input from Bluetooth, and analog AUX 3.5 mm stereo jack for a total of 4 input sources, it seems strange that it can handle AAC files, but only from the USB flash memory.

I tried naming the files suffixed as .m4a. No joy, still could not find any MP3 files on the CD.

Ok, I'll try to fool it. I renamed the AAC files to have .mp3 suffix. Now it doesn't complain that there are no MP3 files, but regards them as invalid, skipping through them without playing.

From this I infer that there are at least two decoder paths, the one for the CD drive that can only play MP3 files, and the one for the USB flash memory that can play both MP3 and AAC.

Incidentally I think this might be the last car player I own that will play CDs. For my next car I'll probably play from USB flash memory, or from my phone via Bluetooth. These days when you mention CDs to people below a certain age, they go: what?

Saturday, 6 January 2024

Clever function names

It came back to me today that in the language that influenced Python, ABC, developed at the CWI to teach programming, there were 2 string operations described as behead and curtail. I suppose the person who thought up the verbs was chuffed. They were probably too bloody for general consumption so these days in programming languages other verbs are used to describe the operations, or means like string slicing are used.

Sunday, 5 November 2023

Excluding devices from pipewire control

In my previous blog post I described how I used pipewire to add a bluetooth dongle to the audio outputs of my computer.

Unfortunately activating pipewire had the result of it taking control of all the sound interfaces on my computer. This caused a problem with a line-in port which I use for recording audio at scheduled times. It turns out that now and then pipewire will reset the port for maximum gain, wrecking the recording.

I searched for how to exclude this port from pipewire's influence and this was the best answer. I discovered the PCI bus id of the sound card (actually onboard sound device) in question and wrote an override script just like the one described with my card's id. Unfortunately the whole device has to be disabled, not just the line-in port, but that's ok for me. I still have an HDMI port which I can connect to my amplifier instead of using the analog line-out port. So thanks to the author of that ZenLinux blog for the insight. It's a pity such a simple task has to be so complicated, maybe future wireplumber developments will make this simpler.

Monday, 11 September 2023

Getting a bluetooth capable amplifier working with a bluetooth USB dongle on Linux

A while back I bought one of those $10 tiny class D amplifiers that can drive my bookshelf speakers with up to 30W (depending on power supply voltage, between 12V and 24V). An old laptop supply supplies this. It can be fed from a 3.5 audio socket or from bluetooth. Works like a charm. May not be audiophile but good enough for me.

I've been feeding it from an older mobile phone that mounts a SMB share from my workhorse computer. But as I would like to automate it so that I can use music to wake me up, I bought a bluetooth USB dongle from one of many AliExpress sellers. It contains a Realtek chipset that I checked is supported in Linux and supports BT 5.3 profile.

How to set the dongle up? Plugging it in elicited messages in the syslog, as dmesg showed. I used the KDE desktop widget (probably bluedevil) to connect to it and command it to pair and connect to amplifier. A prerequisite is that bluetoothd should be running. I discoved that the configuration can also be done from the CLI using bluez-tools. But once discovered and paired, nothing more needs to be done on this front as the setup is static.

How to send audio to it? I thought there might be a PCM device under /dev like for sound cards. But no. It seems that you need a service like pulseaudio to drive it. So I got stuck into pulseaudio documentation. Along the way I discovered that my distro prefers pipewire. So I installed the pipewire packages and this also obsoleted the corresponding pulseaudio packages. One of the packages pipewire-pulseaudio supplies compatible functionality.

Pipewire is a process that should keep running to handle all this. How to fire it off? It turns out that it's preferable to run it with user permissions, using the user instance of systemd, e.g.

systemctl --user start pipewire.service

How to make sure it runs when the user logs in? You activate the systemd units like this:

systemctl --user enable pipewire.service pipewire.socket

Then start both the service and the socket the first time.

How to send audio to pipewire? I struggled with this under pulseaudio, it wouldn't show up in players like vlc or amarok. But once I switched to pipewire, the bluetooth audio sink was just there and I could control the volume and make it the default audio playback.

How to specify this device to mplayer so that I can play from a cron job? I just had to specify it to the -ao option.

mplayer -ao pulse audio.mp3

Notice that mplayer sees pipewire the same as pulseaudio. I can put this in a profile in .mplayer/config named after the amplifier ID:

[xinyi]
ao=pulse

then I can specify:

mplayer -profile xinyi audio.mp3

The advantage is that I can specify other settings in the profile.

Wednesday, 6 September 2023

Pay attention to the size of ipsets for nftables

I was preparing an ipset for blocking access by geographical region using firewalld. Firewalld can work with either the traditional iptables or the newer nftables.

I created the ipset file using this Makefile stanza:

%.load: ipset_%.txt
        -firewall-cmd --permanent --delete-ipset=$(@:.load=)
        firewall-cmd --permanent --new-ipset=$(@:.load=) --type=hash:net
        firewall-cmd --permanent --ipset=$(@:.load=) --add-entries-from-file=$<
        systemctl reload firewalld

Here % is replaced by the two letter country code for the region I want to allow.

In the public zone I have a rich rule which inverts the test. This is the result of the firewall-cmd I issued:

  <rule family="ipv4">
    <source ipset="ok" invert="True"/>
    <service name="theservice"/>
    <log/>
    <drop/>
  </rule>

Later I discovered that that the countries not in ok weren't blocked. Troubleshooting found these symptoms:

  • firewall-cmd --state returned failed, despite systemctl stating that the service had started
  • There was a message about python-nftables failing in systemd logs
  • nft list showed nothing in the chains or rulesets

Searching on these symptoms got hits but none of the solutions worked. The only thing I learnt is that firewalld calls the python3-nftables routines to manipulate the nftables. I thought it might be the top line in the Python script which read #!/usr/bin/python which invokes python2 on my system, but adding 3 made no difference presumably because firewalld calls it with /usr/bin/python3 <name of script>. Someone suggested this python3 module was broken in a particular distro release because it didn't include a needed flag so he went back to iptables as the backend. I didn't want to do this. Also the report was for a previous distro release so over a year old.

Playing around with ipset which deals directly with the kernel to create the sets manually revealed that the default maxelem of 65336 was not sufficient for my set. After some experimentation I figured that I had to increase this parameter at creation. This boiled down to including this final option on the firewall-cmd line:

firewall-cmd --permanent --new-ipset=$(@:.load=) --type=hash:net --option=maxelem=262144

Now restarting firewalld took a bit longer due to having to digest lots of entries, and there was no more failure of python-nftables. Inspecting the ruleset using nft worked and showed that the nftables rule was in place.

It would have been easier if I had understood the relationship of all the components of this setup better, but things are obvious in hindsight.