Audio recording on Linux

This commit is contained in:
Mark Qvist 2024-06-04 14:29:28 +02:00
parent 1a224fd65a
commit 584267dcc5
4 changed files with 108 additions and 35 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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: