Capture 192 kHz audio using Python 3
Asked Answered
K

1

12

I need to capture 192 kHz audio for some bioacoustics experiments using Python 3. I have the hardware, a Sound Devices USBPre 2 sound card, a microphone with good frequency response curve up to 100 kHz, and I have enabled my os (ubuntu 13.04) to sample from this card at 192 kHz.

I have tried recording with PyAudio. It appears to work, and will give me a wav file with a sampling rate of 192 kHz. However, when I look at the spectra there is no power above 24 kHz, suggesting that PyAudio doesn't really capture at 192 kHz, but rather at 48 kHz. However, when I record using Audacity with input from JACK, I get a nice recording with power up to 96kHz. So, I have the impression that PyAudio doesn't actually sample the sound at 192 kHz even though it should be able to. How to fix this?

I start JACK without errors:

/usr/bin/jackd -R -dalsa -Chw:1,0 -n3 -o1 -p2048 -r192000

jackd 0.122.0
Copyright 2001-2009 Paul Davis, Stephane Letz, Jack O'Quinn, Torben Hohn and others.
jackd comes with ABSOLUTELY NO WARRANTY
This is free software, and you are welcome to redistribute it
under certain conditions; see the file COPYING for details

JACK compiled with System V SHM support.
loading driver ..
apparent rate = 192000
creating alsa driver ... -|hw:1,0|2048|3|192000|0|1|nomon|swmeter|-|32bit
control device hw:0
configuring for 192000Hz, period = 2048 frames (10.7 ms), buffer = 3 periods
ALSA: final selected sample format for capture: 24bit little-endian
ALSA: use 3 periods for capture

Initialize PyAudio (without any real errors (as far as I can tell)):

p = pyaudio.PyAudio()
ALSA lib pcm.c:2217:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2217:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2217:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
bt_audio_service_open: connect() failed: Connection refused (111)
bt_audio_service_open: connect() failed: Connection refused (111)
bt_audio_service_open: connect() failed: Connection refused (111)
bt_audio_service_open: connect() failed: Connection refused (111)
ALSA lib pcm_dmix.c:957:(snd_pcm_dmix_open) The dmix plugin supports only playback stream

Open a PyAudio stream:

stream = p.open(format=pyaudio.paInt32,
                channels=1,rate=192000,
                input=True,
                frames_per_buffer=2048)

I have images of the spectrograms, in case someone wants to verify my interpretation that PyAudio doesn't capture at 192 kHz (but Audacity does):

Spectrogram of sound captured usig PyAudio Spectrogram of sound captured usig PyAudio

Spectrogram of sound captured usig Audacity Spectrogram of sound captured usig Audacity

How can I record sound at 192 000 samples/s using PyAudio? Suggestions of other ways to capture sound using Python 3 are also welcome.

Koestler answered 30/10, 2013 at 19:20 Comment(8)
Unfortunately I didn't ever encounter such an issue, and currently don't have the equipment to do any tests. But one thing I'd try is to check if the sample rate is considered "supported" by PyAudio for the recording device you're using.Kwangtung
There seems to be some problems with the support. p.get_device_info_by_index(2) tells me that the defaultSampleRate is 44100, and maxInputChannels is 0. When I try p.is_format_supported it returns an error (but not when I check the built in sound card).Koestler
@Koestler from your code it looks like you didn't specify a device index when you open your stream. I'd try p.open(..., input_device_index=dev_idx), and you make sure you use the right one, or loop over them to try them all. Same for gathering device info.Kwangtung
When I open with p.open(..., input_device_index=dev_idx) I get the following error OSError: [Errno Invalid number of channels] -9998, similar to what I get when try p.is_format_supported(...): ValueError: ('Invalid number of channels', -9998).Koestler
I also got that while trying to get PyAudio to work on OS X just now. That, and [Errno Input overflowed] -9981. I managed to probe for the right configuration (got it working just now) by looping over various sample rates, channel numbers and device indexes. I'll try to pretty up the code a bit and come up with an "answer", maybe it'll be of some help to you.Kwangtung
This is just a hunch, but... Have to tried using pyaudio.paFloat32 instead of pyaudio.paInt32 as your format? (Still cleaning up my code)Kwangtung
Tried, but no. Same error: OSError: [Errno Invalid number of channels] -9998Koestler
let us continue this discussion in chatKwangtung
K
6

This is not meant to be a conclusive answer, but rather an attempt to help you maybe track down the issue yourself.

When trying to reproduce your issue with PyAudio on OS X, I always kept running into [Errno Input overflowed] -9981 (like several other people, it seems). Also configurations that p.is_format_supported() reported as OK caused these errors. So I whipped up a script that just tries to record with all the possible permutations of the recording settings.

This script probes all permutations of lists of devices, sample rates, formats and channels, in a defensive way, and saves the results to files named according to the recording settings.

import os
import pyaudio
import sys

# === These parameters will be permuted ===========
DEVICES = [0, 1, 2]
RATES = [44100, 48000, 192000]
FORMATS = ['Float32', 'Int32', 'Int24', 'Int16', 'Int8', 'UInt8']
CHANNELS = [1, 2]
# =================================================

CHUNK = 1024
COLUMNS = (('filename', 30),
           ('result', 9),
           ('dev', 5),
           ('rate', 8),
           ('format', 9),
           ('channels', 10),
           ('chunk', 7),
           ('reason', 0))
STATUS_MSG = "Recording... "

pa = pyaudio.PyAudio()


def get_format(format):
    fmt = getattr(pyaudio, 'pa%s' % format)
    return fmt


def record(filename=None,
           duration=5,
           dev=0,
           rate=44100,
           format='Float32',
           channels=2,
           chunk=1024,):
    """Record `duration` seconds of audio from the device with index `dev`.
    Store the result in a file named according to recording settings.
    """
    if filename is None:
        filename = "dev{dev}-{rate}-{format}-{channels}ch.raw".format(**locals())
    result = 'FAILURE'
    reason = ''

    outfile = open(filename, 'w')
    print STATUS_MSG,
    sys.stdout.flush()

    try:
        stream = pa.open(input_device_index=dev,
                         rate=rate,
                         format=get_format(format),
                         channels=channels,
                         frames_per_buffer=chunk,
                         input=True,
                         )

        try:
            for i in range(0, rate / (chunk) * duration):
                a = stream.read(chunk)
                outfile.write(a)
            result = 'SUCCESS'
        # Catch exceptions when trying to read from stream
        except Exception, e:
            reason = "'%s'" % e
    # Catch exceptions when trying to even open the stream
    except Exception, e:
        reason = "'%s'" % e

    outfile.close()

    # Don't leave files behind for unsuccessful attempts
    if result == 'FAILURE':
        os.remove(filename)
        filename = ''

    info = {}
    for col_name, width in COLUMNS:
        info[col_name] = str(locals()[col_name]).ljust(width)

    msg = "{filename}{result}{dev}{rate}{format}{channels}{chunk}{reason}"
    print msg.format(**info)

def main():
    # Build the header line
    header = 'STATUS'.ljust(len(STATUS_MSG) + 1)
    for col_name, width in COLUMNS:
        header += col_name.upper().ljust(width)
    print header
    print "=" * len(header)

    # Record samples for all permutations of our parameter lists
    for dev in DEVICES:
        for rate in RATES:
            for format in FORMATS:
                for channels in CHANNELS:
                    record(duration=2,
                           dev=dev,
                           rate=rate,
                           format=format,
                           channels=channels,
                           chunk=CHUNK)

if __name__ == '__main__':
    main()

Sample output (simplified):

STATUS        FILENAME                      RESULT   DEV  RATE    FORMAT   CHANNELS  CHUNK  REASON
==================================================================================================
Recording...  dev0-44100-Float32-1ch.raw    SUCCESS  0    44100   Float32  1         1024
Recording...  dev0-44100-Float32-2ch.raw    SUCCESS  0    44100   Float32  2         1024
Recording...  dev0-44100-Int16-1ch.raw      SUCCESS  0    44100   Int16    1         1024
Recording...  dev0-44100-Int16-2ch.raw      SUCCESS  0    44100   Int16    2         1024
Recording...                                FAILURE  0    192000  Float32  1         1024   '[Errno Input overflowed] -9981'
Recording...                                FAILURE  0    192000  Float32  2         1024   '[Errno Input overflowed] -9981'
Recording...                                FAILURE  0    192000  Int16    1         1024   '[Errno Input overflowed] -9981'
Recording...                                FAILURE  0    192000  Int16    2         1024   '[Errno Input overflowed] -9981'
Recording...  dev1-44100-Float32-1ch.raw    SUCCESS  1    44100   Float32  1         1024
Recording...  dev1-44100-Float32-2ch.raw    SUCCESS  1    44100   Float32  2         1024
Recording...  dev1-44100-Int16-1ch.raw      SUCCESS  1    44100   Int16    1         1024
Recording...  dev1-44100-Int16-2ch.raw      SUCCESS  1    44100   Int16    2         1024
Recording...                                FAILURE  1    192000  Float32  1         1024   '[Errno Input overflowed] -9981'
Recording...                                FAILURE  1    192000  Float32  2         1024   '[Errno Input overflowed] -9981'
Recording...                                FAILURE  1    192000  Int16    1         1024   '[Errno Input overflowed] -9981'
Recording...                                FAILURE  1    192000  Int16    2         1024   '[Errno Input overflowed] -9981'
Recording...                                FAILURE  2    44100   Float32  1         1024   '[Errno Invalid number of channels] -9998'
Recording...                                FAILURE  2    44100   Float32  2         1024   '[Errno Invalid number of channels] -9998'
Recording...                                FAILURE  2    44100   Int16    1         1024   '[Errno Invalid number of channels] -9998'
Recording...                                FAILURE  2    44100   Int16    2         1024   '[Errno Invalid number of channels] -9998'
Recording...                                FAILURE  2    192000  Float32  1         1024   '[Errno Invalid number of channels] -9998'
Recording...                                FAILURE  2    192000  Float32  2         1024   '[Errno Invalid number of channels] -9998'
Recording...                                FAILURE  2    192000  Int16    1         1024   '[Errno Invalid number of channels] -9998'
Recording...                                FAILURE  2    192000  Int16    2         1024   '[Errno Invalid number of channels] -9998'
Kwangtung answered 30/10, 2013 at 23:14 Comment(3)
I tried you script w some minor modification (added 11 to DEVICES). pa.get_device_info_by_index(11) looks better today (parts of output): ... 'defaultSampleRate': 192000.0, 'hostApi': 2, 'index': 11, 'maxInputChannels': 2, 'maxOutputChannels': 1,.... pa.get_host_api_info_by_index(2) gives this: {'defaultInputDevice': 11, 'defaultOutputDevice': 11, 'deviceCount': 1, 'index': 2, 'name': 'JACK Audio Connection Kit', 'structVersion': 1, 'type': 12}.Koestler
I can even open a stream: stream = pa.open(input_device_index=11,rate=192000,format=4,channels=1,frames_per_buffer=2048,input=True), and occasionally read a chunk: stream.read(1024), but most of the time it crashes python: python3: malloc.c:2369: sysmalloc: ... Aborted (core dumped)Koestler
For opening a stream, it seem that I just needed to find the correct input_device_index (which can vary), and then match the parameter values with those returned when starting JACK (rate = 192000, format = paInt24). But, the crash that comes with reading a chunk has made me give up. I will probably record with Audacity instead. Thanks a lot to Lukas for all help.Koestler

© 2022 - 2024 — McMap. All rights reserved.