diff --git a/Client/__pycache__/appcomponent.cpython-310.pyc b/Client/__pycache__/appcomponent.cpython-310.pyc index bfe064c..2dd4eb4 100644 Binary files a/Client/__pycache__/appcomponent.cpython-310.pyc and b/Client/__pycache__/appcomponent.cpython-310.pyc differ diff --git a/Client/client.py b/Client/client.py index 74a63e5..932631f 100644 --- a/Client/client.py +++ b/Client/client.py @@ -28,7 +28,7 @@ import zmq from pyogg import OpusDecoder import configparser import ctypes -import zlib +import lz4.frame from utils import * import appcomponent @@ -40,7 +40,7 @@ class App: self.config = configparser.ConfigParser() self.config.read("config.ini") self.device_name_output = self.config["audio"]["device"] - self.buffersize = self.config["network"]["buffersize"] + self.buffersize = int(self.config["network"]["buffersize"]) self.working = False self.readchannel = 1 @@ -396,7 +396,7 @@ class App: break try: - decompressed_data = zlib.decompress(data) + decompressed_data = lz4.frame.decompress(data) datadecoded = pickle.loads(decompressed_data) except: diff --git a/Server/Encoder.py b/Server/Encoder.py index 2cff720..91a1986 100644 --- a/Server/Encoder.py +++ b/Server/Encoder.py @@ -14,14 +14,12 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with IDRB Project. If not, see . """ - import threading from queue import Queue -from pyogg import OpusBufferedEncoder -import numpy as np import pyaudio import RDS as _RDS import logging +import tools EncoderLog = logging.getLogger("Encoder") @@ -37,64 +35,28 @@ for i in range(p.get_device_count()): device_index_input = dev['index'] break -device_name_input = "Line 4 (Virtual Audio Cable)" -device_index_input2 = 0 -for i in range(p.get_device_count()): - dev = p.get_device_info_by_index(i) - if dev['name'] == device_name_input: - device_index_input2 = dev['index'] - break - -streaminput = p.open(format=pyaudio.paInt16, channels=2, rate=48000, input=True, input_device_index=device_index_input) -streaminput2 = p.open(format=pyaudio.paInt16, channels=2, rate=48000, input=True, input_device_index=device_index_input2) - # Create a shared queue for encoded audio packets channel1 = Queue() channel2 = Queue() -# Function to continuously encode audio and put it into the queue -def encode_audio(): - encoder = OpusBufferedEncoder() - encoder.set_application("audio") - encoder.set_sampling_frequency(_RDS.RDS["ContentInfo"]["samplerates"]) - encoder.set_channels(_RDS.RDS["ContentInfo"]["channel"]) - encoder.set_bitrates(_RDS.RDS["ContentInfo"]["bitrate"]) - encoder.set_frame_size(60) - encoder.set_bitrate_mode("VBR") - encoder.set_compresion_complex(10) +channel1option = { + "Bitrates": 64000, + "DeviceInputIndex": device_index_input +} - while True: - pcm = np.frombuffer(streaminput.read(1024, exception_on_overflow=False), dtype=np.int16) +channel2option = { + "Bitrates": 18000, + "InputWAVFile": "./Samples/audiotest.wav" +} - encoded_packets = encoder.buffered_encode(memoryview(bytearray(pcm))) - for encoded_packet, _, _ in encoded_packets: - # Put the encoded audio into the buffer - channel1.put(encoded_packet.tobytes()) +EncoderChannel1 = tools.AudioEncoder(_RDS.RDS, channel1option, channel1) -def encode_audio2(): - encoder2 = OpusBufferedEncoder() - encoder2.set_application("audio") - encoder2.set_sampling_frequency(_RDS.RDS2["ContentInfo"]["samplerates"]) - encoder2.set_channels(_RDS.RDS2["ContentInfo"]["channel"]) - encoder2.set_bitrates(_RDS.RDS2["ContentInfo"]["bitrate"]) - encoder2.set_frame_size(60) - - while True: - pcm2 = np.frombuffer(streaminput2.read(1024, exception_on_overflow=False), dtype=np.int16) - - encoded_packets = encoder2.buffered_encode(memoryview(bytearray(pcm2))) - for encoded_packet, _, _ in encoded_packets: - # Put the encoded audio into the buffer - channel2.put(encoded_packet.tobytes()) - - #channel2.put(pcm2.tobytes()) # if you use pcm +EncoderChannel2 = tools.AudioEncoder(_RDS.RDS2, channel2option, channel2, "wav") def StartEncoder(): EncoderLog.info("Starting encoder") - audio_thread = threading.Thread(target=encode_audio) - audio_thread2 = threading.Thread(target=encode_audio2) - audio_thread.start() - audio_thread2.start() \ No newline at end of file + EncoderChannel1.startencoder() + EncoderChannel2.startencoder() \ No newline at end of file diff --git a/Server/RDS.py b/Server/RDS.py index 8833964..994b62b 100644 --- a/Server/RDS.py +++ b/Server/RDS.py @@ -19,7 +19,7 @@ import time from datetime import datetime import cv2 import numpy as np -from damp11113 import scrollTextBySteps +from damp11113.utils import scrollTextBySteps import threading import Settings import logging @@ -96,12 +96,6 @@ RDS = { "AS": [ # AS = Alternative Server # can add more server here ], - "ContentInfo": { - "Codec": "opus", - "bitrate": 64000, - "channel": 2, - "samplerates": 48000 - }, "images": { "logo": { "lazy": False, @@ -145,12 +139,6 @@ RDS2 = { "AS": [ # AS = Alternative Server # can add more server here ], - "ContentInfo": { - "Codec": "Opus", - "bitrate": 8000, - "channel": 2, - "samplerates": 48000 - }, "images": { "logo": None } @@ -194,5 +182,5 @@ def startRDSThread(): thread.start() thread2.start() - thread3.start() + #thread3.start() diff --git a/Server/Samples/audiotest.wav b/Server/Samples/audiotest.wav new file mode 100644 index 0000000..b8e2ef9 Binary files /dev/null and b/Server/Samples/audiotest.wav differ diff --git a/Server/Settings.py b/Server/Settings.py index 0bfe478..eb35dae 100644 --- a/Server/Settings.py +++ b/Server/Settings.py @@ -15,6 +15,8 @@ You should have received a copy of the GNU General Public License along with IDRB Project. If not, see . """ +import lz4.frame + # To config Muxer you need to goto server.py and find "Config Muxer" # To config RDS you need to goto RDS.py # To config Encoder you need to goto Encoder.py @@ -23,7 +25,7 @@ along with IDRB Project. If not, see . # Server Settings protocol = "ZMQ_WS" # TCP ZMQ ZMQ_WS server_port = ('*', 6980) # if use other protocol ZMQ please use 0.0.0.0 -compression_level = 9 # 0-9 +compression_level = lz4.frame.COMPRESSIONLEVEL_MAX buffersize = 32 # must be int (on working it use buffersize + (buffersize/2) to standby) # low buffersize = low delay @@ -42,13 +44,9 @@ If you want your server to be listed publicly on ThaiSDR Directory, following th 6. copy api key """ -public = True +public = False #ServerIP = "IDRB.damp11113.xyz" # do not add protocol before ip ServerIP = "localhost" #ServerPort = server_port[1] ServerPort = 6980 -<<<<<<< HEAD -ThaiSDRkey = "" -======= -ThaiSDRkey = "" ->>>>>>> 53163be3dc12010715271f6020a0c07d2af720ec +ThaiSDRkey = "1N5LURICLIN1U9QNYZ4MHJ6FNXISFXFELZAX135CFM0HSD17O2.63E60BE9EEA2339C113A15EB" diff --git a/Server/__pycache__/Encoder.cpython-310.pyc b/Server/__pycache__/Encoder.cpython-310.pyc index 3eaf79e..d5208db 100644 Binary files a/Server/__pycache__/Encoder.cpython-310.pyc and b/Server/__pycache__/Encoder.cpython-310.pyc differ diff --git a/Server/__pycache__/RDS.cpython-310.pyc b/Server/__pycache__/RDS.cpython-310.pyc index 46653df..d2cea96 100644 Binary files a/Server/__pycache__/RDS.cpython-310.pyc and b/Server/__pycache__/RDS.cpython-310.pyc differ diff --git a/Server/__pycache__/Settings.cpython-310.pyc b/Server/__pycache__/Settings.cpython-310.pyc index 4533904..d962345 100644 Binary files a/Server/__pycache__/Settings.cpython-310.pyc and b/Server/__pycache__/Settings.cpython-310.pyc differ diff --git a/Server/__pycache__/tools.cpython-310.pyc b/Server/__pycache__/tools.cpython-310.pyc new file mode 100644 index 0000000..df7695c Binary files /dev/null and b/Server/__pycache__/tools.cpython-310.pyc differ diff --git a/Server/server.py b/Server/server.py index 703d29b..0635c01 100644 --- a/Server/server.py +++ b/Server/server.py @@ -21,9 +21,13 @@ import pickle import threading import zmq import logging -import zlib +import lz4.frame import queue import math +import os + +os.environ["damp11113_load_all_module"] = "NO" +os.environ["damp11113_check_update"] = "NO" logging.basicConfig(level=logging.INFO, format='[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s') ServerLog = logging.getLogger("IDRBServer") @@ -88,8 +92,10 @@ def Muxer(): # ENC1encrypted, ENC1salt, ENC1iv = utils.encrypt_data(ENchannel1, "password") # ENchannel1 = ENC1encrypted + b'|||||' + ENC1salt + b'|||||' + ENC1iv - - ENchannel2 = Encoder.channel2.get() + try: + ENchannel2 = Encoder.channel2.get() + except: + ENchannel2 = b"" content = { "first": False, "mainchannel": 1, @@ -119,7 +125,7 @@ def Muxer(): } ThaiSDRDir.content = content - compressedcontent = zlib.compress(pickle.dumps(content), level=Settings.compression_level) + compressedcontent = lz4.frame.compress(pickle.dumps(content), compression_level=Settings.compression_level) Buffer.put(compressedcontent) diff --git a/Server/tools.py b/Server/tools.py new file mode 100644 index 0000000..efff21e --- /dev/null +++ b/Server/tools.py @@ -0,0 +1,141 @@ +""" +This file is part of IDRB Project. + +IDRB Project is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +IDRB Project is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with IDRB Project. If not, see . +""" +import socket +import time +from pyogg import OpusBufferedEncoder +import numpy as np +import pyaudio +import wave +import threading +from damp11113.randoms import rannum + +class AudioEncoder: + def __init__(self, RDS: dict, option: dict, queuebuffer, audioinputtype="device", audiocodec="opus", standalone=False): + self.audiocodec = audiocodec.upper() + self.audioinput = audioinputtype.upper() + self.RDS = RDS + self.option = option + self.buffer = queuebuffer + + self.running = False + self.encoder = None + self.standalone = standalone + + self.runningthread = threading.Thread(target=self._running) + self.sourceinput = None + + self._temp_ip = None + self._temp_port = None + + self._temp_socket = None + + def _createencoder(self): + self.RDS.update({ + "ContentInfo": { + "Codec": self.audiocodec, + "bitrate": self.option.get("Bitrates", 64000), + "channel": self.option.get("Channel", 2), + "samplerates": self.option.get("SamplesRates", 48000) + } + }) + + if self.audiocodec == "PCM": + pass + elif self.audiocodec == "OPUS": + self.encoder = OpusBufferedEncoder() + self.encoder.set_application("audio") + self.encoder.set_sampling_frequency(self.option.get("SamplesRates", 48000)) + self.encoder.set_channels(self.option.get("Channel", 2)) + self.encoder.set_bitrates(self.option.get("Bitrates", 64000)) + self.encoder.set_frame_size(self.option.get("opusFrameSize", 60)) + self.encoder.set_bitrate_mode(self.option.get("BitrateMode", "VBR").upper()) + self.encoder.set_compresion_complex(self.option.get("opusCompressionLevel", 10)) + + def _createaudiosource(self): + if self.audioinput == "DEVICE": + p = pyaudio.PyAudio() + self.sourceinput = p.open(format=pyaudio.paInt16, channels=self.option.get("Channel", 2), rate=self.option.get("SamplesRates", 48000), input=True, input_device_index=self.option.get("DeviceInputIndex", 0)) + elif self.audioinput == "WAV": + file = self.option.get("InputWAVFile", "") + if file == "": + raise "No Audio wav Input Please use option 'InputWAVFile' to set wav file path" + self.sourceinput = wave.open(file, "rb") + elif self.audioinput == "SOCKET": + IP = self.option.get("InputSocketIP", "0.0.0.0") + Port = self.option.get("InputSocketPort", rannum(12000, 12300)) + + self._temp_ip = IP + self._temp_port = Port + + self._temp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._temp_socket.bind((IP, Port)) + + else: + raise "No Audio Device Supported" + + def _running(self): + framesize = self.option.get("InputFrameSize", 1024) + samplesrates = self.option.get("SamplesRates", 48000) + + while self.running: + # get audio + if self.audioinput == "DEVICE": + pcm = np.frombuffer(self.sourceinput.read(framesize, exception_on_overflow=False), dtype=np.int16) + elif self.audioinput == "WAV": + pcm = np.frombuffer(self.sourceinput.readframes(framesize * 2), dtype=np.int16) + if len(pcm) == 0: + self.sourceinput.rewind() + + if self.standalone: + time.sleep(framesize / samplesrates) + + elif self.audioinput == "SOCKET": + pcm = np.frombuffer(self.sourceinput.recv(framesize), dtype=np.int16) + else: + raise "no input data support" + + + + # encode audio + if self.audiocodec == "PCM": + self.buffer.put(pcm.tobytes()) + elif self.audiocodec == "OPUS": + encoded_packets = self.encoder.buffered_encode(memoryview(bytearray(pcm))) + for encoded_packet, _, _ in encoded_packets: + self.buffer.put(encoded_packet.tobytes()) + + + def startencoder(self): + self._createaudiosource() + self._createencoder() + + if self.audioinput == "SOCKET": + self._temp_socket.listen(2) + print(f"[Socket Input Beta] {self.__class__.__name__} audio incoming server is running on {self._temp_ip}:{self._temp_port}. (PCM Data only)") + print(f"[Socket Input Beta] {self.__class__.__name__} waiting for data for start encoder") + self.sourceinput, addr = self._temp_socket.accept() + print(f"[Socket Input Beta] {self.__class__.__name__} {addr} is connected. encoder is starting") + + self._temp_socket.settimeout(self.option.get("InputSocketTimeout", 0.1)) + + self.running = True + self.runningthread.start() + + + def stopencoder(self): + self.running = False + self.runningthread.join() \ No newline at end of file