From d5ebfb4b8a1ca43badd0ccf74c9433fd550816b8 Mon Sep 17 00:00:00 2001 From: flandre Date: Thu, 20 Feb 2025 14:06:23 +0800 Subject: [PATCH] fix many --- .../A60,U=60,M=PWI,S=(64 3001).txt | 0 .../A80,U=80,M=PWI,S=(64 4001).txt | 0 .../ARIA64,U=30,M=PWI,S=(64 1501).txt | 0 .../ARIA641,U=30,M=PWI,S=(64 1501).txt | 0 src/config.py | 6 +- src/nodes/Beamformer.py | 7 +- src/nodes/Device.py | 4 +- src/nodes/ImageFFMPEG.py | 58 ++++++ src/nodes/MainUI.py | 33 +++- src/nodes/Node.py | 5 +- src/ui/Main.py | 79 +++++--- src/ui/Main.ui | 99 ++++++---- src/utils/Msg.py | 25 ++- test/avencode.py | 182 ++++++++++++++++++ test/kdemain.py | 2 +- test/rtc.py | 69 +++++++ 16 files changed, 484 insertions(+), 85 deletions(-) rename config/{ => device}/A60,U=60,M=PWI,S=(64 3001).txt (100%) rename config/{ => device}/A80,U=80,M=PWI,S=(64 4001).txt (100%) rename config/{ => device}/ARIA64,U=30,M=PWI,S=(64 1501).txt (100%) rename config/{ => device}/ARIA641,U=30,M=PWI,S=(64 1501).txt (100%) create mode 100644 src/nodes/ImageFFMPEG.py create mode 100644 test/avencode.py create mode 100644 test/rtc.py 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 + )