feat: implemented sample rate conversion
This commit is contained in:
@@ -7,6 +7,7 @@ import logging
|
|||||||
import json
|
import json
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
|
import audioop
|
||||||
from pyaudio import PyAudio, Stream
|
from pyaudio import PyAudio, Stream
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -16,9 +17,27 @@ INTERVAL_QUEUE = 0.1
|
|||||||
INTERVAL_STREAM = 0.0001
|
INTERVAL_STREAM = 0.0001
|
||||||
|
|
||||||
|
|
||||||
class PlayEntry(NamedTuple):
|
class PlayEntry:
|
||||||
|
def __init__(self, wave_file: wave.Wave_read, stream: Stream, device_framerate):
|
||||||
|
self.wave_file = wave_file
|
||||||
|
self.stream = stream
|
||||||
|
self.cvstate = None
|
||||||
|
|
||||||
|
self.device_framerate = device_framerate
|
||||||
|
self.sample_width = wave_file.getsampwidth()
|
||||||
|
self.channels = wave_file.getnchannels()
|
||||||
|
self.frame_rate = wave_file.getframerate()
|
||||||
|
|
||||||
|
def get_actual_data(self, nframes):
|
||||||
|
data = self.wave_file.readframes(nframes)
|
||||||
|
new_data, self.cvstate = audioop.ratecv(data, self.sample_width, self.channels, self.frame_rate,
|
||||||
|
self.device_framerate, self.cvstate)
|
||||||
|
return new_data
|
||||||
|
|
||||||
wave_file: wave.Wave_read
|
wave_file: wave.Wave_read
|
||||||
stream: Stream
|
stream: Stream
|
||||||
|
cvstate: Optional['audioop.RatecvState']
|
||||||
|
actual_size: int = None
|
||||||
|
|
||||||
|
|
||||||
class AudioSystem:
|
class AudioSystem:
|
||||||
@@ -29,6 +48,8 @@ class AudioSystem:
|
|||||||
self.to_play = Queue()
|
self.to_play = Queue()
|
||||||
self.playing = {}
|
self.playing = {}
|
||||||
self.audio = PyAudio()
|
self.audio = PyAudio()
|
||||||
|
self.default_output_device = self.audio.get_default_output_device_info()
|
||||||
|
self.default_sample_rate = int(self.default_output_device['defaultSampleRate'])
|
||||||
self.scheduler = scheduler
|
self.scheduler = scheduler
|
||||||
self.scheduler.enter(0, 2, self.start_streams)
|
self.scheduler.enter(0, 2, self.start_streams)
|
||||||
self.scheduler.enter(0, 1, self.fill_streams)
|
self.scheduler.enter(0, 1, self.fill_streams)
|
||||||
@@ -58,13 +79,13 @@ class AudioSystem:
|
|||||||
stream = self.audio.open(
|
stream = self.audio.open(
|
||||||
format=self.audio.get_format_from_width(wave_file.getsampwidth()),
|
format=self.audio.get_format_from_width(wave_file.getsampwidth()),
|
||||||
channels=wave_file.getnchannels(),
|
channels=wave_file.getnchannels(),
|
||||||
rate=wave_file.getframerate(),
|
rate=self.default_sample_rate,
|
||||||
output=True,
|
output=True,
|
||||||
)
|
)
|
||||||
entry_id = uuid.uuid4()
|
entry_id = uuid.uuid4()
|
||||||
while entry_id in self.playing:
|
while entry_id in self.playing:
|
||||||
entry_id = uuid.uuid4()
|
entry_id = uuid.uuid4()
|
||||||
self.playing[entry_id] = PlayEntry(wave_file, stream)
|
self.playing[entry_id] = PlayEntry(wave_file, stream, self.default_sample_rate)
|
||||||
self.to_play.task_done()
|
self.to_play.task_done()
|
||||||
except Empty:
|
except Empty:
|
||||||
pass
|
pass
|
||||||
@@ -82,7 +103,7 @@ class AudioSystem:
|
|||||||
if entry.stream.get_write_available() < CHUNK_SIZE:
|
if entry.stream.get_write_available() < CHUNK_SIZE:
|
||||||
continue # not enough space in buffer, wait for next iteration
|
continue # not enough space in buffer, wait for next iteration
|
||||||
try:
|
try:
|
||||||
data = entry.wave_file.readframes(CHUNK_SIZE)
|
data = entry.get_actual_data(CHUNK_SIZE)
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
logger.warning('An IO Error occurred while reading WAV file', exc_info=e)
|
logger.warning('An IO Error occurred while reading WAV file', exc_info=e)
|
||||||
data = b''
|
data = b''
|
||||||
|
|||||||
Reference in New Issue
Block a user