Improved audio playback on Linux
This commit is contained in:
parent
912f86ea8f
commit
050b3aa17a
|
@ -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])
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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""
|
||||||
|
|
Loading…
Reference in New Issue