25

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()
showdev
  • 28,454
  • 37
  • 55
  • 73
Openclosed
  • 251
  • 1
  • 3
  • 3

5 Answers5

24

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)

showdev
  • 28,454
  • 37
  • 55
  • 73
mate
  • 339
  • 2
  • 10
  • 1
    How about Linux side? any information about it? – ati ince Jun 29 '21 at 13:27
  • 3
    For the next person (2022 update): TL;DR: Use [PyAudioWPatch](https://github.com/s0d3s/PyAudioWPatch) instead. This pyaudio fork appears to be actively developed, and explicitly supports this use case. There's even a [complete example](https://github.com/s0d3s/PyAudioWPatch/blob/master/examples/pawp_record_wasapi_loopback.py) in the repo. – Kent Nov 30 '22 at 04:30
17

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)
macost
  • 171
  • 1
  • 5
  • This worked perfectly for me. If you don't have Stereo Mixer on your PC you can get the codec here: https://www.realtek.com/en/component/zoo/category/pc-audio-codecs-high-definition-audio-codecs-software. It wont rout the audio through your headphones, you have to have your audio output set to your speakers. – SixenseMan Nov 14 '21 at 22:36
14

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.

antoineMoPa
  • 854
  • 10
  • 20
  • This is the most helpful answer for Linux users (if using Pulseaudio). I myself run Ubuntu (14.04/18.04) and Linux Mint (17.3/19.3) with Pulseaudio and JACK and this works. You can even edit `/etc/pulse/default.pa` and include a line like the above (without the `pacmd`) to make it persistent. – Moonbase Jan 05 '20 at 11:15
  • Will this work on the Raspberry Pi? I have some audio running on VLC and I want to send that audio to an FFT algorithm and check out the frequencies. – Souvik Saha May 22 '20 at 04:24
  • I am not 100% sure, you have to try to know! In your case, there is also the (easier) option to open the audio file directly in Python instead of trying to record it indirectly from VLC. Look into PyAudio methods to open audio files. – antoineMoPa May 22 '20 at 13:28
2

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
tom10
  • 67,082
  • 10
  • 127
  • 137
1

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']
Timo Herngreen
  • 112
  • 1
  • 9