-
Notifications
You must be signed in to change notification settings - Fork 881
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add test for sound device lib * add trace for missing portaudio * optimize imports * add test for pyaudio * initial soundcard implementation with health check * enable rx for soundcard * enable tx for soundcard * make sample rate configurable * python 3.4 compat
- Loading branch information
Showing
7 changed files
with
254 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
from collections import OrderedDict | ||
from multiprocessing import Array | ||
from multiprocessing.connection import Connection | ||
|
||
import numpy as np | ||
import pyaudio | ||
|
||
from urh.dev.native.Device import Device | ||
from urh.util.Logger import logger | ||
|
||
|
||
class SoundCard(Device): | ||
DEVICE_LIB = pyaudio | ||
ASYNCHRONOUS = False | ||
DEVICE_METHODS = dict() | ||
|
||
CHUNK_SIZE = 1024 | ||
SYNC_TX_CHUNK_SIZE = 2 * CHUNK_SIZE | ||
|
||
SAMPLE_RATE = 48000 | ||
|
||
pyaudio_handle = None | ||
pyaudio_stream = None | ||
|
||
@classmethod | ||
def init_device(cls, ctrl_connection: Connection, is_tx: bool, parameters: OrderedDict) -> bool: | ||
try: | ||
cls.SAMPLE_RATE = int(parameters[cls.Command.SET_SAMPLE_RATE.name]) | ||
except (KeyError, ValueError): | ||
pass | ||
return super().init_device(ctrl_connection, is_tx, parameters) | ||
|
||
@classmethod | ||
def setup_device(cls, ctrl_connection: Connection, device_identifier): | ||
ctrl_connection.send("Initializing pyaudio...") | ||
try: | ||
cls.pyaudio_handle = pyaudio.PyAudio() | ||
ctrl_connection.send("Initialized pyaudio") | ||
return True | ||
except Exception as e: | ||
logger.exception(e) | ||
ctrl_connection.send("Failed to initialize pyaudio") | ||
|
||
@classmethod | ||
def prepare_sync_receive(cls, ctrl_connection: Connection): | ||
try: | ||
cls.pyaudio_stream = cls.pyaudio_handle.open(format=pyaudio.paFloat32, | ||
channels=2, | ||
rate=cls.SAMPLE_RATE, | ||
input=True, | ||
frames_per_buffer=cls.CHUNK_SIZE) | ||
ctrl_connection.send("Successfully started pyaudio stream") | ||
return 0 | ||
except Exception as e: | ||
logger.exception(e) | ||
ctrl_connection.send("Failed to start pyaudio stream") | ||
|
||
@classmethod | ||
def prepare_sync_send(cls, ctrl_connection: Connection): | ||
try: | ||
cls.pyaudio_stream = cls.pyaudio_handle.open(format=pyaudio.paFloat32, | ||
channels=2, | ||
rate=cls.SAMPLE_RATE, | ||
output=True) | ||
ctrl_connection.send("Successfully started pyaudio stream") | ||
return 0 | ||
except Exception as e: | ||
logger.exception(e) | ||
ctrl_connection.send("Failed to start pyaudio stream") | ||
|
||
@classmethod | ||
def receive_sync(cls, data_conn: Connection): | ||
if cls.pyaudio_stream: | ||
data_conn.send_bytes(cls.pyaudio_stream.read(cls.CHUNK_SIZE)) | ||
|
||
@classmethod | ||
def send_sync(cls, data): | ||
if cls.pyaudio_stream: | ||
cls.pyaudio_stream.write(data.tostring()) | ||
|
||
@classmethod | ||
def shutdown_device(cls, ctrl_connection, is_tx: bool): | ||
logger.debug("shutting down pyaudio...") | ||
try: | ||
if cls.pyaudio_stream: | ||
cls.pyaudio_stream.stop_stream() | ||
cls.pyaudio_stream.close() | ||
if cls.pyaudio_handle: | ||
cls.pyaudio_handle.terminate() | ||
ctrl_connection.send("CLOSE:0") | ||
except Exception as e: | ||
logger.exception(e) | ||
ctrl_connection.send("Failed to shut down pyaudio") | ||
|
||
def __init__(self, sample_rate, resume_on_full_receive_buffer=False): | ||
super().__init__(center_freq=0, sample_rate=sample_rate, bandwidth=0, | ||
gain=1, if_gain=1, baseband_gain=1, | ||
resume_on_full_receive_buffer=resume_on_full_receive_buffer) | ||
|
||
self.success = 0 | ||
|
||
@property | ||
def device_parameters(self) -> OrderedDict: | ||
return OrderedDict([(self.Command.SET_SAMPLE_RATE.name, self.sample_rate)]) | ||
|
||
@staticmethod | ||
def unpack_complex(buffer): | ||
return np.frombuffer(buffer, dtype=np.complex64) | ||
|
||
@staticmethod | ||
def pack_complex(complex_samples: np.ndarray): | ||
arr = Array("f", 2*len(complex_samples), lock=False) | ||
numpy_view = np.frombuffer(arr, dtype=np.float32) | ||
numpy_view[:] = complex_samples.view(np.float32) | ||
return arr |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import numpy as np | ||
|
||
|
||
def test_sounddevice_lib(): | ||
import time | ||
|
||
import numpy as np | ||
from sounddevice import InputStream, OutputStream, sleep as sd_sleep | ||
""" | ||
if no portaudio installed: | ||
Traceback (most recent call last): | ||
File "TestSoundCard.py", line 42, in <module> | ||
test_sounddevice_lib() | ||
File "TestSoundCard.py", line 5, in test_sounddevice_lib | ||
import sounddevice as sd | ||
File "/usr/lib/python3.6/site-packages/sounddevice.py", line 64, in <module> | ||
raise OSError('PortAudio library not found') | ||
OSError: PortAudio library not found | ||
""" | ||
|
||
duration = 2.5 # seconds | ||
|
||
rx_buffer = np.ones((10 ** 6, 2), dtype=np.float32) | ||
global current_rx, current_tx | ||
current_rx = 0 | ||
current_tx = 0 | ||
|
||
def rx_callback(indata: np.ndarray, frames: int, time, status): | ||
global current_rx | ||
if status: | ||
print(status) | ||
|
||
rx_buffer[current_rx:current_rx + frames] = indata | ||
current_rx += frames | ||
|
||
def tx_callback(outdata: np.ndarray, frames: int, time, status): | ||
global current_tx | ||
if status: | ||
print(status) | ||
|
||
outdata[:] = rx_buffer[current_tx:current_tx + frames] | ||
current_tx += frames | ||
|
||
with InputStream(channels=2, callback=rx_callback): | ||
sd_sleep(int(duration * 1000)) | ||
|
||
print("Current rx", current_rx) | ||
|
||
with OutputStream(channels=2, callback=tx_callback): | ||
sd_sleep(int(duration * 1000)) | ||
|
||
print("Current tx", current_tx) | ||
|
||
|
||
def test_pyaudio(): | ||
import pyaudio | ||
|
||
CHUNK = 1024 | ||
p = pyaudio.PyAudio() | ||
|
||
stream = p.open(format=pyaudio.paFloat32, | ||
channels=2, | ||
rate=48000, | ||
input=True, | ||
frames_per_buffer=CHUNK) | ||
|
||
print("* recording") | ||
|
||
frames = [] | ||
|
||
for i in range(0, 100): | ||
data = stream.read(CHUNK) | ||
frames.append(data) | ||
|
||
print("* done recording") | ||
|
||
stream.stop_stream() | ||
stream.close() | ||
p.terminate() | ||
data = b''.join(frames) | ||
|
||
print("* playing") | ||
|
||
p = pyaudio.PyAudio() | ||
stream = p.open(format=pyaudio.paFloat32, | ||
channels=2, | ||
rate=48000, | ||
output=True, | ||
) | ||
|
||
for i in range(0, len(data), CHUNK): | ||
stream.write(data[i:i+CHUNK]) | ||
|
||
stream.stop_stream() | ||
stream.close() | ||
|
||
p.terminate() | ||
|
||
print("* done playing") | ||
|
||
|
||
if __name__ == '__main__': | ||
# test_sounddevice_lib() | ||
test_pyaudio() |