update 1.6.2

this update is update on server side
new
Buffer

client side
fix buffer
This commit is contained in:
dharm pimsen 2024-02-27 20:48:19 +07:00
parent d5e6b8e1ea
commit 1e77eae2d8
9 changed files with 170 additions and 93 deletions

View File

@ -1,3 +1,20 @@
"""
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 <https://www.gnu.org/licenses/>.
"""
import dearpygui.dearpygui as dpg import dearpygui.dearpygui as dpg
from utils import * from utils import *
@ -60,7 +77,7 @@ def window(self):
dpg.add_spacer() dpg.add_spacer()
dpg.add_text("IDRB (Internet Digital Radio Broadcasting System) Client") dpg.add_text("IDRB (Internet Digital Radio Broadcasting System) Client")
dpg.add_spacer() dpg.add_spacer()
dpg.add_text(f"IDRB Client v1.6.1 Beta") dpg.add_text(f"IDRB Client v1.6.2 Beta")
dpg.add_spacer() dpg.add_spacer()
desc = "IDRB is a novel internet radio broadcasting alternative that uses HLS/DASH/HTTP streams, transferring over TCP/IP. This system supports images and RDS (Dynamic update) capabilities, enabling the transmission of station information. Additionally, it allows for setting station logos and images. IDRB offers multi-broadcasting functionalities and currently supports the Opus codec, with plans to incorporate PCM, MP2/3, AAC/AAC+, and more in the future, ensuring low delay. If you find this project intriguing, you can support it at damp11113.xyz/support." desc = "IDRB is a novel internet radio broadcasting alternative that uses HLS/DASH/HTTP streams, transferring over TCP/IP. This system supports images and RDS (Dynamic update) capabilities, enabling the transmission of station information. Additionally, it allows for setting station logos and images. IDRB offers multi-broadcasting functionalities and currently supports the Opus codec, with plans to incorporate PCM, MP2/3, AAC/AAC+, and more in the future, ensuring low delay. If you find this project intriguing, you can support it at damp11113.xyz/support."
@ -148,8 +165,10 @@ def window(self):
dpg.add_text("Please restart software when configured") dpg.add_text("Please restart software when configured")
with dpg.tab_bar(): with dpg.tab_bar():
with dpg.tab(label="Audio"): with dpg.tab(label="Audio"):
dpg.add_combo([], label="Output Device", tag="selectaudiooutputdevicecombo", dpg.add_combo([], label="Output Device", tag="selectaudiooutputdevicecombo", callback=self.changeaudiodevice)
callback=self.changeaudiodevice) with dpg.tab(label="Network"):
dpg.add_input_int(label="Buffer Size", tag="buffersizeintinput", callback=self.changebuffersize)
def menubar(self): def menubar(self):
with dpg.viewport_menu_bar(): with dpg.viewport_menu_bar():

View File

@ -40,8 +40,7 @@ class App:
self.config = configparser.ConfigParser() self.config = configparser.ConfigParser()
self.config.read("config.ini") self.config.read("config.ini")
self.device_name_output = self.config["audio"]["device"] self.device_name_output = self.config["audio"]["device"]
self.buffersize = 64 # can configable self.buffersize = self.config["network"]["buffersize"]
self.working = False self.working = False
self.readchannel = 1 self.readchannel = 1
@ -60,8 +59,6 @@ class App:
self.lsitem = None self.lsitem = None
self.ccconwithpubselect = False self.ccconwithpubselect = False
self.buffer = queue.Queue(maxsize=self.buffersize) self.buffer = queue.Queue(maxsize=self.buffersize)
self.okbuffer = False
self.firstrunbuffer = True
def connecttoserverwithpubselect(self, sender, data): def connecttoserverwithpubselect(self, sender, data):
self.ccconwithpubselect = True self.ccconwithpubselect = True
@ -125,8 +122,6 @@ class App:
dpg.configure_item("station_logo_config", show=False) dpg.configure_item("station_logo_config", show=False)
dpg.configure_item("RDSinfo", show=False) dpg.configure_item("RDSinfo", show=False)
dpg.configure_item("disconnectbutton", show=False) dpg.configure_item("disconnectbutton", show=False)
dpg.configure_item("connectservergroup", show=True)
dpg.configure_item("serverstatus", default_value='disconnected', color=(255, 0, 0))
dpg.configure_item("logostatus", show=False) dpg.configure_item("logostatus", show=False)
self.firstrun = True self.firstrun = True
self.firststart = True self.firststart = True
@ -136,9 +131,7 @@ class App:
self.ccisdecryptpassword = None self.ccisdecryptpassword = None
self.cciswaitlogoim = True self.cciswaitlogoim = True
self.ccthreadlogorecisworking = False self.ccthreadlogorecisworking = False
self.buffer = queue.Queue(maxsize=self.buffersize) # clear buffer
self.okbuffer = False
self.firstrunbuffer = True
def RDSshow(self): def RDSshow(self):
try: try:
@ -231,6 +224,11 @@ class App:
self.config["audio"]["device"] = dpg.get_value(sender) self.config["audio"]["device"] = dpg.get_value(sender)
self.config.write(open('config.ini', 'w')) self.config.write(open('config.ini', 'w'))
def changebuffersize(self, sender, data):
self.buffersize = int(dpg.get_value(sender))
self.config["network"]["buffersize"] = str(dpg.get_value(sender))
self.config.write(open('config.ini', 'w'))
def pubserverselectone(self, sender, data): def pubserverselectone(self, sender, data):
if data == False: if data == False:
dpg.configure_item("connectbuttonpubserverselect", show=False) dpg.configure_item("connectbuttonpubserverselect", show=False)
@ -295,25 +293,33 @@ class App:
self.pubserverselectsearch() self.pubserverselectsearch()
def streambuffer(self, socket): def streambuffer(self, socket):
consecutive_above_threshold = 0 # Counter to track consecutive iterations above threshold
tolerance_iterations = 5 # Number of consecutive iterations required above threshold
while self.working: while self.working:
if self.cprotocol == "TCP": try:
tempdata = b'' if self.cprotocol == "TCP":
# data = socket.recv(1580152) tempdata = b''
while True: # data = socket.recv(1580152)
part = socket.recv(1024) while True:
tempdata += part part = socket.recv(1024)
if len(part) < 1024: tempdata += part
# either 0 or end of data if len(part) < 1024:
break # either 0 or end of data
self.buffer.put(tempdata) break
elif self.cprotocol == "ZeroMQ": self.buffer.put(tempdata, timeout=0.1)
self.buffer.put(socket.recv()) elif self.cprotocol == "ZeroMQ":
else: self.buffer.put(socket.recv(), timeout=0.1)
self.buffer.put(b"") else:
self.buffer.put(b"")
dpg.configure_item("bufferstatus", default_value=f'Buffer: {self.buffer.qsize()}/{self.buffersize}') dpg.configure_item("bufferstatus", default_value=f'Buffer: {self.buffer.qsize()}/{self.buffersize}', color=(0, 255, 0))
except queue.Full:
dpg.configure_item("bufferstatus", default_value=f'Buffer: {self.buffer.qsize()}/{self.buffersize}', color=(255, 0, 0))
while not self.buffer.empty():
self.buffer.get()
dpg.configure_item("bufferstatus", default_value=f'Buffer: {self.buffer.qsize()}/{self.buffersize}', color=(255, 255, 0))
dpg.configure_item("serverstatus", default_value='disconnected', color=(255, 0, 0))
dpg.configure_item("connectservergroup", show=True)
def stream(self, socket): def stream(self, socket):
opus_decoder = None opus_decoder = None
@ -339,6 +345,7 @@ class App:
if self.buffer.not_empty: if self.buffer.not_empty:
data = self.buffer.get() data = self.buffer.get()
else: else:
dpg.configure_item("serverstatus", default_value='Buffering...', color=(255, 255, 0))
continue continue
bytesconunt += len(data) bytesconunt += len(data)
@ -589,7 +596,7 @@ class App:
ctypes.CDLL("opus.dll") ctypes.CDLL("opus.dll")
dpg.create_context() dpg.create_context()
dpg.create_viewport(title=f'IDRB Client v1.6.1 Beta', width=1280, height=720, large_icon="IDRBfavicon.ico", clear_color=(43, 45, 48)) # set viewport window dpg.create_viewport(title=f'IDRB Client v1.6.2 Beta', width=1280, height=720, large_icon="IDRBfavicon.ico", clear_color=(43, 45, 48)) # set viewport window
dpg.setup_dearpygui() dpg.setup_dearpygui()
# -------------- add code here -------------- # -------------- add code here --------------
noimage_texture_data = [] noimage_texture_data = []
@ -621,6 +628,7 @@ class App:
output_devices.append(device_info['name']) output_devices.append(device_info['name'])
dpg.configure_item("selectaudiooutputdevicecombo", items=output_devices, default_value=self.config["audio"]["device"]) dpg.configure_item("selectaudiooutputdevicecombo", items=output_devices, default_value=self.config["audio"]["device"])
dpg.configure_item("buffersizeintinput", default_value=int(self.config["network"]["buffersize"]))
# ------------------------------------------- # -------------------------------------------
dpg.show_viewport() dpg.show_viewport()

View File

@ -1,6 +1,9 @@
[audio] [audio]
device = Speakers (2- USB Audio DAC ) device = Speakers (2- USB Audio DAC )
[network]
buffersize = 128
[debug] [debug]
hideconsole = true hideconsole = true

View File

@ -1,3 +1,20 @@
"""
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 <https://www.gnu.org/licenses/>.
"""
from Crypto.Cipher import AES from Crypto.Cipher import AES
from Crypto.Protocol.KDF import scrypt from Crypto.Protocol.KDF import scrypt
import numpy as np import numpy as np

View File

@ -15,10 +15,17 @@ You should have received a copy of the GNU General Public License
along with IDRB Project. If not, see <https://www.gnu.org/licenses/>. along with IDRB Project. If not, see <https://www.gnu.org/licenses/>.
""" """
# 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
# Do not goto ThaiSDRDir.py and utils.py
# Server Settings # Server Settings
protocol = "ZMQ_WS" # TCP ZMQ ZMQ_WS protocol = "ZMQ_WS" # TCP ZMQ ZMQ_WS
server_port = ('*', 6980) # if use other protocol ZMQ please use 0.0.0.0 server_port = ('*', 6980) # if use other protocol ZMQ please use 0.0.0.0
compression_level = 9 # 0-9 compression_level = 9 # 0-9
buffersize = 32 # must be int (on working it use buffersize + (buffersize/2) to standby)
# low buffersize = low delay
# Server Info # Server Info
ServerName = "DPCloudev" ServerName = "DPCloudev"
@ -40,4 +47,4 @@ public = True
ServerIP = "localhost" ServerIP = "localhost"
#ServerPort = server_port[1] #ServerPort = server_port[1]
ServerPort = 6980 ServerPort = 6980
ThaiSDRkey = "1N5LURICLIN1U9QNYZ4MHJ6FNXISFXFELZAX135CFM0HSD17O2.63E60BE9EEA2339C113A15EB" ThaiSDRkey = ""

View File

@ -22,6 +22,8 @@ import threading
import zmq import zmq
import logging import logging
import zlib import zlib
import queue
import math
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s') logging.basicConfig(level=logging.INFO, format='[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s')
ServerLog = logging.getLogger("IDRBServer") ServerLog = logging.getLogger("IDRBServer")
@ -61,7 +63,6 @@ _RDS.startRDSThread()
ServerLog.info('starting audio encoding') ServerLog.info('starting audio encoding')
Encoder.StartEncoder() Encoder.StartEncoder()
if protocol == "TCP": if protocol == "TCP":
connected_users = 0 connected_users = 0
elif protocol == "ZMQ": elif protocol == "ZMQ":
@ -74,78 +75,98 @@ timestart = time.time()
connectionlist = [] connectionlist = []
first = True first = True
Buffer = queue.Queue(maxsize=math.trunc(Settings.buffersize + (Settings.buffersize/2)))
# ---------------------------------- Config Muxer ---------------------------------------
def Muxer():
while True:
# Get the encoded audio from the buffer
ENchannel1 = Encoder.channel1.get()
# encrypt data
# ENC1encrypted, ENC1salt, ENC1iv = utils.encrypt_data(ENchannel1, "password")
# ENchannel1 = ENC1encrypted + b'|||||' + ENC1salt + b'|||||' + ENC1iv
ENchannel2 = Encoder.channel2.get()
content = {
"first": False,
"mainchannel": 1,
"channel": {
1: {
"Station": "DPRadio+",
"StationDesc": "The best station in the world!",
"Encrypt": b'|||||' in ENchannel1, # check if encrypt
"ContentSize": len(ENchannel1),
"Content": ENchannel1,
"RDS": _RDS.RDS
},
2: {
"Station": "DPTest",
"StationDesc": "",
"Encrypt": b'|||||' in ENchannel2,
"ContentSize": len(ENchannel2),
"Content": ENchannel2,
"RDS": _RDS.RDS2
}
},
"serverinfo": {
"Listener": connected_users,
"Startat": timestart,
"RDS": _RDS.ServerRDS
}
}
ThaiSDRDir.content = content
compressedcontent = zlib.compress(pickle.dumps(content), level=Settings.compression_level)
Buffer.put(compressedcontent)
# -----------------------------------------------------------------------------------------------
def handle_client(): def handle_client():
global connected_users, first global connected_users, first
try: try:
while True: while True:
# Get the encoded audio from the buffer # Check if the buffer queue has enough data to send
ENchannel1 = Encoder.channel1.get() if Buffer.qsize() >= Settings.buffersize: # Adjust the threshold as needed
if protocol == "TCP":
# encrypt data for i in connectionlist:
#ENC1encrypted, ENC1salt, ENC1iv = utils.encrypt_data(ENchannel1, "password") try:
# Send data from the buffer queue to connected clients
#ENchannel1 = ENC1encrypted + b'|||||' + ENC1salt + b'|||||' + ENC1iv for _ in range(Settings.buffersize):
i.sendall(Buffer.get())
ENchannel2 = Encoder.channel2.get() except Exception as e:
content = { if i in connectionlist:
"first": False, i.close()
"mainchannel": 1, connectionlist.remove(i)
"channel": { connected_users -= 1
1: { if not connectionlist:
"Station": "DPRadio+", first = True
"StationDesc": "The best station in the world!", ServerLog.info('server is standby now')
"Encrypt": b'|||||' in ENchannel1, # check if encrypt break
"ContentSize": len(ENchannel1), elif protocol == "ZMQ":
"Content": ENchannel1, # Send data from the buffer queue to ZMQ socket
"RDS": _RDS.RDS for _ in range(Settings.buffersize):
}, s.send(Buffer.get())
2: {
"Station": "DPTest",
"StationDesc": "",
"Encrypt": b'|||||' in ENchannel2,
"ContentSize": len(ENchannel2),
"Content": ENchannel2,
"RDS": _RDS.RDS2
}
},
"serverinfo": {
"Listener": connected_users,
"Startat": timestart,
"RDS": _RDS.ServerRDS
}
}
ThaiSDRDir.content = content
compressedcontent = zlib.compress(pickle.dumps(content), level=Settings.compression_level)
#connection.sendall(pickle.dumps(content))
if protocol == "TCP":
for i in connectionlist:
try:
i.sendall(compressedcontent)
except Exception as e:
#print(f'Error sending data to {i.getpeername()}: {e}')
# Remove disconnected client from the list
if i in connectionlist:
i.close()
connectionlist.remove(i)
connected_users -= 1
# check if no user
if not connectionlist:
first = True
ServerLog.info('server is standby now')
break
elif protocol == "ZMQ":
s.send(compressedcontent)
except Exception as e: except Exception as e:
print(f'Error: {e}') print(f'Error: {e}')
# Your main server logic using threading for handling connections # Your main server logic using threading for handling connections
if __name__ == "__main__": if __name__ == "__main__":
if public: if public:
ServerLog.info('starting ThaiSDR Directory') ServerLog.info('starting ThaiSDR Directory')
ThaiSDRDir.run() ThaiSDRDir.run()
ServerLog.info('server is running')
ServerLog.info('starting Muxer')
muxerthread = threading.Thread(target=Muxer)
muxerthread.start()
ServerLog.info('starting server')
if protocol == "TCP": if protocol == "TCP":
while True: while True:
connection, client_address = s.accept() connection, client_address = s.accept()
@ -162,4 +183,6 @@ if __name__ == "__main__":
elif protocol == "ZMQ": elif protocol == "ZMQ":
client_thread = threading.Thread(target=handle_client) client_thread = threading.Thread(target=handle_client)
# client_thread.daemon = True # Set the thread as a daemon so it exits when the main thread exits # client_thread.daemon = True # Set the thread as a daemon so it exits when the main thread exits
client_thread.start() client_thread.start()
ServerLog.info('server is running')