Improved audio playback on Linux

This commit is contained in:
Mark Qvist 2024-06-04 04:46:47 +02:00
parent 912f86ea8f
commit 050b3aa17a
3 changed files with 67 additions and 29 deletions

View File

@ -1534,8 +1534,11 @@ class SidebandApp(MDApp):
elif audio_field[0] >= LXMF.AM_CODEC2_700C and audio_field[0] <= LXMF.AM_CODEC2_3200: elif audio_field[0] >= LXMF.AM_CODEC2_700C and audio_field[0] <= LXMF.AM_CODEC2_3200:
temp_path = self.sideband.rec_cache+"/msg.ogg" temp_path = self.sideband.rec_cache+"/msg.ogg"
from sideband.audioproc import samples_to_ogg, decode_codec2 from sideband.audioproc import samples_to_ogg, decode_codec2
if samples_to_ogg(decode_codec2(audio_field[1], audio_field[0]), temp_path): samples = decode_codec2(audio_field[1], audio_field[0])
RNS.log("Wrote wav file to: "+temp_path) if samples_to_ogg(samples, temp_path):
RNS.log("Wrote OGG file to: "+temp_path, RNS.LOG_DEBUG)
else:
RNS.log("OGG write failed", RNS.LOG_DEBUG)
else: else:
raise NotImplementedError(audio_field[0]) raise NotImplementedError(audio_field[0])

View File

@ -1,7 +1,8 @@
import time import time
import threading import threading
import RNS
from sbapp.plyer.facades.audio import Audio from sbapp.plyer.facades.audio import Audio
from kivy.core.audio import SoundLoader from ffpyplayer.player import MediaPlayer
class LinuxAudio(Audio): class LinuxAudio(Audio):
@ -18,8 +19,13 @@ class LinuxAudio(Audio):
self.is_playing = False self.is_playing = False
def _check_playback(self): def _check_playback(self):
while self.sound != None and self.sound.state == "play": run = True
while run and self.sound != None and not self.sound.get_pause():
time.sleep(0.25) time.sleep(0.25)
if self.duration:
pts = self.sound.get_pts()
if pts > self.duration:
run = False
self.is_playing = False self.is_playing = False
@ -32,23 +38,30 @@ class LinuxAudio(Audio):
pass pass
def _stop(self): def _stop(self):
if self.sound != None and self.sound.state == "play": if self.sound != None:
self.sound.stop() self.sound.set_pause(True)
self.sound.seek(0, relative=False)
self.is_playing = False self.is_playing = False
def _play(self): def _play(self):
if self.sound == None or self._loaded_path != self._file_path: self.sound = MediaPlayer(self._file_path)
self.sound = SoundLoader.load(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"]
RNS.log(str(self.metadata))
self._loaded_path = self._file_path
self.is_playing = True self.is_playing = True
self.sound.play()
self._check_thread = threading.Thread(target=self._check_playback, daemon=True) self._check_thread = threading.Thread(target=self._check_playback, daemon=True)
self._check_thread.start() self._check_thread.start()
def reload(self): def reload(self):
self._loaded_path = None self._loaded_path = None
self.sound = None
def playing(self): def playing(self):
return self.is_playing return self.is_playing

View File

@ -43,8 +43,13 @@ def samples_from_ogg(file_path=None):
return audio.get_array_of_samples() return audio.get_array_of_samples()
def samples_to_ogg(samples=None, file_path=None): def samples_to_ogg(samples=None, file_path=None):
try:
if file_path != None and samples != None: if file_path != None and samples != None:
pcm_data = io.BytesIO(samples) pcm_data = io.BytesIO(samples)
RNS.log(f"Samples: {len(samples)}")
RNS.log(f"Type : {type(samples)}")
channels = 1; samples_per_second = 8000; bytes_per_sample = 2 channels = 1; samples_per_second = 8000; bytes_per_sample = 2
opus_buffered_encoder = pyogg.OpusBufferedEncoder() opus_buffered_encoder = pyogg.OpusBufferedEncoder()
@ -54,18 +59,33 @@ def samples_to_ogg(samples=None, file_path=None):
opus_buffered_encoder.set_frame_size(20) # milliseconds opus_buffered_encoder.set_frame_size(20) # milliseconds
ogg_opus_writer = pyogg.OggOpusWriter(file_path, opus_buffered_encoder) ogg_opus_writer = pyogg.OggOpusWriter(file_path, opus_buffered_encoder)
frame_duration = 0.1 frame_duration = 0.020
frame_size = int(frame_duration * samples_per_second) frame_size = int(frame_duration * samples_per_second)
bytes_per_frame = frame_size*bytes_per_sample bytes_per_frame = frame_size*bytes_per_sample
read_bytes = 0
written_bytes = 0
while True: while True:
pcm = pcm_data.read(bytes_per_frame) pcm = pcm_data.read(bytes_per_frame)
if len(pcm) == 0: if len(pcm) == 0:
break break
else:
read_bytes += len(pcm)
ogg_opus_writer.write(memoryview(bytearray(pcm))) ogg_opus_writer.write(memoryview(bytearray(pcm)))
written_bytes += len(pcm)
ogg_opus_writer.close() ogg_opus_writer.close()
RNS.log(f"Read {read_bytes} bytes")
RNS.log(f"Wrote {written_bytes} bytes")
return True
except Exception as e:
RNS.trace_exception(e)
return False
def samples_to_wav(samples=None, file_path=None): def samples_to_wav(samples=None, file_path=None):
if samples != None and file_path != None: if samples != None and file_path != None:
import wave import wave
@ -87,7 +107,9 @@ def encode_codec2(samples, mode):
SPF = c2.samples_per_frame() SPF = c2.samples_per_frame()
PACKET_SIZE = SPF * 2 # 16-bit samples PACKET_SIZE = SPF * 2 # 16-bit samples
STRUCT_FORMAT = '{}h'.format(SPF) STRUCT_FORMAT = '{}h'.format(SPF)
F_FRAMES = len(samples)/SPF
N_FRAMES = math.floor(len(samples)/SPF) N_FRAMES = math.floor(len(samples)/SPF)
# TODO: Add padding to align to whole frames
frames = np.array(samples[0:N_FRAMES*SPF], dtype=np.int16) frames = np.array(samples[0:N_FRAMES*SPF], dtype=np.int16)
encoded = b"" encoded = b""