diff --git a/config/A60,U=60,M=PWI,S=(64 3001).txt b/config/device/A60,U=60,M=PWI,S=(64 3001).txt
similarity index 100%
rename from config/A60,U=60,M=PWI,S=(64 3001).txt
rename to config/device/A60,U=60,M=PWI,S=(64 3001).txt
diff --git a/config/A80,U=80,M=PWI,S=(64 4001).txt b/config/device/A80,U=80,M=PWI,S=(64 4001).txt
similarity index 100%
rename from config/A80,U=80,M=PWI,S=(64 4001).txt
rename to config/device/A80,U=80,M=PWI,S=(64 4001).txt
diff --git a/config/ARIA64,U=30,M=PWI,S=(64 1501).txt b/config/device/ARIA64,U=30,M=PWI,S=(64 1501).txt
similarity index 100%
rename from config/ARIA64,U=30,M=PWI,S=(64 1501).txt
rename to config/device/ARIA64,U=30,M=PWI,S=(64 1501).txt
diff --git a/config/ARIA641,U=30,M=PWI,S=(64 1501).txt b/config/device/ARIA641,U=30,M=PWI,S=(64 1501).txt
similarity index 100%
rename from config/ARIA641,U=30,M=PWI,S=(64 1501).txt
rename to config/device/ARIA641,U=30,M=PWI,S=(64 1501).txt
diff --git a/src/config.py b/src/config.py
index 6a89bcf..ef05ce3 100644
--- a/src/config.py
+++ b/src/config.py
@@ -19,7 +19,11 @@ DOC = BASE / 'doc'
CONFIG = BASE / 'config'
DS.mkdir(exist_ok=True, parents=True)
DOC.mkdir(exist_ok=True, parents=True)
-CONFIG.mkdir(exist_ok=True, parents=True)
+DEVICE_CONFIG = CONFIG / 'device'
+IMAGING_CONFIG = CONFIG / 'imaging'
+DEVICE_CONFIG.mkdir(exist_ok=True, parents=True)
+IMAGING_CONFIG.mkdir(exist_ok=True, parents=True)
+# CONFIG.mkdir(exist_ok=True, parents=True)
CONFIG_FOLDER = BASE / 'config'
LAST_CONFIG = BASE / 'config' / 'last_imaging_config.json'
diff --git a/src/nodes/Beamformer.py b/src/nodes/Beamformer.py
index 0a8fc94..270202b 100644
--- a/src/nodes/Beamformer.py
+++ b/src/nodes/Beamformer.py
@@ -1,5 +1,6 @@
import logging
import struct
+import time
from pathlib import Path
import cupy as cp
@@ -7,10 +8,10 @@ import cv2
import numpy as np
import zmq
-from config import PLAYBACK_SOCKET, VIDEO_WIDTH, VIDEO_HEIGHT, LIVE_SOCKET
+from config import PLAYBACK_SOCKET, VIDEO_WIDTH, VIDEO_HEIGHT, LIVE_SOCKET, IMAGING_CONFIG
from nodes.Node import Node
from utils.Msg import BMMsg, ImageArgMsg, KillMsg, SetSeqMetaMsg, SetPlayMode, SetDeviceConfigMsg, SetRecordMsg, \
- RecordFrameMsg
+ RecordFrameMsg, ImagingConfigNameListMsg
from utils.RfFile import RfFrame, RfSequenceMeta
from utils.RfMat import RfMat
from utils.RfMeta import RfFrameMeta
@@ -60,6 +61,8 @@ class Beamformer(Node):
device_socket.setsockopt(zmq.CONFLATE, 1)
self.c.poller.register(device_socket, zmq.POLLIN)
mat = None
+ time.sleep(1)
+ self.send(ImagingConfigNameListMsg([path.stem for path in IMAGING_CONFIG.glob('*.json')]))
while True:
socks = dict(self.c.poller.poll())
if device_socket in socks and socks[device_socket] == zmq.POLLIN:
diff --git a/src/nodes/Device.py b/src/nodes/Device.py
index 6a505e2..4bb2b76 100644
--- a/src/nodes/Device.py
+++ b/src/nodes/Device.py
@@ -4,7 +4,7 @@ import time
import zmq
-from config import LIVE_REP_SOCKET, CONFIG
+from config import LIVE_REP_SOCKET, CONFIG, DEVICE_CONFIG
from nodes.Node import Node
from utils.Msg import ImageArgMsg, KillMsg, SetDeviceConnectedMsg, SetDeviceEnabledMsg, DeviceEnabledMsg, \
DeviceConnectedMsg, SetDeviceConfigMsg, DeviceOnlineMsg, DeviceConfigListMsg
@@ -132,7 +132,7 @@ class Device(Node):
if self.online():
self.connected()
self.enabled()
- for f in CONFIG.glob('*.txt'):
+ for f in DEVICE_CONFIG.glob('*.txt'):
arr.append((f.stem, f.read_text()))
self.send(DeviceConfigListMsg(arr))
# if arr.__len__() > 0:
diff --git a/src/nodes/ImageFFMPEG.py b/src/nodes/ImageFFMPEG.py
new file mode 100644
index 0000000..3a79c21
--- /dev/null
+++ b/src/nodes/ImageFFMPEG.py
@@ -0,0 +1,58 @@
+import logging
+import subprocess
+import time
+
+import numpy as np
+import zmq
+
+from BusClient import BusClient
+from config import VIDEO_HEIGHT, VIDEO_WIDTH
+from nodes.Node import Node
+from utils.Msg import BMMsg, SetWindowVisibleMsg, Msg
+
+logger = logging.getLogger(__name__)
+
+
+class ImageFFMPEG(Node):
+ topics = [BMMsg, SetWindowVisibleMsg]
+
+ def __init__(self, level=logging.INFO):
+ super().__init__(level=level)
+ self.buffer = np.zeros((VIDEO_HEIGHT, VIDEO_WIDTH, 3), dtype=np.uint8) + 128
+ self.buffer = self.buffer.tobytes()
+
+ def loop(self):
+ self.c = BusClient(BMMsg, pub=False, conflare=True, poller=True)
+ p = subprocess.Popen(['ffmpeg',
+ '-f', 'rawvideo',
+ '-pixel_format', 'rgb24',
+ '-video_size', '1080x1920',
+ '-framerate', '60',
+ '-hwaccel', 'nvdec',
+ '-i', '-',
+ '-vcodec', 'h264_nvenc',
+ '-preset', 'faster',
+ '-pix_fmt', 'yuv420p',
+ # '-vcodec', 'libx264',
+ '-b:v', '40M',
+ '-f', 'flv',
+ 'rtmp://localhost/live/livestream'
+ # '-f', 'mpegts',
+ # 'srt://localhost:10080?streamid=#!::r=live/livestream,m=publish'
+ ],
+ stdin=subprocess.PIPE,
+ )
+ while True:
+ # socks = dict(self.c.poller.poll(1 / 30))
+ events = self.c.poller.poll(1)
+ if events:
+ msg: BMMsg = Msg.decode_msg(events[0][0].recv())
+ b = np.frombuffer(msg.data, dtype=np.uint8)
+ b = np.reshape(b, (VIDEO_HEIGHT, VIDEO_WIDTH, 4))[:, :, :3]
+ self.buffer = b.tobytes()
+ p.stdin.write(self.buffer)
+ time.sleep(1 / 60)
+
+
+if __name__ == '__main__':
+ ImageFFMPEG()()
diff --git a/src/nodes/MainUI.py b/src/nodes/MainUI.py
index d16bc45..d70ceeb 100644
--- a/src/nodes/MainUI.py
+++ b/src/nodes/MainUI.py
@@ -5,19 +5,18 @@ import traceback
from enum import Enum, auto
from pathlib import Path
-from PyQt6 import QtCore
-from PyQt6 import QtGui
+from PyQt6 import QtCore, QtWidgets
from PyQt6.QtCore import QByteArray
-from PyQt6.QtWidgets import QMainWindow, QApplication, QFrame, QMessageBox, QFileDialog
+from PyQt6.QtWidgets import QMainWindow, QApplication, QFrame, QMessageBox, QFileDialog, QLineEdit
from Main import Ui_MainWindow
from ZMQReceiver import ZMQReceiver
-from config import DS, SOFTWARE_CONFIG
+from config import DS, SOFTWARE_CONFIG, IMAGING_CONFIG
from nodes.Node import Node
from utils.Msg import KillMsg, Msg, ImageArgMsg, SeqIdMinMax, MoveAxisMsg, SeqListMsg, SetBaseMsg, \
SetSeqMetaMsg, SetPlayMode, DeviceConnectedMsg, DeviceEnabledMsg, DeviceOnlineMsg, SetDeviceEnabledMsg, \
SetDeviceConnectedMsg, DeviceConfigListMsg, SetDeviceConfigMsg, SetRecordMsg, RobotRtsiMsg, RecordFrameMsg, \
- SeqIdList, SetWindowVisibleMsg, SetSidMsg
+ SeqIdList, SetWindowVisibleMsg, SetSidMsg, ImagingConfigNameListMsg
from utils.RfMeta import RfSequenceMeta
logger = logging.getLogger(__name__)
@@ -95,6 +94,22 @@ class Adv(QMainWindow, Ui_MainWindow):
# self.cb_bscan.checkStateChanged.connect(lambda e: print(e == 2, flush=True))
# self.cb_bscan.checkStateChanged.connect(lambda e: print(e.name, flush=True))
+ self.b_new_imaging_config.clicked.connect(self.on_new_imaging_config)
+ self.c_imaging_config.currentIndexChanged.connect(self.on_imaging_config)
+
+ def on_imaging_config(self, i):
+ name = self.c_imaging_config.itemText(i)
+ self.p.send(ImageArgMsg.from_path(IMAGING_CONFIG / f'{name}.json'))
+
+ def on_new_imaging_config(self):
+ text, okPressed = QtWidgets.QInputDialog.getText(None,
+ "Set New Imaging Config Name",
+ "Config Name:",
+ QLineEdit.EchoMode.Normal,
+ "")
+ if okPressed and text != '':
+ (IMAGING_CONFIG / f'{text}.json').write_text(self.arg.json())
+
def on_select_base(self):
base = QFileDialog.getExistingDirectory(self, 'Select Base Folder', DS.__str__())
self.l_base.setText(Path(base).__str__())
@@ -256,6 +271,8 @@ class Adv(QMainWindow, Ui_MainWindow):
if msg.name == '':
self.close()
elif isinstance(msg, ImageArgMsg):
+ self.arg = msg
+ self.arg.sender = 'ui'
self.s_t_start.setValue(msg.t_start)
self.s_t_end.setValue(msg.t_end)
elif isinstance(msg, MoveAxisMsg):
@@ -324,6 +341,11 @@ class Adv(QMainWindow, Ui_MainWindow):
elif isinstance(msg, SetWindowVisibleMsg):
if msg.name == 'bscan' and msg.sender != 'ui':
self.cb_bscan.setChecked(msg.value)
+ elif isinstance(msg, ImagingConfigNameListMsg):
+ print(msg, flush=True)
+ self.c_imaging_config.clear()
+ for name in msg.value:
+ self.c_imaging_config.addItem(name)
except Exception as e:
logger.error(e)
traceback.print_exception(e)
@@ -377,6 +399,7 @@ class Adv(QMainWindow, Ui_MainWindow):
class MainUI(Node):
topics = [ImageArgMsg, SeqIdMinMax, MoveAxisMsg,
+ ImagingConfigNameListMsg,
SeqListMsg, SetSeqMetaMsg, SeqIdList, SetWindowVisibleMsg, SetSidMsg,
DeviceConnectedMsg, DeviceEnabledMsg, DeviceOnlineMsg, DeviceConfigListMsg,
RobotRtsiMsg,
diff --git a/src/nodes/Node.py b/src/nodes/Node.py
index e20330b..d2f5f70 100644
--- a/src/nodes/Node.py
+++ b/src/nodes/Node.py
@@ -12,10 +12,11 @@ class Node:
bp = BusClient.bp
topics = []
- def __init__(self, enable_init=True, level=logging.INFO):
+ def __init__(self, enable_init=True, level=logging.INFO, conflare=False):
self.enable_init = enable_init
self.isalive = True
self.level = level
+ self.conflare = conflare
def recv(self):
return self.c.recv()
@@ -33,6 +34,6 @@ class Node:
logging.basicConfig(level=self.level, format=FORMAT)
self.context = zmq.Context()
if self.enable_init:
- self.c = BusClient(*([KillMsg] + self.topics), poller=True)
+ self.c = BusClient(*([KillMsg] + self.topics), poller=True, conflare=self.conflare)
self.loop()
print(self.__class__.__name__, 'exiting')
diff --git a/src/ui/Main.py b/src/ui/Main.py
index e44b38e..23acc0a 100644
--- a/src/ui/Main.py
+++ b/src/ui/Main.py
@@ -12,7 +12,7 @@ from PyQt6 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
- MainWindow.resize(936, 732)
+ MainWindow.resize(1158, 805)
self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
@@ -182,31 +182,24 @@ class Ui_MainWindow(object):
self.gridLayout.addLayout(self.gridLayout_4, 2, 1, 1, 1)
self.gridLayout_5 = QtWidgets.QGridLayout()
self.gridLayout_5.setObjectName("gridLayout_5")
- self.label_5 = QtWidgets.QLabel(parent=self.centralwidget)
- self.label_5.setObjectName("label_5")
- self.gridLayout_5.addWidget(self.label_5, 2, 0, 1, 1)
+ self.spinBox_3 = QtWidgets.QSpinBox(parent=self.centralwidget)
+ self.spinBox_3.setObjectName("spinBox_3")
+ self.gridLayout_5.addWidget(self.spinBox_3, 6, 2, 1, 1)
self.label_7 = QtWidgets.QLabel(parent=self.centralwidget)
self.label_7.setObjectName("label_7")
- self.gridLayout_5.addWidget(self.label_7, 4, 0, 1, 1)
- self.s_t_start = QtWidgets.QSlider(parent=self.centralwidget)
- self.s_t_start.setOrientation(QtCore.Qt.Orientation.Horizontal)
- self.s_t_start.setObjectName("s_t_start")
- self.gridLayout_5.addWidget(self.s_t_start, 1, 1, 1, 1)
+ self.gridLayout_5.addWidget(self.label_7, 6, 0, 1, 1)
+ self.spinBox_12 = QtWidgets.QSpinBox(parent=self.centralwidget)
+ self.spinBox_12.setObjectName("spinBox_12")
+ self.gridLayout_5.addWidget(self.spinBox_12, 3, 2, 1, 1)
+ self.spinBox_2 = QtWidgets.QSpinBox(parent=self.centralwidget)
+ self.spinBox_2.setObjectName("spinBox_2")
+ self.gridLayout_5.addWidget(self.spinBox_2, 5, 2, 1, 1)
self.horizontalSlider_2 = QtWidgets.QSlider(parent=self.centralwidget)
self.horizontalSlider_2.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.horizontalSlider_2.setObjectName("horizontalSlider_2")
- self.gridLayout_5.addWidget(self.horizontalSlider_2, 3, 1, 1, 1)
- self.spinBox_12 = QtWidgets.QSpinBox(parent=self.centralwidget)
- self.spinBox_12.setObjectName("spinBox_12")
- self.gridLayout_5.addWidget(self.spinBox_12, 1, 2, 1, 1)
+ self.gridLayout_5.addWidget(self.horizontalSlider_2, 5, 1, 1, 1)
spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
- self.gridLayout_5.addItem(spacerItem1, 5, 1, 1, 1)
- self.spinBox_2 = QtWidgets.QSpinBox(parent=self.centralwidget)
- self.spinBox_2.setObjectName("spinBox_2")
- self.gridLayout_5.addWidget(self.spinBox_2, 3, 2, 1, 1)
- self.spinBox_3 = QtWidgets.QSpinBox(parent=self.centralwidget)
- self.spinBox_3.setObjectName("spinBox_3")
- self.gridLayout_5.addWidget(self.spinBox_3, 4, 2, 1, 1)
+ self.gridLayout_5.addItem(spacerItem1, 7, 1, 1, 1)
self.label_2 = QtWidgets.QLabel(parent=self.centralwidget)
font = QtGui.QFont()
font.setPointSize(20)
@@ -215,25 +208,45 @@ class Ui_MainWindow(object):
self.label_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.label_2.setObjectName("label_2")
self.gridLayout_5.addWidget(self.label_2, 0, 0, 1, 3)
- self.spinBox = QtWidgets.QSpinBox(parent=self.centralwidget)
- self.spinBox.setObjectName("spinBox")
- self.gridLayout_5.addWidget(self.spinBox, 2, 2, 1, 1)
self.s_t_end = QtWidgets.QSlider(parent=self.centralwidget)
self.s_t_end.setMinimum(1)
self.s_t_end.setMaximum(1500)
self.s_t_end.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.s_t_end.setObjectName("s_t_end")
- self.gridLayout_5.addWidget(self.s_t_end, 2, 1, 1, 1)
+ self.gridLayout_5.addWidget(self.s_t_end, 4, 1, 1, 1)
+ self.label_6 = QtWidgets.QLabel(parent=self.centralwidget)
+ self.label_6.setObjectName("label_6")
+ self.gridLayout_5.addWidget(self.label_6, 5, 0, 1, 1)
+ self.spinBox = QtWidgets.QSpinBox(parent=self.centralwidget)
+ self.spinBox.setObjectName("spinBox")
+ self.gridLayout_5.addWidget(self.spinBox, 4, 2, 1, 1)
self.label_15 = QtWidgets.QLabel(parent=self.centralwidget)
self.label_15.setObjectName("label_15")
- self.gridLayout_5.addWidget(self.label_15, 1, 0, 1, 1)
+ self.gridLayout_5.addWidget(self.label_15, 3, 0, 1, 1)
+ self.s_t_start = QtWidgets.QSlider(parent=self.centralwidget)
+ self.s_t_start.setOrientation(QtCore.Qt.Orientation.Horizontal)
+ self.s_t_start.setObjectName("s_t_start")
+ self.gridLayout_5.addWidget(self.s_t_start, 3, 1, 1, 1)
+ self.label_5 = QtWidgets.QLabel(parent=self.centralwidget)
+ self.label_5.setObjectName("label_5")
+ self.gridLayout_5.addWidget(self.label_5, 4, 0, 1, 1)
self.horizontalSlider_4 = QtWidgets.QSlider(parent=self.centralwidget)
self.horizontalSlider_4.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.horizontalSlider_4.setObjectName("horizontalSlider_4")
- self.gridLayout_5.addWidget(self.horizontalSlider_4, 4, 1, 1, 1)
- self.label_6 = QtWidgets.QLabel(parent=self.centralwidget)
- self.label_6.setObjectName("label_6")
- self.gridLayout_5.addWidget(self.label_6, 3, 0, 1, 1)
+ self.gridLayout_5.addWidget(self.horizontalSlider_4, 6, 1, 1, 1)
+ self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_3.setObjectName("horizontalLayout_3")
+ self.label_27 = QtWidgets.QLabel(parent=self.centralwidget)
+ self.label_27.setObjectName("label_27")
+ self.horizontalLayout_3.addWidget(self.label_27)
+ self.c_imaging_config = QtWidgets.QComboBox(parent=self.centralwidget)
+ self.c_imaging_config.setObjectName("c_imaging_config")
+ self.horizontalLayout_3.addWidget(self.c_imaging_config)
+ self.b_new_imaging_config = QtWidgets.QPushButton(parent=self.centralwidget)
+ self.b_new_imaging_config.setObjectName("b_new_imaging_config")
+ self.horizontalLayout_3.addWidget(self.b_new_imaging_config)
+ self.horizontalLayout_3.setStretch(1, 1)
+ self.gridLayout_5.addLayout(self.horizontalLayout_3, 2, 0, 1, 3)
self.gridLayout.addLayout(self.gridLayout_5, 2, 0, 1, 1)
self.gridLayout_7 = QtWidgets.QGridLayout()
self.gridLayout_7.setObjectName("gridLayout_7")
@@ -358,7 +371,7 @@ class Ui_MainWindow(object):
self.gridLayout.addLayout(self.horizontalLayout_6, 4, 0, 1, 2)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
- self.menubar.setGeometry(QtCore.QRect(0, 0, 936, 30))
+ self.menubar.setGeometry(QtCore.QRect(0, 0, 1158, 30))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
@@ -393,11 +406,13 @@ class Ui_MainWindow(object):
self.label_12.setText(_translate("MainWindow", "Roll"))
self.label_11.setText(_translate("MainWindow", "E"))
self.label_8.setText(_translate("MainWindow", "X"))
- self.label_5.setText(_translate("MainWindow", "t_end"))
self.label_7.setText(_translate("MainWindow", "TextLabel"))
self.label_2.setText(_translate("MainWindow", "Imaging"))
- self.label_15.setText(_translate("MainWindow", "t_start"))
self.label_6.setText(_translate("MainWindow", "TextLabel"))
+ self.label_15.setText(_translate("MainWindow", "t_start"))
+ self.label_5.setText(_translate("MainWindow", "t_end"))
+ self.label_27.setText(_translate("MainWindow", "Profile"))
+ self.b_new_imaging_config.setText(_translate("MainWindow", "New"))
self.b_play_playback.setText(_translate("MainWindow", "Playback"))
self.label_16.setText(_translate("MainWindow", "SeqName"))
self.label_4.setText(_translate("MainWindow", "Frame ID"))
diff --git a/src/ui/Main.ui b/src/ui/Main.ui
index 603846c..4eaf95e 100644
--- a/src/ui/Main.ui
+++ b/src/ui/Main.ui
@@ -6,8 +6,8 @@
0
0
- 936
- 732
+ 1158
+ 805
@@ -337,38 +337,30 @@
-
-
-
-
-
- t_end
-
-
+
-
+
- -
+
-
TextLabel
- -
-
-
- Qt::Orientation::Horizontal
-
-
+
-
+
- -
+
-
+
+
+ -
Qt::Orientation::Horizontal
- -
-
-
- -
+
-
Qt::Orientation::Vertical
@@ -381,12 +373,6 @@
- -
-
-
- -
-
-
-
@@ -403,10 +389,7 @@
- -
-
-
- -
+
-
1
@@ -419,26 +402,64 @@
- -
+
-
+
+
+ TextLabel
+
+
+
+ -
+
+
+ -
t_start
- -
+
-
+
+
+ Qt::Orientation::Horizontal
+
+
+
+ -
+
+
+ t_end
+
+
+
+ -
Qt::Orientation::Horizontal
- -
-
-
- TextLabel
-
-
+
-
+
+
-
+
+
+ Profile
+
+
+
+ -
+
+
+ -
+
+
+ New
+
+
+
+
@@ -701,7 +722,7 @@
0
0
- 936
+ 1158
30
diff --git a/src/utils/Msg.py b/src/utils/Msg.py
index 9e4dc39..ecdd469 100644
--- a/src/utils/Msg.py
+++ b/src/utils/Msg.py
@@ -2,6 +2,7 @@ import dataclasses
import json
import struct
from enum import auto, Enum
+from pathlib import Path
class BG(Enum):
@@ -31,6 +32,7 @@ class BG(Enum):
SeqIdList = auto()
SetWindowVisibleMsg = auto()
SetSidMsg = auto()
+ ImagingConfigNameListMsg = auto()
class Msg:
@@ -142,6 +144,10 @@ class SeqListMsg(Msg):
value: list[str]
+@dataclasses.dataclass
+class ImagingConfigNameListMsg(Msg):
+ value: list[str]
+
class SetPlayMode(StrMsg):
pass
@@ -177,6 +183,21 @@ class ImageArgMsg(Msg):
t_start: int
t_end: int
+ @staticmethod
+ def from_path(p: Path):
+ j = json.loads(p.read_text())
+ return ImageArgMsg(
+ 'load',
+ t_start=j['t_start'],
+ t_end=j['t_end'],
+ )
+
+ def json(self):
+ return json.dumps(dict(
+ t_start=self.t_start,
+ t_end=self.t_end,
+ ))
+
@dataclasses.dataclass
class DeviceConfigListMsg(Msg):
@@ -188,10 +209,12 @@ class RecordFrameMsg(Msg):
size: int
current_sid: int
+
@dataclasses.dataclass
class SetSidMsg(Msg):
value: int
+
class BMMsg(Msg):
def __init__(self, t: int, data: bytes):
self.data = data
@@ -217,7 +240,7 @@ class RobotRtsiMsg(Msg):
def test():
values = set(item.name for item in BG)
for k in globals().keys():
- if k.endswith('Msg') and k not in ['Msg','BoolMsg']:
+ if k.endswith('Msg') and k not in ['Msg', 'BoolMsg']:
if k not in values:
raise RuntimeError(f"Unknown msg type: {k}")
diff --git a/test/avencode.py b/test/avencode.py
new file mode 100644
index 0000000..764a3a1
--- /dev/null
+++ b/test/avencode.py
@@ -0,0 +1,182 @@
+import fractions
+import logging
+from typing import cast
+
+import av
+import numpy as np
+from av import VideoCodecContext, VideoFrame
+
+# for e in av.codecs_available:
+# print(e)
+logging.basicConfig(level=logging.DEBUG)
+logging.getLogger('libav').setLevel(logging.DEBUG)
+MAX_FRAME_RATE = 60
+
+
+def f0():
+ # input_ = av.open('asd','w')
+ # in_stream = input_.streams.video[0]
+ # libx264
+ codec: VideoCodecContext = av.CodecContext.create('libx264', "w")
+
+ codec.width = 640
+ codec.height = 480
+ codec.pix_fmt = 'yuv420p' # 常用格式, 大部分解码器都支持
+
+ # codec.width = 100
+ # codec.height = 100
+ # codec.bit_rate = 100000
+ # codec.pix_fmt = "yuv420p"
+ # codec.framerate = fractions.Fraction(MAX_FRAME_RATE, 1)
+ # codec.time_base = fractions.Fraction(1, MAX_FRAME_RATE)
+ # codec.options = {
+ # "profile": "baseline",
+ # "level": "31",
+ # "tune": "zerolatency",
+ # }
+ codec.open()
+ pts = 0
+ while True:
+ f = VideoFrame.from_ndarray(np.zeros((640, 480, 3), dtype=np.uint8))
+ pts += 1
+ f.pts = pts
+ r = codec.encode(f)
+ if r.__len__() > 0:
+ print(r[0].__buffer__(0).__len__())
+
+
+def f1():
+ import av
+
+ # 创建一个输出容器
+ output = av.open('output.mp4', 'w')
+
+ # 添加一个视频流
+ stream = output.add_stream('libx264', rate=24)
+ stream.width = 640
+ stream.height = 480
+ stream.pix_fmt = 'yuv420p'
+
+ # 编码循环
+ for i in range(100):
+ frame = av.VideoFrame(width=640, height=480, format='rgb24')
+ # ... 对帧进行处理 ...
+ for packet in stream.encode(frame):
+ output.mux(packet)
+
+ # 完成编码
+ for packet in stream.encode():
+ output.mux(packet)
+
+ # 关闭输出容器
+ output.close()
+
+
+def f2():
+ import av
+ from av import VideoFrame
+ from fractions import Fraction
+
+ # 1. 创建输出容器
+ output_path = 'output.mp4'
+ output = av.open(output_path, 'w')
+
+ # 2. 创建视频流(使用 libx264 编码器)
+ stream = output.add_stream('libx264', rate=24) # 帧率
+
+ # 3. 设置编解码器上下文参数
+ stream.width = 640
+ stream.height = 480
+ stream.pix_fmt = 'yuv420p' # 常用格式, 大部分解码器都支持
+ stream.codec_context.time_base = Fraction(1, 24)
+
+ # 4. 手动打开编解码器上下文 (可选, add_stream 已经完成了这一步)
+ stream.codec_context.open()
+
+ # 5. 编码循环
+ for i in range(100): # 生成 100 帧
+ frame = av.VideoFrame(width=640, height=480, format='rgb24')
+
+ # 生成测试数据
+ # 在实际应用中,您需要从图像源获取数据
+ import numpy as np
+ import colorsys
+ cx = int(640 / 100 * i)
+ cy = int(480 / 100 * i)
+ rgb = (np.array(colorsys.hsv_to_rgb(i / 100.0, 1.0, 1.0)) * 255).astype(np.uint8)
+
+ data = np.zeros((frame.height, frame.width, 3), dtype=np.uint8)
+ data[cy - 10:cy + 10, cx - 10:cx + 10, :] = rgb
+ frame.planes[0].update(data)
+
+ for packet in stream.encode(frame):
+ output.mux(packet)
+
+ # 6. 刷新编码器
+ for packet in stream.encode():
+ output.mux(packet)
+
+ # 7. 关闭容器
+ output.close()
+
+
+def f3():
+ import av
+ from av import VideoFrame
+ from fractions import Fraction
+
+ # 1. 创建输出容器
+ output_path = 'output.mp4'
+ output = av.open(output_path, 'w')
+
+ # 2. 获取编码器codec
+ codec = av.Codec('libx264', "w")
+
+ # 3. 手动创建并配置编解码器上下文
+ ctx = av.CodecContext.create(codec, mode="w")
+ ctx.width = 640
+ ctx.height = 480
+ ctx.pix_fmt = 'yuv420p' # 常用格式, 大部分解码器都支持
+ ctx.time_base = Fraction(1, 24)
+
+ # 4. 添加视频流 (使用创建好的编解码器上下文).
+ stream = output.add_stream('libx264', rate=24) # 使用编码器名称
+ stream.codec_context = ctx # 将之前创建好的 ctx 赋值给 stream
+ stream.width = ctx.width # 宽度
+ stream.height = ctx.height
+ stream.pix_fmt = ctx.pix_fmt
+
+ # 5. 手动打开编解码器上下文 (在添加到 stream 后会自动打开)
+ ctx.open()
+
+ # 6. 编码循环
+ for i in range(100): # 生成 100 帧
+ frame = av.VideoFrame(width=640, height=480, format='rgb24')
+
+ # 生成测试数据
+ # 在实际应用中,您需要从图像源获取数据
+ import numpy as np
+ import colorsys
+ cx = int(640 / 100 * i)
+ cy = int(480 / 100 * i)
+ rgb = (np.array(colorsys.hsv_to_rgb(i / 100.0, 1.0, 1.0)) * 255).astype(np.uint8)
+
+ data = np.zeros((frame.height, frame.width, 3), dtype=np.uint8)
+ data[cy - 10:cy + 10, cx - 10:cx + 10, :] = rgb
+ frame.planes[0].update(data)
+
+ for packet in stream.encode(frame):
+ output.mux(packet)
+
+ # 7. 刷新编码器
+ for packet in stream.encode(None):
+ output.mux(packet)
+
+ # 8. 关闭容器
+ output.close()
+
+ print(f"视频已保存到 {output_path}")
+
+
+if __name__ == '__main__':
+ f0()
diff --git a/test/kdemain.py b/test/kdemain.py
index 66cc2cd..f04d9d1 100644
--- a/test/kdemain.py
+++ b/test/kdemain.py
@@ -19,7 +19,7 @@ if __name__ == '__main__':
pps = []
ps = [
Broker(),
- WebRTC(),
+ # WebRTC(),
kde_pyqt6_mainui,
Device(level=logging.DEBUG),
ImageCV(level=logging.DEBUG),
diff --git a/test/rtc.py b/test/rtc.py
new file mode 100644
index 0000000..93262f1
--- /dev/null
+++ b/test/rtc.py
@@ -0,0 +1,69 @@
+import asyncio
+import json
+import logging
+import os
+
+import aiohttp_cors
+from aiohttp import web
+from aiortc import MediaStreamTrack, RTCPeerConnection, RTCSessionDescription, RTCConfiguration, RTCRtpCodecCapability
+from aiortc.contrib.media import MediaBlackhole, MediaPlayer, MediaRecorder, MediaRelay
+
+ROOT = os.path.dirname(__file__)
+
+web.WebSocketResponse()
+logger = logging.getLogger(__name__)
+
+pcs = set()
+
+
+async def offer(request):
+ params = await request.json()
+ offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"])
+ pc = RTCPeerConnection(RTCConfiguration([]))
+ pcs.add(pc)
+ player = MediaPlayer(os.path.join(ROOT, "demo-instruct.wav"))
+ rc = pc.addTransceiver(player.video, 'sendonly')
+ rc.setCodecPreferences([RTCRtpCodecCapability(mimeType='video/H264',
+ clockRate=90000,
+ channels=None,
+ parameters={
+ 'level-asymmetry-allowed': '1',
+ 'packetization-mode': '1',
+ 'profile-level-id': '42e01f'
+ })])
+ await pc.setRemoteDescription(offer)
+ answer = await pc.createAnswer()
+ await pc.setLocalDescription(answer)
+ return web.Response(
+ content_type="application/json",
+ text=json.dumps(
+ {"sdp": pc.localDescription.sdp, "type": pc.localDescription.type}
+ ),
+ )
+
+
+async def on_shutdown(app):
+ # close peer connections
+ coros = [pc.close() for pc in pcs]
+ await asyncio.gather(*coros)
+ pcs.clear()
+
+
+if __name__ == '__main__':
+ app = web.Application()
+ app.on_shutdown.append(on_shutdown)
+ app.router.add_post("/offer", offer)
+
+ cors = aiohttp_cors.setup(app, defaults={
+ "*": aiohttp_cors.ResourceOptions(
+ allow_credentials=True,
+ expose_headers="*",
+ allow_headers="*"
+ )
+ })
+
+ for route in list(app.router.routes()):
+ cors.add(route)
+ web.run_app(
+ app, access_log=None, host='0.0.0.0', port=8081
+ )