python-sounddevice
python-sounddevice copied to clipboard
how to use channel mapping when in 'Recording with Arbitrary Duration'?
I connect with a fireface sound card. The example using Inputstream object has no mapping as input parameter?
I suppose stream does not allow to do a more sophisticated mapping. But you can get that with some small modifications, for example (there is certainly nicer ways to code it):
...
rec_channels = [0, 2, 3] # channel IDs starting at 0
def callback(indata, frames, time, status):
"""This is called (from a separate thread) for each audio block."""
if status:
print(status, file=sys.stderr)
q.put(indata[:, rec_channels].copy())
...
with sf.SoundFile(args.filename, mode='x', samplerate=args.samplerate,
channels=len(rec_channels), subtype=args.subtype) as file:
...
@HaHeho already suggested a plaform-independent method. Something very similar is also used in the implementation of the rec()
function.
Depending on your OS and host API you might also be able to use a platform-specific channel mapping, see https://python-sounddevice.readthedocs.io/en/0.4.2/api/platform-specific-settings.html.
I suppose stream does not allow to do a more sophisticated mapping. But you can get that with some small modifications, for example (there is certainly nicer ways to code it):
... rec_channels = [0, 2, 3] # channel IDs starting at 0 def callback(indata, frames, time, status): """This is called (from a separate thread) for each audio block.""" if status: print(status, file=sys.stderr) q.put(indata[:, rec_channels].copy()) ... with sf.SoundFile(args.filename, mode='x', samplerate=args.samplerate, channels=len(rec_channels), subtype=args.subtype) as file: ...
thanks for your reply. A question here is when using rec() or playrec() the mapping list can be either channel which could start any number ,eg.[5,6], and this works normally, but this is can not be used in your code when mapping is [5,6] and raise "index 5 is out of bounds for axis 1 with size 2".
@HaHeho already suggested a plaform-independent method. Something very similar is also used in the implementation of the
rec()
function.Depending on your OS and host API you might also be able to use a platform-specific channel mapping, see https://python-sounddevice.readthedocs.io/en/0.4.2/api/platform-specific-settings.html.
thks!it is quite different when i use rec() function and Inputstream() object with extra settings. The former can record normally with dual channel. and the later can only get mono wav.
channels = len(mapping)
sd_in = sd.CoreAudioSettings(channel_map=mapping)
# sd.default.extra_settings = sd_in
q = queue.Queue()
def audio_callback(indata, frames, time, status):
if status:
print(status, file=sys.stderr)
q.put(indata.copy())
try:
filename_tmp = 'tmp.wav'
with sf.SoundFile(filename_tmp, mode='x', samplerate=fs, channels=channels) as file:
with sd.InputStream(samplerate=fs, channels=channels, extra_settings=sd_in,
callback=audio_callback, device=self.deviceOnlineDetection(soundcard='Fireface', verbose=False)):
print('#' * 80)
print('press Ctrl+C to stop the recording')
print('#' * 80)
while True:
file.write(q.get())
except KeyboardInterrupt:
if filename is None:
filename = '{}.wav'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
os.rename(filename_tmp, filename)
# os.remove(filename_tmp)
print('\nRecording finished: ' + repr(filename))`
thanks for your reply. A question here is when using rec() or playrec() the mapping list can be either channel which could start any number ,eg.[5,6], and this works normally, but this is can not be used in your code when mapping is [5,6] and raise "index 5 is out of bounds for axis 1 with size 2".
Yes, the provided number of channels for InputStream
still has to be max() + 1
. I have changed the argparse channels
parameter, so this can be used by python rec.py rec.wav -c 0 2 3
:
#!/usr/bin/env python3
"""Create a recording with arbitrary duration.
The soundfile module (https://PySoundFile.readthedocs.io/) has to be installed!
"""
import argparse
import tempfile
import queue
import sys
import sounddevice as sd
import soundfile as sf
import numpy # Make sure NumPy is loaded before it is used in the callback
assert numpy # avoid "imported but unused" message (W0611)
def int_or_str(text):
"""Helper function for argument parsing."""
try:
return int(text)
except ValueError:
return text
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument(
"-l",
"--list-devices",
action="store_true",
help="show list of audio devices and exit",
)
args, remaining = parser.parse_known_args()
if args.list_devices:
print(sd.query_devices())
parser.exit(0)
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter,
parents=[parser],
)
parser.add_argument(
"filename", nargs="?", metavar="FILENAME", help="audio file to store recording to"
)
parser.add_argument(
"-d", "--device", type=int_or_str, help="input device (numeric ID or substring)"
)
parser.add_argument("-r", "--samplerate", type=int, help="sampling rate")
parser.add_argument(
"-c", "--channels", nargs="+", type=int, default=0, help="list of input channels"
)
parser.add_argument(
"-t", "--subtype", type=str, help='sound file subtype (e.g. "PCM_24")'
)
args = parser.parse_args(remaining)
q = queue.Queue()
def callback(indata, frames, time, status):
"""This is called (from a separate thread) for each audio block."""
if status:
print(status, file=sys.stderr)
q.put(indata[:, args.channels].copy())
try:
if args.samplerate is None:
device_info = sd.query_devices(args.device, "input")
# soundfile expects an int, sounddevice provides a float:
args.samplerate = int(device_info["default_samplerate"])
if args.filename is None:
args.filename = tempfile.mkstemp(
prefix="delme_rec_unlimited_", suffix=".wav", dir=""
)
# Make sure the file is opened before recording anything:
with sf.SoundFile(
args.filename,
mode="x",
samplerate=args.samplerate,
channels=len(args.channels),
subtype=args.subtype,
) as file:
with sd.InputStream(
samplerate=args.samplerate,
device=args.device,
channels=max(args.channels) + 1,
callback=callback,
):
print("#" * 80)
print("press Ctrl+C to stop the recording")
print("#" * 80)
while True:
file.write(q.get())
except KeyboardInterrupt:
print("\nRecording finished: " + repr(args.filename))
parser.exit(0)
except Exception as e:
parser.exit(type(e).__name__ + ": " + str(e))
thanks for your reply. A question here is when using rec() or playrec() the mapping list can be either channel which could start any number ,eg.[5,6], and this works normally, but this is can not be used in your code when mapping is [5,6] and raise "index 5 is out of bounds for axis 1 with size 2".
Yes, the provided number of channels for
InputStream
still has to bemax() + 1
. I have changed the argparsechannels
parameter, so this can be used bypython rec.py rec.wav -c 0 2 3
:#!/usr/bin/env python3 """Create a recording with arbitrary duration. The soundfile module (https://PySoundFile.readthedocs.io/) has to be installed! """ import argparse import tempfile import queue import sys import sounddevice as sd import soundfile as sf import numpy # Make sure NumPy is loaded before it is used in the callback assert numpy # avoid "imported but unused" message (W0611) def int_or_str(text): """Helper function for argument parsing.""" try: return int(text) except ValueError: return text parser = argparse.ArgumentParser(add_help=False) parser.add_argument( "-l", "--list-devices", action="store_true", help="show list of audio devices and exit", ) args, remaining = parser.parse_known_args() if args.list_devices: print(sd.query_devices()) parser.exit(0) parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter, parents=[parser], ) parser.add_argument( "filename", nargs="?", metavar="FILENAME", help="audio file to store recording to" ) parser.add_argument( "-d", "--device", type=int_or_str, help="input device (numeric ID or substring)" ) parser.add_argument("-r", "--samplerate", type=int, help="sampling rate") parser.add_argument( "-c", "--channels", nargs="+", type=int, default=0, help="list of input channels" ) parser.add_argument( "-t", "--subtype", type=str, help='sound file subtype (e.g. "PCM_24")' ) args = parser.parse_args(remaining) q = queue.Queue() def callback(indata, frames, time, status): """This is called (from a separate thread) for each audio block.""" if status: print(status, file=sys.stderr) q.put(indata[:, args.channels].copy()) try: if args.samplerate is None: device_info = sd.query_devices(args.device, "input") # soundfile expects an int, sounddevice provides a float: args.samplerate = int(device_info["default_samplerate"]) if args.filename is None: args.filename = tempfile.mkstemp( prefix="delme_rec_unlimited_", suffix=".wav", dir="" ) # Make sure the file is opened before recording anything: with sf.SoundFile( args.filename, mode="x", samplerate=args.samplerate, channels=len(args.channels), subtype=args.subtype, ) as file: with sd.InputStream( samplerate=args.samplerate, device=args.device, channels=max(args.channels) + 1, callback=callback, ): print("#" * 80) print("press Ctrl+C to stop the recording") print("#" * 80) while True: file.write(q.get()) except KeyboardInterrupt: print("\nRecording finished: " + repr(args.filename)) parser.exit(0) except Exception as e: parser.exit(type(e).__name__ + ": " + str(e))
no error raise, but get an empty wav file... I dont know why.
With no command line parameters I get this error:
TypeError: object of type 'int' has no len()
See plot_input.py for an example how to use a list of channels.
With a command line argument of -c 1
I get this error:
TypeError: No format specified and unable to get format from file extension: (3, '[...]/delme_rec_unlimited_a7j6kaie.wav')
This gives a hint about the problem: mkstemp()
returns a tuple.