diff --git a/sbapp/main.py b/sbapp/main.py index feb5cfa..5ef4564 100644 --- a/sbapp/main.py +++ b/sbapp/main.py @@ -1534,8 +1534,12 @@ class SidebandApp(MDApp): elif audio_field[0] >= LXMF.AM_CODEC2_700C and audio_field[0] <= LXMF.AM_CODEC2_3200: temp_path = self.sideband.rec_cache+"/msg.ogg" from sideband.audioproc import samples_to_ogg, decode_codec2 - samples = decode_codec2(audio_field[1], audio_field[0]) - if samples_to_ogg(samples, temp_path): + + target_rate = 8000 + if RNS.vendor.platformutils.is_linux(): + target_rate = 48000 + + if samples_to_ogg(decode_codec2(audio_field[1], audio_field[0]), temp_path, input_rate=8000, output_rate=target_rate): RNS.log("Wrote OGG file to: "+temp_path, RNS.LOG_DEBUG) else: RNS.log("OGG write failed", RNS.LOG_DEBUG) @@ -1641,7 +1645,7 @@ class SidebandApp(MDApp): audio = audio.set_sample_width(2) samples = audio.get_array_of_samples() - from sideband.audioproc import samples_from_ogg, encode_codec2, decode_codec2 + from sideband.audioproc import encode_codec2 encoded = encode_codec2(samples, self.audio_msg_mode) ap_duration = time.time() - ap_start diff --git a/sbapp/plyer/platforms/android/audio.py b/sbapp/plyer/platforms/android/audio.py index 3d40dc6..8c5ff7b 100644 --- a/sbapp/plyer/platforms/android/audio.py +++ b/sbapp/plyer/platforms/android/audio.py @@ -56,7 +56,7 @@ class AndroidAudio(Audio): else: self._recorder.setAudioSource(AudioSource.DEFAULT) self._recorder.setAudioSamplingRate(48000) - self._recorder.setAudioEncodingBitRate(16000) + self._recorder.setAudioEncodingBitRate(32000) self._recorder.setAudioChannels(1) self._recorder.setOutputFormat(OutputFormat.OGG) self._recorder.setAudioEncoder(AudioEncoder.OPUS) diff --git a/sbapp/plyer/platforms/linux/audio.py b/sbapp/plyer/platforms/linux/audio.py index 3509f33..a7de523 100644 --- a/sbapp/plyer/platforms/linux/audio.py +++ b/sbapp/plyer/platforms/linux/audio.py @@ -1,8 +1,11 @@ import time import threading import RNS +import io from sbapp.plyer.facades.audio import Audio from ffpyplayer.player import MediaPlayer +from sbapp.pyogg import OpusFile, OpusBufferedEncoder, OggOpusWriter +import pyaudio class LinuxAudio(Audio): @@ -16,7 +19,10 @@ class LinuxAudio(Audio): self._finished_callback = None self._loaded_path = None self.sound = None + self.pa = None self.is_playing = False + self.recorder = None + self.should_record = False def _check_playback(self): run = True @@ -33,12 +39,76 @@ class LinuxAudio(Audio): self._check_thread = None self._finished_callback(self) + 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() + def _start(self): - # TODO: Implement recording - pass + 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() def _stop(self): - if self.sound != None: + if self.should_record == True: + self.should_record = False + + elif self.sound != None: self.sound.set_pause(True) self.sound.seek(0, relative=False) self.is_playing = False diff --git a/sbapp/sideband/audioproc.py b/sbapp/sideband/audioproc.py index 0b9bfcc..8ebe8df 100644 --- a/sbapp/sideband/audioproc.py +++ b/sbapp/sideband/audioproc.py @@ -8,7 +8,7 @@ import RNS import LXMF if RNS.vendor.platformutils.is_android(): - import pyogg + from pyogg import OpusFile, OpusBufferedEncoder, OggOpusWriter from pydub import AudioSegment else: if RNS.vendor.platformutils.is_linux(): @@ -30,7 +30,7 @@ codec2_modes = { LXMF.AM_CODEC2_3200: 3200, } -def samples_from_ogg(file_path=None): +def samples_from_ogg(file_path=None, output_rate=8000): if file_path != None and os.path.isfile(file_path): opus_file = OpusFile(file_path) audio = AudioSegment( @@ -41,49 +41,48 @@ def samples_from_ogg(file_path=None): audio = audio.split_to_mono()[0] audio = audio.apply_gain(-audio.max_dBFS) - audio = audio.set_frame_rate(8000) + audio = audio.set_frame_rate(output_rate) audio = audio.set_sample_width(2) return audio.get_array_of_samples() -def samples_to_ogg(samples=None, file_path=None): +def resample(samples, width, channels, input_rate, output_rate, normalize): + audio = AudioSegment( + samples, + frame_rate=input_rate, + sample_width=width, + channels=channels) + + if normalize: + audio = audio.apply_gain(-audio.max_dBFS) + + resampled_audio = audio.set_frame_rate(output_rate) + return resampled_audio.get_array_of_samples().tobytes() + +def samples_to_ogg(samples=None, file_path=None, normalize=False, input_channels=1, input_sample_width=2, input_rate=8000, output_rate=12000, profile="audio"): try: if file_path != None and samples != None: + if input_rate != output_rate or normalize: + samples = resample(samples, input_sample_width, input_channels, input_rate, output_rate, normalize) + 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 = input_channels; samples_per_second = output_rate; bytes_per_sample = 2 + frame_duration_ms = 60 opus_buffered_encoder = OpusBufferedEncoder() - opus_buffered_encoder.set_application("audio") + opus_buffered_encoder.set_application(profile) opus_buffered_encoder.set_sampling_frequency(samples_per_second) opus_buffered_encoder.set_channels(channels) - opus_buffered_encoder.set_frame_size(20) # milliseconds + opus_buffered_encoder.set_frame_size(frame_duration_ms) ogg_opus_writer = OggOpusWriter(file_path, opus_buffered_encoder) - frame_duration = 0.020 + frame_duration = frame_duration_ms/1000.0 frame_size = int(frame_duration * samples_per_second) bytes_per_frame = frame_size*bytes_per_sample - read_bytes = 0 - written_bytes = 0 - while True: - pcm = pcm_data.read(bytes_per_frame) - if len(pcm) == 0: - break - else: - read_bytes += len(pcm) - - ogg_opus_writer.write(memoryview(bytearray(pcm))) - written_bytes += len(pcm) - + ogg_opus_writer.write(memoryview(bytearray(samples))) ogg_opus_writer.close() - - RNS.log(f"Read {read_bytes} bytes") - RNS.log(f"Wrote {written_bytes} bytes") - + return True except Exception as e: