PulseAudioDB
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
Anthony Wang 2024-02-25 19:49:32 -05:00
parent 060256369b
commit 32560ebd9d
Signed by: a
SSH key fingerprint: SHA256:B5ADfMCqd2M7d/jtXDoihAV/yfXOAbWWri9+GdCN4hQ
2 changed files with 88 additions and 0 deletions

View file

@ -0,0 +1,60 @@
---
title: "PulseAudioDB"
date: 2024-02-25T16:42:10-05:00
description: "Anything can be a key-value database if you misuse it well enough!"
type: "post"
tags: ["database", "fun", "audio", "satire"]
---
[A friend of mine](https://www.1a-insec.net) shared a surprising fact: The Linux kernel has a built-in key-value database!
```
keyctl add user key value @u
keyctl list @u
keyctl read <ID>
```
Actually, `keyctl` is intended for managing the kernel keyring, not as a general purpose key-value database. That made me wonder: What other key-value databases are hiding right in plain sight? What else could we use as a database? Ideally, it should be something that's fast, simple-to-use, and preinstalled in your favorite Linux distro.
Oh, I know! PulseAudio!
Yeah, we're going to turn a sound server into a database now. After all, who's stopping us?
The idea is simple. Each key-value pair will be stored as its own sink device. The key will be the name of the sink, and the value will be the volume of the sink. Due to the brilliance of the PulseAudio developers, volumes in PulseAudio don't range between 0 to 100 but rather 0 to 2^31-1 in case you ever want to blast out your ears or your speakers. Seriously, I don't recommend turning up the volume to 2147483647. It's not fun. Anyways, our PulseAudioDB will only be able to store integer keys, which honestly is good enough.
The implementation is also super straightforward. Here's some Python code, but it's trivial to port this to any language.
```python
class PulseAudioDB:
def __getitem__(self, key):
try:
return int(subprocess.check_output(['pactl', 'get-sink-volume', key]).split()[2])
except:
raise KeyError
def __setitem__(self, key, value):
run = lambda x: subprocess.run(x, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if run(['pactl', 'set-sink-volume', key, str(value)]).returncode:
# Key doesn't exist yet
run(['pactl', 'load-module', 'module-null-sink', 'channels=1', 'sink_name=' + key])
run(['pactl', 'set-sink-volume', key, str(value)])
```
Now let's try our amazing database!
```python
padb = PulseAudioDB()
N = 200
for i in range(N):
padb['a' + str(i)] = i
ans = 0
for i in range(N):
ans += padb['a' + str(i)]
print(ans)
```
This code is blazingly fast, prints the correct answer of 19900, and only takes 15.14 seconds to run for an amazing 200 reads and 200 writes. Seriously, I can't imagine myself reading or writing something that fast! To delete all the sinks and reset the database, just run `pactl unload-module module-null-sink`.
PulseAudio doesn't seem to support more than 250 sinks, but who even has that much data? 200 key-value pairs should be enough for anyone, just like how 640K of RAM ought to be enough for anyone. If you're ever in need of a fast, simple database, look no further than PulseAudio!
By the way, if you enjoyed this post, you'll also like [MangoDB](https://github.com/dcramer/mangodb).

28
static/src/padb.py Normal file
View file

@ -0,0 +1,28 @@
import subprocess
class PulseAudioDB:
def __getitem__(self, key):
try:
return int(subprocess.check_output(['pactl', 'get-sink-volume', key]).split()[2])
except:
raise KeyError
def __setitem__(self, key, value):
run = lambda x: subprocess.run(x, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if run(['pactl', 'set-sink-volume', key, str(value)]).returncode:
# Key doesn't exist yet
run(['pactl', 'load-module', 'module-null-sink', 'channels=1', 'sink_name=' + key])
run(['pactl', 'set-sink-volume', key, str(value)])
padb = PulseAudioDB()
N = 200
for i in range(N):
padb['a' + str(i)] = i
ans = 0
for i in range(N):
ans += padb['a' + str(i)]
print(ans)