Comprehensive Technical Guide to the Linux Audio Stack¶
This guide provides a detailed overview of the Linux audio stack, practical examples, code snippets, and bash scripting techniques for audio device management and audio processing on Linux systems.
Table of Contents¶
- Overview of Linux Audio Stack
- System Components and Flow Diagram
- Detailed I/O Flow View
- Practical Audio Device Management: Bash Commands
- ALSA Configuration Files
- Using PyAudio for Audio Capture and Playback
- Using Python sounddevice Library
- Bash Script to Set Default ALSA Device
- Troubleshooting and Useful Tips
Overview of Linux Audio Stack¶
Linux audio architecture is layered and modular. Key components involved in processing audio from applications to hardware include:
1. Applications¶
Programs generating or capturing sound (e.g., media players, browsers, audio recorders).
2. Audio Servers and Sound Systems¶
Middleware that mixes audio streams, manages routing, and provides APIs:
- PipeWire (modern default in many distros including Raspberry Pi OS Bookworm): Low-latency multimedia server.
- PulseAudio (legacy): Software mixer, routing, and device manager.
- JACK: Low-latency server mostly for professional audio.
3. ALSA (Advanced Linux Sound Architecture)¶
- Core kernel sound subsystem.
- Provides drivers for sound hardware.
- Contains libraries for user access (
libasound). - Manages sound cards and audio device nodes.
4. Kernel Sound Drivers¶
- Hardware interface drivers within Linux kernel.
- Communicates with sound cards.
5. Sound Cards / Audio Hardware¶
System Components and Flow Diagram¶
graph LR
Apps[Applications]
AudioSrv["Audio Server (PipeWire / PulseAudio / JACK)"]
ALSA[ALSA: Kernel Modules & Libasound]
Kernel[Linux Kernel Drivers]
Hardware[Sound Card / Audio HW]
Apps --> AudioSrv
AudioSrv --> ALSA
ALSA --> Kernel
Kernel --> Hardware
Detailed I/O Flow View¶
graph LR
%% Detailed I/O Flow View
subgraph DetailedIO["Linux Audio Stack - Detailed I/O Flow"]
AppsDet[Applications]
PyAudioWrapper[PyAudio Wrapper Lib]
SdWrapper[SoundDevice Wrapper Lib]
AudioSrvDet["Audio Server (PipeWire etc.)"]
ALSADriver[ALSA Kernel Driver]
KernelDet[Linux Kernel]
HWDet["Sound Card / Audio Hardware"]
%% Detailed Flow
AppsDet -->|Audio API| PyAudioWrapper
AppsDet -->|Audio API| SdWrapper
PyAudioWrapper -->|PulseAudio compatibility layer or directly| AudioSrvDet
SdWrapper -->|ALSA or PipeWire| AudioSrvDet
AudioSrvDet --> ALSADriver
ALSADriver --> KernelDet
KernelDet --> HWDet
end
Practical Audio Device Management: Bash Commands¶
List Sound Cards and Devices¶
# List sound cards
cat /proc/asound/cards
# List PCM devices for playback or capture
aplay -l # playback devices
arecord -l # capture devices
# List all PCM device names and aliases
aplay -L
arecord -L
Display ALSA Device Files¶
Display PCI Audio Devices¶
Script: List all Devices with Info¶
#!/bin/bash
echo "Sound Cards:"
cat /proc/asound/cards
echo -e "\nPlayback Devices:"
aplay -l
echo -e "\nCapture Devices:"
arecord -l
Save as list_audio_devices.sh, make executable (chmod +x list_audio_devices.sh) and run (./list_audio_devices.sh).
ALSA Configuration Files¶
- User config:
~/.asoundrc - System config:
/etc/asound.conf
Example ALSA config snippet: Default device setup¶
This sets the default ALSA PCM and control devices to sound card 0, device 0.
Using PyAudio for Audio Capture and Playback¶
Blocking Mode Example¶
import pyaudio
import wave
def blocking_audio_record_play():
CHUNK = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
WAVE_OUTPUT_FILENAME = "output_blocking.wav"
p = pyaudio.PyAudio()
stream = p.open(format=FORMAT, channels=CHANNELS,
rate=RATE, input=True,
frames_per_buffer=CHUNK)
print("* Recording...")
frames = []
for _ in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
data = stream.read(CHUNK)
frames.append(data)
print("* Done recording")
stream.stop_stream()
stream.close()
p.terminate()
# Write to WAV
wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
wf.setnchannels(CHANNELS)
wf.setsampwidth(p.get_sample_size(FORMAT))
wf.setframerate(RATE)
wf.writeframes(b''.join(frames))
wf.close()
if __name__ == "__main__":
blocking_audio_record_play()
Callback Mode Example¶
import pyaudio
import wave
import time
def callback_audio_record_play():
CHUNK = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
WAVE_OUTPUT_FILENAME = "output_callback.wav"
p = pyaudio.PyAudio()
frames = []
def callback(in_data, frame_count, time_info, status):
frames.append(in_data)
return (in_data, pyaudio.paContinue)
stream = p.open(format=FORMAT, channels=CHANNELS,
rate=RATE, input=True, output=True,
frames_per_buffer=CHUNK,
stream_callback=callback)
stream.start_stream()
print("* Recording with callback...")
try:
while stream.is_active():
time.sleep(0.1)
except KeyboardInterrupt:
print("\n* Stopped recording.")
stream.stop_stream()
stream.close()
p.terminate()
wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
wf.setnchannels(CHANNELS)
wf.setsampwidth(p.get_sample_size(FORMAT))
wf.setframerate(RATE)
wf.writeframes(b''.join(frames))
wf.close()
if __name__ == "__main__":
callback_audio_record_play()
Using Python sounddevice Library¶
Blocking Mode Example¶
import sounddevice as sd
import numpy as np
import wave
def blocking_record():
RATE = 44100
CHANNELS = 2
DURATION = 5
WAVE_OUTPUT_FILENAME = "sounddevice_blocking.wav"
print("* Recording blocking...")
recording = sd.rec(int(DURATION * RATE), samplerate=RATE, channels=CHANNELS, dtype='int16')
sd.wait()
print("* Done recording")
with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf:
wf.setnchannels(CHANNELS)
wf.setsampwidth(2) # int16 = 2 bytes
wf.setframerate(RATE)
wf.writeframes(recording.tobytes())
if __name__ == "__main__":
blocking_record()
Callback Mode Example¶
import sounddevice as sd
import numpy as np
import wave
import time
def callback_record_play():
RATE = 44100
CHANNELS = 2
BLOCKSIZE = 1024
WAVE_OUTPUT_FILENAME = "sounddevice_callback.wav"
frames = []
def callback(indata, outdata, frames_count, time_info, status):
if status:
print(status)
outdata[:] = indata
frames.append(indata.copy())
with sd.Stream(samplerate=RATE, blocksize=BLOCKSIZE,
channels=CHANNELS, dtype='int16',
callback=callback):
print("* Recording and playing (callback mode). Press Ctrl+C to stop.")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\n* Stopped recording.")
audio_data = np.concatenate(frames, axis=0)
with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf:
wf.setnchannels(CHANNELS)
wf.setsampwidth(2)
wf.setframerate(RATE)
wf.writeframes(audio_data.tobytes())
if __name__ == "__main__":
callback_record_play()
Bash Script to Set Default ALSA Device¶
#!/bin/bash
# Usage: ./set_default_alsa.sh <card_number>
if [ -z "$1" ]; then
echo "Usage: $0 card_number"
exit 1
fi
CARD=$1
cat << EOF > ~/.asoundrc
pcm.!default {
type hw
card $CARD
}
ctl.!default {
type hw
card $CARD
}
EOF
echo "ALSA default device set to card $CARD"
Troubleshooting and Useful Tips¶
- List ALSA devices to verify changes with
aplay -lorarecord -l. - Verify PipeWire or PulseAudio status with
systemctl --user status pipewireorpulseaudio. - Use
pactlorpw-clito query PulseAudio and PipeWire devices. - Test recording/playback via command line:
arecord -d 5 test.wavandaplay test.wav. - Use environment variable
AUDIODEVor ALSA config to override device selection. - Check
/proc/asound/cardsand/proc/asound/modulesfor hardware info.
This guide covers the essentials of Linux audio stack, from kernel to user applications, with practical examples and scripting for daily audio management and development. If more granular or advanced topics are needed (e.g., PipeWire config, JACK usage), those can be expanded separately.