0

I found this code below and apparently it could solve my problem but how can I modify to observe the speaker device rather than mic? And also appears to me that audioop lib is deprecated in pynthon 3.11, what can be an alternative for it?

When I run this code I can't handle the in_data buffer because it is always empty same defing the output device before.

import pyaudio
import time
from math import log10
import audioop  

p = pyaudio.PyAudio()
WIDTH = 2
RATE = int(p.get_default_input_device_info()['defaultSampleRate'])
DEVICE = p.get_default_input_device_info()['index']
rms = 1
print(p.get_default_input_device_info())

def callback(in_data, frame_count, time_info, status):
    global rms
    rms = audioop.rms(in_data, WIDTH) / 32767
    return in_data, pyaudio.paContinue


stream = p.open(format=p.get_format_from_width(WIDTH),
                input_device_index=DEVICE,
                channels=1,
                rate=RATE,
                input=True,
                output=False,
                stream_callback=callback)

stream.start_stream()

while stream.is_active(): 
    db = 20 * log10(rms)
    print(f"RMS: {rms} DB: {db}") 
    # refresh every 0.3 seconds 
    time.sleep(0.3)

stream.stop_stream()
stream.close()

p.terminate()

1 Answer 1

0

If you want to observe the output speakers device instead of the microphone, you need to switch the inputs and outputs and use get_default_output_device_info() method for pyaudio instance.

Regarding your question about audioop in Python 3.11, you might want to consider scipy library as an alternative. The scipy.io.wavfile module can read and write WAV files, and it's more likely to be kept up to date and compatible with future Python versions.

Here is the modified code using speaker device and scipy library instead of audioop:

import pyaudio
import time
import numpy as np
from math import log10
from scipy.io import wavfile

p = pyaudio.PyAudio()
WIDTH = 2
RATE = int(p.get_default_output_device_info()['defaultSampleRate'])
DEVICE = p.get_default_output_device_info()['index']
rms = 1
print(p.get_default_output_device_info())

def callback(in_data, frame_count, time_info, status):
    global rms
    # convert byte data to numpy array
    in_data_ = np.frombuffer(in_data, dtype=np.int16)
    # calculate rms
    rms = np.sqrt(np.mean(np.square(in_data_))) / 32767
    return in_data, pyaudio.paContinue

stream = p.open(format=p.get_format_from_width(WIDTH),
                output_device_index=DEVICE,
                channels=1,
                rate=RATE,
                input=False,
                output=True,
                stream_callback=callback)

stream.start_stream()

while stream.is_active(): 
    db = 20 * log10(max(rms, 1e-5))  # to avoid log0 error, use max(rms, 1e-5)
    print(f"RMS: {rms} DB: {db}") 
    # refresh every 0.3 seconds 
    time.sleep(0.3)

stream.stop_stream()
stream.close()

p.terminate()

Remember that this change will only work if the default output device allows capturing the audio. Some output devices might not support this due to the digital rights management concerns. It's also worth mentioning that this code might not be working on some operating systems like Windows due to system limitations.

Please also keep in mind that the audio captured from speakers will not be what is actually played on the speakers. It will be the audio that is sent to the speakers. Once the audio is played on the speakers, it might have some changes due to hardware or software on the audio path not controlled by pyaudio.

Edit:

While typically, you might consider switching the input=True to output=True instead, that wouldn't work here as it seems you cannot directly fetch output from a speaker using pyaudio. The issue you're seeing mostly stems from a general limitation within computer audio stack where capturing of speaker outputs is not straightforwardly permitted for DRM (Digital Rights Management) concerns.

A common workaround for this is to create a "virtual" microphone that listens to your system sounds. You would then change get_default_input_device_info to point to this virtual device. The details of creating a virtual microphone would depend on your OS.

For Windows, you can use applications like "Stereo Mix", "VB-Cable", or even "VoiceMeeter Banana".

For MacOS, you could look at "BlackHole", "Loopback", or "Soundflower".

For Linux, look into "Jack Audio" or "PulseAudio" configurations.

Once you have one of these set up, you should be able to capture the speaker output without generating a feedback loop.

Then in your PyAudio setup, you would set 'input = True' and point to the correct device index of the virtual microphone. This should capture the output of your speakers without the undesireable reverberation caused by the physical microphone. In applications where 'output = True', it might not be able to "listen" to the device, instead it is most likely "writing" to the device, which does not help if you're trying to measure the output.

I'm afraid without the ability to capture speaker output directly via PyAudio, this workaround would be your best bet!

2
  • Hi, best resgards! So, yeterday i had see another problem. The code works just when I put the flag: input=True but generate undesirable reverberation and noises from mic. If i remove or set to False the input parameter from stream = p.open() method it throws a exception. There is an alternative to this? thanks.
    – wmendes
    Commented Aug 1, 2023 at 15:02
  • @wmendes update the answer
    – Rapidmod
    Commented Aug 2, 2023 at 22:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.