use pyalsaaudio to connect an input and output device

78 Views Asked by At

I'm facing an issue with my Python audio streaming code. Currently, when running the code, I can only hear a "tuuuut" sound on the Bluetooth audio speaker. I expected the audio from the input sound device to be properly streamed to the speaker.

The magic happens in the function _process_audio and I am not able to figure out, how to enter the data from capture to the playback.

Does anybody has an idea what is wrong and how to improve it?


EDIT:

I found out, that if I just record it to a wav and play it with aplay, it's the same.

on the bash I can do it like this

arecord -D hw:0,0 -f cd -r 44100 -c 2 | aplay -D bluealsa 

and it works!

Why not with my python solution??


import alsaaudio
import multiprocessing


class AudioProcessor:
    def __init__(
        self,
        playback_device="bluealsa",
        capture_device="hw:0,0",
        channels=2,
        rate=44100,
        format=alsaaudio.PCM_FORMAT_S16_LE,
        mode=alsaaudio.PCM_NONBLOCK,
        buffer_size=128,
    ):
        """
        Initializes the AudioProcessor class.

        Args:
            playback_device (str): The playback device name.
            capture_device (str): The capture device name.
            channels (int): The number of audio channels.
            rate (int): The audio sample rate.
            format (int): The audio sample format.
            mode (int): The audio mode.
            buffer_size (int): The audio buffer size.
        """
        # Set up the capture (record) device
        self.capture = alsaaudio.PCM(
            type=alsaaudio.PCM_CAPTURE,
            device=capture_device,
            format=format,
            rate=rate,
            channels=channels,
            periodsize=buffer_size,
            mode=mode,
        )

        # Set up the playback device
        self.playback = alsaaudio.PCM(
            type=alsaaudio.PCM_PLAYBACK,
            device=playback_device,
            format=format,
            rate=rate,
            channels=channels,
            periodsize=buffer_size,
            mode=mode,
        )

        # Flag for graceful termination
        self.terminate_flag = multiprocessing.Event()

    def _process_audio(self):
        """
        Continuously processes audio data from the capture device and plays it back.

        This method is intended to be run in a separate process.
        """
        while not self.terminate_flag.is_set():
            try:
                # Capture audio
                # _, input_data = self.capture.read()

                # Playback audio
                self.playback.write(self.capture.read()[1])
            except alsaaudio.ALSAAudioError as e:
                print(f"Error in audio processing: {e}")

    def start_playback(self):
        """Starts the audio playback process"""
        self.playback_process = multiprocessing.Process(
            target=self._process_audio, name="playback_process"
        )
        self.playback_process.start()

    def close_streams(self):
        """
        Closes the audio streams and terminates the playback process.

        This method should be called to gracefully stop audio processing.
        """
        self.terminate_flag.set()
        self.playback_process.join()  # Wait for the process to finish
        with self.playback, self.capture:
            pass
1

There are 1 best solutions below

0
theother On

here is my solution. I added a multiprocessing queue and starts the capture a half second before the playback. So I can use the PCM_NORMAL mode. It works fine!

import alsaaudio
import multiprocessing
import time


class AudioProcessor:
    def __init__(
        self,
        playback_device="bluealsa",
        capture_device="hw:0,0",
        channels=2,
        rate=48000,
        format=alsaaudio.PCM_FORMAT_S32_LE,
        mode=alsaaudio.PCM_NORMAL,
        buffer_size=8192,
    ):
        """
        Initializes the AudioProcessor class.

        Args:
            playback_device (str): The playback device name.
            capture_device (str): The capture device name.
            channels (int): The number of audio channels.
            rate (int): The audio sample rate.
            format (int): The audio sample format.
            mode (int): The audio mode.
            buffer_size (int): The audio buffer size.
        """
        # Set up the capture (record) device
        self.capture = alsaaudio.PCM(
            type=alsaaudio.PCM_CAPTURE,
            device=capture_device,
            format=format,
            rate=rate,
            channels=channels,
            periodsize=buffer_size,
            mode=mode,
        )

        # Set up the playback device
        self.playback = alsaaudio.PCM(
            type=alsaaudio.PCM_PLAYBACK,
            device=playback_device,
            format=format,
            rate=rate,
            channels=channels,
            periodsize=buffer_size,
            mode=mode,
        )

        # Flag for graceful termination
        self.terminate_flag = multiprocessing.Event()

        # define a fifo queue for the audio data
        self.audio_queue = multiprocessing.Queue()

    def _read_audio(self):
        """
        Continuously processes audio data from the capture device.
        This method is intended to be run in a separate process.
        """
        while not self.terminate_flag.is_set():
            try:
                self.audio_queue.put(self.capture.read()[1])
            except alsaaudio.ALSAAudioError as e:
                print(f"Error in audio reading: {e}")

    def _write_audio(self):
        """
        Continuously processes audio data to the playback device.
        This method is intended to be run in a separate process.
        """
        # wait for the queue to be filled
        time.sleep(0.5)
        while not self.terminate_flag.is_set():
            try:
                self.playback.write(self.audio_queue.get())
            except alsaaudio.ALSAAudioError as e:
                print(f"Error in audio writing: {e}")

    def start_playback(self):
        """Starts the capture and playback processes"""
        # Start the capture
        self.capture_process = multiprocessing.Process(
            target=self._read_audio, name="capture_process"
        )
        self.capture_process.start()

        # Start the playback
        self.playback_process = multiprocessing.Process(
            target=self._write_audio, name="playback_process"
        )
        self.playback_process.start()

    def close_streams(self):
        """
        Closes the audio streams and terminates the playback process.

        This method should be called to gracefully stop audio processing.
        """
        self.terminate_flag.set()
        time.sleep(1)
        if self.capture_process:
            self.capture_process.terminate()
        if self.playback_process:
            self.playback_process.terminate()
        self.playback.close()
        self.capture.close()