Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature for device selection #64

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,20 @@ For validation, run::

$ export BITRATE=48 # explicitly select high MODEM bit rate (assuming good SNR).
$ amodem -h
usage: amodem [-h] {send,recv} ...
usage: amodem [-h] {send,recv,lsdev} ...

Audio OFDM MODEM: 48.0 kb/s (64-QAM x 8 carriers) Fs=32.0 kHz
Audio OFDM MODEM v1.15.4: 48.0 kb/s (64-QAM x 8 carriers) Fs=32.0 kHz

positional arguments:
{send,recv}
send modulate binary data into audio signal.
recv demodulate audio signal into binary data.
{send,recv,lsdev}
send modulate binary data into audio signal.
recv demodulate audio signal into binary data.
lsdev list all devices. (portaudio only)

optional arguments:
-h, --help show this help message and exit
-h, --help show this help message and exit

On, Windows you may download the `portaudio` library from `MinGW <https://packages.msys2.org/base/mingw-w64-portaudio>`_.
On Windows you may download the `portaudio` library from `MinGW <https://packages.msys2.org/base/mingw-w64-portaudio>`_.
Then, you should specify the DLL using the following command-line flag::

-l AUDIO_LIBRARY, --audio-library AUDIO_LIBRARY
Expand Down Expand Up @@ -136,6 +137,21 @@ and send me the resulting ``audio.raw`` file for debugging::

You can see a screencast of the `calibration process <https://asciinema.org/a/25065?autoplay=1>`_.

Devices
-------
To specify an input/output device other than default, set the following environment variables::

~/sender $ export OUTAUDIODEVICE=2 # Set output device using device id.
~/sender $ export INAUDIODEVICE=2 # Set input device using device id.

To list input/output devices, use the following command::

~/sender $ amodem lsdev

To see details of a specific i/o device, specify the device id using ``-d`` as shown below::

~/sender $ amodem lsdev -d 2

Usage
-----

Expand Down
33 changes: 30 additions & 3 deletions amodem/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from . import audio
from . import calib
from . import main
from . import lsdev
from .config import bitrates


Expand All @@ -25,6 +26,9 @@
bitrate = os.environ.get('BITRATE', 1)
config = bitrates.get(int(bitrate))

input_device = os.environ.get('INAUDIODEVICE')
output_device = os.environ.get('OUTAUDIODEVICE')


class Compressor:
def __init__(self, stream):
Expand Down Expand Up @@ -71,10 +75,10 @@ def opener(fname):
if fname is None:
assert audio_interface is not None
if 'r' in mode:
s = audio_interface.recorder()
s = audio_interface.recorder(int(input_device))
return async_reader.AsyncReader(stream=s, bufsize=s.bufsize)
if 'w' in mode:
return audio_interface.player()
return audio_interface.player(int(output_device))

if fname == '-':
if 'r' in mode:
Expand Down Expand Up @@ -177,6 +181,24 @@ def create_parser(description, interface_factory):
g.add_argument('-v', '--verbose', default=0, action='count')
g.add_argument('-q', '--quiet', default=False, action='store_true')

device_lister = subparsers.add_parser(
'lsdev', help='list all devices. (portaudio only)')
device_lister.add_argument(
'-d', '--device', help='get details of a single device')
device_lister.add_argument(
'-l', '--audio-library', default='libportaudio.so',
help='File name of PortAudio shared library.')
device_lister.set_defaults(
input_type=FileType('rb'),
output_type=FileType('wb'),
input=None,
output=None,
verbose=0,
quiet=False,
calibrate=False,
command='lsdev'
)

if argcomplete:
argcomplete.autocomplete(p)

Expand Down Expand Up @@ -251,7 +273,12 @@ def interface_factory():
args.src = args.input_type(args.input)
args.dst = args.output_type(args.output)
try:
if args.calibrate is False:
if args.command == 'lsdev':
if args.device:
lsdev.getParticularDevice(interface, int(args.device))
else:
lsdev.getDevices(interface)
elif args.calibrate is False:
args.main(config=config, args=args)
else:
args.calib(config=config, args=args)
Expand Down
27 changes: 18 additions & 9 deletions amodem/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,15 @@ def __exit__(self, *args):
s.close()
self.call('Terminate')

def recorder(self):
return Stream(self, config=self.config, read=True)
def recorder(self, preferred_device=None):
return Stream(
self, config=self.config, read=True,
preferred_device=preferred_device)

def player(self):
return Stream(self, config=self.config, write=True)
def player(self, preferred_device=None):
return Stream(
self, config=self.config, write=True,
preferred_device=preferred_device)


class Stream:
Expand All @@ -66,7 +70,9 @@ class Parameters(ctypes.Structure):
('hostApiSpecificStreamInfo', ctypes.POINTER(None))
]

def __init__(self, interface, config, read=False, write=False):
def __init__(
self, interface, config,
read=False, write=False, preferred_device=None):
self.interface = interface
self.stream = ctypes.POINTER(ctypes.c_void_p)()
self.user_data = ctypes.c_void_p(None)
Expand All @@ -80,11 +86,14 @@ def __init__(self, interface, config, read=False, write=False):
write = bool(write)
assert read != write # don't support full duplex

direction = 'Input' if read else 'Output'
api_name = f'GetDefault{direction}Device'
index = interface.call(api_name, restype=ctypes.c_int)
# use default device if preferred_device is undefined
if preferred_device is None:
direction = 'Input' if read else 'Output'
api_name = f'GetDefault{direction}Device'
preferred_device = interface.call(api_name, restype=ctypes.c_int)

self.params = Stream.Parameters(
device=index, # choose default device
device=preferred_device,
channelCount=1, # mono audio
sampleFormat=0x00000008, # 16-bit samples (paInt16)
suggestedLatency=self.latency,
Expand Down
39 changes: 39 additions & 0 deletions amodem/lsdev.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from ctypes import *


class PADeviceInfo(Structure):
_fields_ = [
("structVersion", c_int),
("name", c_char_p),
("hostApi", c_int),
("maxInputChannels", c_int),
("maxOutputChannels", c_int),
("defaultLowInputLatency", c_double),
("defaultLowOutputLatency", c_double),
("defaultHighInputLatency", c_double),
("defaultHighOutputLatency", c_double),
("defaultSampleRate", c_double)
]


def printDeviceDetails(device):
contents = device.contents
print(str(contents.name, 'ascii'))
print(f"Input Channels: {contents.maxInputChannels}")
print(f"Output Channels: {contents.maxOutputChannels}")
print(f"Default Sample Rate: {contents.defaultSampleRate}")


def getDevices(interface):
num_devices = interface.call('GetDeviceCount', restype=c_int)
for i in range(num_devices):
device_i = interface.call(
'GetDeviceInfo', i, restype=POINTER(PADeviceInfo))
device_str = str(device_i.contents.name, 'ascii')
print(f"Device #{i}: {device_str}")


def getParticularDevice(interface, device_id):
device = interface.call(
'GetDeviceInfo', device_id, restype=POINTER(PADeviceInfo))
printDeviceDetails(device)
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from setuptools import setup
from setuptools.command.test import test as TestCommand


class PyTest(TestCommand):

def finalize_options(self):
Expand All @@ -13,6 +14,7 @@ def run_tests(self):
import pytest
sys.exit(pytest.main(['.']))


setup(
name='amodem',
version='1.15.4',
Expand Down