2024-06-02 10:31:58 -06:00
|
|
|
import time
|
|
|
|
import threading
|
2024-06-03 20:46:47 -06:00
|
|
|
import RNS
|
2024-06-04 06:29:28 -06:00
|
|
|
import io
|
2024-06-02 10:31:58 -06:00
|
|
|
from sbapp.plyer.facades.audio import Audio
|
2024-06-03 20:46:47 -06:00
|
|
|
from ffpyplayer.player import MediaPlayer
|
2024-06-04 06:29:28 -06:00
|
|
|
from sbapp.pyogg import OpusFile, OpusBufferedEncoder, OggOpusWriter
|
|
|
|
import pyaudio
|
2024-06-02 10:31:58 -06:00
|
|
|
|
|
|
|
class LinuxAudio(Audio):
|
|
|
|
|
|
|
|
def __init__(self, file_path=None):
|
|
|
|
default_path = None
|
|
|
|
super().__init__(file_path or default_path)
|
|
|
|
|
|
|
|
self._recorder = None
|
|
|
|
self._player = None
|
|
|
|
self._check_thread = None
|
|
|
|
self._finished_callback = None
|
2024-06-03 05:20:41 -06:00
|
|
|
self._loaded_path = None
|
|
|
|
self.sound = None
|
2024-06-04 06:29:28 -06:00
|
|
|
self.pa = None
|
2024-06-03 18:55:13 -06:00
|
|
|
self.is_playing = False
|
2024-06-04 06:29:28 -06:00
|
|
|
self.recorder = None
|
|
|
|
self.should_record = False
|
2024-06-02 10:31:58 -06:00
|
|
|
|
|
|
|
def _check_playback(self):
|
2024-06-03 20:46:47 -06:00
|
|
|
run = True
|
|
|
|
while run and self.sound != None and not self.sound.get_pause():
|
2024-06-02 10:31:58 -06:00
|
|
|
time.sleep(0.25)
|
2024-06-03 20:46:47 -06:00
|
|
|
if self.duration:
|
|
|
|
pts = self.sound.get_pts()
|
|
|
|
if pts > self.duration:
|
|
|
|
run = False
|
2024-06-03 18:55:13 -06:00
|
|
|
|
|
|
|
self.is_playing = False
|
2024-06-02 10:31:58 -06:00
|
|
|
|
|
|
|
if self._finished_callback and callable(self._finished_callback):
|
|
|
|
self._check_thread = None
|
|
|
|
self._finished_callback(self)
|
|
|
|
|
2024-06-04 06:29:28 -06:00
|
|
|
def _record_job(self):
|
|
|
|
samples_per_second = self.default_rate;
|
|
|
|
bytes_per_sample = 2; frame_duration_ms = 20
|
|
|
|
opus_buffered_encoder = OpusBufferedEncoder()
|
|
|
|
opus_buffered_encoder.set_application("voip")
|
|
|
|
opus_buffered_encoder.set_sampling_frequency(samples_per_second)
|
|
|
|
opus_buffered_encoder.set_channels(1)
|
|
|
|
opus_buffered_encoder.set_frame_size(frame_duration_ms)
|
|
|
|
ogg_opus_writer = OggOpusWriter(self._file_path, opus_buffered_encoder)
|
|
|
|
|
|
|
|
frame_duration = frame_duration_ms/1000
|
|
|
|
frame_size = int(frame_duration * samples_per_second)
|
|
|
|
bytes_per_frame = frame_size*bytes_per_sample
|
|
|
|
|
|
|
|
read_bytes = 0
|
|
|
|
pcm_buf = b""
|
|
|
|
should_continue = True
|
|
|
|
while self.should_record and self.recorder:
|
|
|
|
samples_available = self.recorder.get_read_available()
|
|
|
|
bytes_available = samples_available*bytes_per_sample
|
|
|
|
if bytes_available > 0:
|
|
|
|
read_req = bytes_per_frame - len(pcm_buf)
|
|
|
|
read_n = min(bytes_available, read_req)
|
|
|
|
read_s = read_n//bytes_per_sample
|
|
|
|
rb = self.recorder.read(read_s); read_bytes += len(rb)
|
|
|
|
pcm_buf += rb
|
|
|
|
|
|
|
|
if len(pcm_buf) == bytes_per_frame:
|
|
|
|
ogg_opus_writer.write(memoryview(bytearray(pcm_buf)))
|
|
|
|
# RNS.log("Wrote frame of "+str(len(pcm_buf))+", expected size "+str(bytes_per_frame))
|
|
|
|
pcm_buf = b""
|
|
|
|
|
|
|
|
# Finish up anything left in buffer
|
|
|
|
time.sleep(frame_duration)
|
|
|
|
samples_available = self.recorder.get_read_available()
|
|
|
|
bytes_available = samples_available*bytes_per_sample
|
|
|
|
if bytes_available > 0:
|
|
|
|
read_req = bytes_per_frame - len(pcm_buf)
|
|
|
|
read_n = min(bytes_available, read_req)
|
|
|
|
read_s = read_n//bytes_per_sample
|
|
|
|
rb = self.recorder.read(read_s); read_bytes += len(rb)
|
|
|
|
pcm_buf += rb
|
|
|
|
|
|
|
|
if len(pcm_buf) == bytes_per_frame:
|
|
|
|
ogg_opus_writer.write(memoryview(bytearray(pcm_buf)))
|
|
|
|
# RNS.log("Wrote frame of "+str(len(pcm_buf))+", expected size "+str(bytes_per_frame))
|
|
|
|
pcm_buf = b""
|
|
|
|
|
|
|
|
ogg_opus_writer.close()
|
|
|
|
if self.recorder:
|
|
|
|
self.recorder.close()
|
|
|
|
|
2024-06-02 10:31:58 -06:00
|
|
|
def _start(self):
|
2024-06-04 06:29:28 -06:00
|
|
|
self.should_record = True
|
|
|
|
if self.pa == None:
|
|
|
|
self.pa = pyaudio.PyAudio()
|
|
|
|
self.default_input_device = self.pa.get_default_input_device_info()
|
|
|
|
self.default_rate = 48000
|
|
|
|
# self.default_rate = int(self.default_input_device["defaultSampleRate"])
|
|
|
|
if self.recorder:
|
|
|
|
self.recorder.close()
|
|
|
|
self.recorder = None
|
|
|
|
self.recorder = self.pa.open(self.default_rate, 1, pyaudio.paInt16, input=True)
|
|
|
|
threading.Thread(target=self._record_job, daemon=True).start()
|
2024-06-02 10:31:58 -06:00
|
|
|
|
|
|
|
def _stop(self):
|
2024-06-04 06:29:28 -06:00
|
|
|
if self.should_record == True:
|
|
|
|
self.should_record = False
|
|
|
|
|
|
|
|
elif self.sound != None:
|
2024-06-03 20:46:47 -06:00
|
|
|
self.sound.set_pause(True)
|
|
|
|
self.sound.seek(0, relative=False)
|
2024-06-03 18:55:13 -06:00
|
|
|
self.is_playing = False
|
2024-06-02 10:31:58 -06:00
|
|
|
|
|
|
|
def _play(self):
|
2024-06-03 20:46:47 -06:00
|
|
|
self.sound = MediaPlayer(self._file_path)
|
|
|
|
self.metadata = self.sound.get_metadata()
|
|
|
|
self.duration = self.metadata["duration"]
|
|
|
|
if self.duration == None:
|
|
|
|
time.sleep(0.15)
|
|
|
|
self.metadata = self.sound.get_metadata()
|
|
|
|
self.duration = self.metadata["duration"]
|
2024-06-03 05:20:41 -06:00
|
|
|
|
2024-06-03 20:46:47 -06:00
|
|
|
self._loaded_path = self._file_path
|
2024-06-02 10:31:58 -06:00
|
|
|
self.is_playing = True
|
|
|
|
|
|
|
|
self._check_thread = threading.Thread(target=self._check_playback, daemon=True)
|
|
|
|
self._check_thread.start()
|
|
|
|
|
2024-06-03 05:20:41 -06:00
|
|
|
def reload(self):
|
|
|
|
self._loaded_path = None
|
2024-06-02 10:31:58 -06:00
|
|
|
|
2024-06-03 18:55:13 -06:00
|
|
|
def playing(self):
|
|
|
|
return self.is_playing
|
|
|
|
|
2024-06-02 10:31:58 -06:00
|
|
|
|
|
|
|
def instance():
|
|
|
|
return LinuxAudio()
|