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!