This commit is contained in:
flandre 2025-02-20 14:06:23 +08:00
parent 8427211b78
commit d5ebfb4b8a
16 changed files with 484 additions and 85 deletions

View File

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

View File

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

View File

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

58
src/nodes/ImageFFMPEG.py Normal file
View File

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

View File

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

View File

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

View File

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

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>936</width>
<height>732</height>
<width>1158</width>
<height>805</height>
</rect>
</property>
<property name="windowTitle">
@ -337,38 +337,30 @@
</item>
<item row="2" column="0">
<layout class="QGridLayout" name="gridLayout_5">
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>t_end</string>
</property>
</widget>
<item row="6" column="2">
<widget class="QSpinBox" name="spinBox_3"/>
</item>
<item row="4" column="0">
<item row="6" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSlider" name="s_t_start">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
<item row="3" column="2">
<widget class="QSpinBox" name="spinBox_12"/>
</item>
<item row="3" column="1">
<item row="5" column="2">
<widget class="QSpinBox" name="spinBox_2"/>
</item>
<item row="5" column="1">
<widget class="QSlider" name="horizontalSlider_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QSpinBox" name="spinBox_12"/>
</item>
<item row="5" column="1">
<item row="7" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
@ -381,12 +373,6 @@
</property>
</spacer>
</item>
<item row="3" column="2">
<widget class="QSpinBox" name="spinBox_2"/>
</item>
<item row="4" column="2">
<widget class="QSpinBox" name="spinBox_3"/>
</item>
<item row="0" column="0" colspan="3">
<widget class="QLabel" name="label_2">
<property name="font">
@ -403,10 +389,7 @@
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QSpinBox" name="spinBox"/>
</item>
<item row="2" column="1">
<item row="4" column="1">
<widget class="QSlider" name="s_t_end">
<property name="minimum">
<number>1</number>
@ -419,26 +402,64 @@
</property>
</widget>
</item>
<item row="1" column="0">
<item row="5" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QSpinBox" name="spinBox"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>t_start</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="3" column="1">
<widget class="QSlider" name="s_t_start">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>t_end</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSlider" name="horizontalSlider_4">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<item row="2" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="0,1,0">
<item>
<widget class="QLabel" name="label_27">
<property name="text">
<string>Profile</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="c_imaging_config"/>
</item>
<item>
<widget class="QPushButton" name="b_new_imaging_config">
<property name="text">
<string>New</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
@ -701,7 +722,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>936</width>
<width>1158</width>
<height>30</height>
</rect>
</property>

View File

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

182
test/avencode.py Normal file
View File

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

View File

@ -19,7 +19,7 @@ if __name__ == '__main__':
pps = []
ps = [
Broker(),
WebRTC(),
# WebRTC(),
kde_pyqt6_mainui,
Device(level=logging.DEBUG),
ImageCV(level=logging.DEBUG),

69
test/rtc.py Normal file
View File

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