'Record speakers output with PyAudio

I'm trying to record the output from my computer speakers with PyAudio.
I tried to modify the code example given in the PyAudio documentation, but it doesn't work.

Technically, there's no error. I obtain the file output.wav and I can open it, but there's no sound. On Audacity, I can only see a straight line.

What's going wrong?

import pyaudio
import wave

CHUNK = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
WAVE_OUTPUT_FILENAME = "output.wav"

p = pyaudio.PyAudio()

SPEAKERS = p.get_default_output_device_info()["hostApi"] #The part I have modified

stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                frames_per_buffer=CHUNK,
                input_host_api_specific_stream_info=SPEAKERS) #The part I have modified

print("* recording")

frames = []

for i 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()

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()


Solution 1:[1]

In case someone is still stumbling over this like me, I found a PyAudio fork to record the output on windows.

Explanation:

The official PyAudio build isn't able to record the output. BUT with Windows Vista and above, a new API, WASAPI was introduced, which includes the ability to open a stream to an output device in loopback mode. In this mode the stream will behave like an input stream, with the ability to record the outgoing audio stream.

To set the mode, one has to set a special flag (AUDCLNT_STREAMFLAGS_LOOPBACK). Since this flag is not supported in the official build one needs to edit PortAudio as well as PyAudio, to add loopback support.

New option:

"as_loopback":(true|false)

Solution 2:[2]

I got to record my speaker output with pyaudio with some configuration and code from pyaudio's documentation.

Code

"""PyAudio example: Record a few seconds of audio and save to a WAVE file."""

import pyaudio
import wave

CHUNK = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
WAVE_OUTPUT_FILENAME = "output.wav"

p = pyaudio.PyAudio()

stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                frames_per_buffer=CHUNK)

print("* recording")

frames = []

for i 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()

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()

Configuration

First, with pulseaudio running, create a loopback device:

pacmd load-module module-loopback latency_msec=5

Then set the default (fallback) to this loopback device in pavucontrol:

Pavu control example

Then you can start the script, wait 5 seconds, and you should have an output.wav.

Solution 3:[3]

If you create an application on windows platform, you can use default stereo mixer virtual device to record your PC's output.

1) Enable stereo mixer.

2) Connect PyAudio to your stereo mixer, this way:

p = pyaudio.PyAudio()
stream = p.open(format = FORMAT,
                channels = CHANNELS,
                rate = RATE,
                input = True,
                input_device_index = dev_index,
                frames_per_buffer = CHUNK)

where dev_index is an index of your stereo mixer.

3) List your devices to get required index:

for i in range(p.get_device_count()):
    print(p.get_device_info_by_index(i))

Alternatively, you can automatically get index by device name:

for i in range(p.get_device_count()):
    dev = p.get_device_info_by_index(i)
    if (dev['name'] == 'Stereo Mix (Realtek(R) Audio)' and dev['hostApi'] == 0):
        dev_index = dev['index'];
        print('dev_index', dev_index)

4) Continue to work with pyAudio as in the case of recording from a microphone:

data = stream.read(CHUNK)

Solution 4:[4]

You can't record from an output stream as though it were input. To record, you need to connect PyAudio to an input device, like a microphone. At least that's the normal way to do things.

Try connecting to a microphone first, and see if you get anything. If this works, then try doing something unusual.

As a small speedup to your iterations, rather than recording and looking at the file, it's often easier just to print out the max for a few chunks to make sure you're bringing in data. Usually just watching the numbers scroll by and comparing them to the sound gives a quick estimate of whether things are correctly connected.

import audioop
mx = audioop.max(data, 2)
print mx

Solution 5:[5]

The speaker is an output stream even if you open it as an input. The hostApi value of the speaker is probably 0. You can check the 'maxInputChannels' and 'maxOutputChannels' of every connected devices and the maxInputChannels for the speaker shall be 0. You can't write to an input stream and you can't read from an output stream.

You can detect the available devices with the following code:

import pyaudio 

# detect devices:
p = pyaudio.PyAudio()
host_info = p.get_host_api_info_by_index(0)    
device_count = host_info.get('deviceCount')
devices = []

# iterate between devices:
for i in range(0, device_count):
    device = p.get_device_info_by_host_api_device_index(0, i)
    devices.append(device['name'])

print devices

After you get all the connected devices you can check the 'hostApi' of each devices. For instance if the speaker index is 5 than:

p.get_device_info_by_host_api_device_index(0, 5)['hostApi']

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 showdev
Solution 2 antoineMoPa
Solution 3 Michael McMahon
Solution 4
Solution 5 Timo Herngreen