init
This commit is contained in:
commit
b277145104
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.idea
|
||||||
|
.venv
|
||||||
|
bak
|
||||||
|
__pycache__
|
||||||
24
pyproject.toml
Normal file
24
pyproject.toml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
[project]
|
||||||
|
name = "flandre"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"aiohttp-cors>=0.7.0",
|
||||||
|
"aiortc>=1.9.0",
|
||||||
|
"cupy-cuda12x>=13.3.0",
|
||||||
|
"opencv-python>=4.10.0.84",
|
||||||
|
"pyqt6-fluent-widgets[full]>=1.7.4",
|
||||||
|
"pyqt6>=6.8.0",
|
||||||
|
"pyzmq>=26.2.0",
|
||||||
|
"scipy>=1.14.1",
|
||||||
|
"tqdm>=4.67.1",
|
||||||
|
"vtk>=9.4.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
no-binary-package = ["av"]
|
||||||
|
override-dependencies = [
|
||||||
|
"av>=14.0.0",
|
||||||
|
]
|
||||||
49
src/BusClient.py
Normal file
49
src/BusClient.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import zmq
|
||||||
|
from zmq import Context
|
||||||
|
|
||||||
|
from Msg import Msg
|
||||||
|
|
||||||
|
|
||||||
|
class BusClient:
|
||||||
|
fp = 5001
|
||||||
|
bp = 5002
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
*msgs: type(Msg),
|
||||||
|
ctx=None,
|
||||||
|
pub=True,
|
||||||
|
sub=True,
|
||||||
|
conflare=False,
|
||||||
|
poller=False
|
||||||
|
):
|
||||||
|
if ctx is None:
|
||||||
|
self.ctx: Context = zmq.Context()
|
||||||
|
else:
|
||||||
|
self.ctx = ctx
|
||||||
|
if sub:
|
||||||
|
self.sub = self.ctx.socket(zmq.SUB)
|
||||||
|
for msg in msgs:
|
||||||
|
self.sub.setsockopt(zmq.SUBSCRIBE, msg.eid())
|
||||||
|
if conflare:
|
||||||
|
self.sub.setsockopt(zmq.CONFLATE, 1)
|
||||||
|
self.sub.connect(f'tcp://127.0.0.1:{self.bp}')
|
||||||
|
if poller:
|
||||||
|
self.poller = zmq.Poller()
|
||||||
|
self.poller.register(self.sub, zmq.POLLIN)
|
||||||
|
if pub:
|
||||||
|
self.pub = self.ctx.socket(zmq.PUB)
|
||||||
|
self.pub.connect(f'tcp://127.0.0.1:{self.fp}')
|
||||||
|
|
||||||
|
def recv(self):
|
||||||
|
b = self.sub.recv()
|
||||||
|
return Msg.decode_msg(b)
|
||||||
|
|
||||||
|
def send(self, msg: Msg):
|
||||||
|
return self.pub.send(msg.encode_msg())
|
||||||
|
|
||||||
|
async def recv_async(self):
|
||||||
|
b = await self.sub.recv()
|
||||||
|
return Msg.decode_msg(b)
|
||||||
|
|
||||||
|
async def send_async(self, msg: Msg):
|
||||||
|
return self.pub.send(msg.encode_msg())
|
||||||
375
src/H264NV.py
Normal file
375
src/H264NV.py
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
import fractions
|
||||||
|
import logging
|
||||||
|
import math
|
||||||
|
from itertools import tee
|
||||||
|
from struct import pack, unpack_from
|
||||||
|
from typing import Iterator, List, Optional, Sequence, Tuple, Type, TypeVar
|
||||||
|
|
||||||
|
import av
|
||||||
|
from aiortc.jitterbuffer import JitterFrame
|
||||||
|
from aiortc.mediastreams import VIDEO_TIME_BASE, convert_timebase
|
||||||
|
from aiortc.codecs.base import Decoder, Encoder
|
||||||
|
|
||||||
|
from av.frame import Frame
|
||||||
|
from av.packet import Packet
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
gpu = '0'
|
||||||
|
DEFAULT_BITRATE = 1000000 # 1 Mbps
|
||||||
|
MIN_BITRATE = 500000 # 500 kbps
|
||||||
|
# MAX_BITRATE = 3000000 # 3 Mbps
|
||||||
|
MAX_BITRATE = 10000000 # 3 Mbps
|
||||||
|
|
||||||
|
MAX_FRAME_RATE = 120
|
||||||
|
PACKET_MAX = 1300
|
||||||
|
|
||||||
|
NAL_TYPE_FU_A = 28
|
||||||
|
NAL_TYPE_STAP_A = 24
|
||||||
|
|
||||||
|
NAL_HEADER_SIZE = 1
|
||||||
|
FU_A_HEADER_SIZE = 2
|
||||||
|
LENGTH_FIELD_SIZE = 2
|
||||||
|
STAP_A_HEADER_SIZE = NAL_HEADER_SIZE + LENGTH_FIELD_SIZE
|
||||||
|
|
||||||
|
DESCRIPTOR_T = TypeVar("DESCRIPTOR_T", bound="H264PayloadDescriptor")
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
def pairwise(iterable: Sequence[T]) -> Iterator[Tuple[T, T]]:
|
||||||
|
a, b = tee(iterable)
|
||||||
|
next(b, None)
|
||||||
|
return zip(a, b)
|
||||||
|
|
||||||
|
|
||||||
|
class H264PayloadDescriptor:
|
||||||
|
def __init__(self, first_fragment):
|
||||||
|
self.first_fragment = first_fragment
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"H264PayloadDescriptor(FF={self.first_fragment})"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse(cls: Type[DESCRIPTOR_T], data: bytes) -> Tuple[DESCRIPTOR_T, bytes]:
|
||||||
|
output = bytes()
|
||||||
|
|
||||||
|
# NAL unit header
|
||||||
|
if len(data) < 2:
|
||||||
|
raise ValueError("NAL unit is too short")
|
||||||
|
nal_type = data[0] & 0x1F
|
||||||
|
f_nri = data[0] & (0x80 | 0x60)
|
||||||
|
pos = NAL_HEADER_SIZE
|
||||||
|
|
||||||
|
if nal_type in range(1, 24):
|
||||||
|
# single NAL unit
|
||||||
|
output = bytes([0, 0, 0, 1]) + data
|
||||||
|
obj = cls(first_fragment=True)
|
||||||
|
elif nal_type == NAL_TYPE_FU_A:
|
||||||
|
# fragmentation unit
|
||||||
|
original_nal_type = data[pos] & 0x1F
|
||||||
|
first_fragment = bool(data[pos] & 0x80)
|
||||||
|
pos += 1
|
||||||
|
|
||||||
|
if first_fragment:
|
||||||
|
original_nal_header = bytes([f_nri | original_nal_type])
|
||||||
|
output += bytes([0, 0, 0, 1])
|
||||||
|
output += original_nal_header
|
||||||
|
output += data[pos:]
|
||||||
|
|
||||||
|
obj = cls(first_fragment=first_fragment)
|
||||||
|
elif nal_type == NAL_TYPE_STAP_A:
|
||||||
|
# single time aggregation packet
|
||||||
|
offsets = []
|
||||||
|
while pos < len(data):
|
||||||
|
if len(data) < pos + LENGTH_FIELD_SIZE:
|
||||||
|
raise ValueError("STAP-A length field is truncated")
|
||||||
|
nalu_size = unpack_from("!H", data, pos)[0]
|
||||||
|
pos += LENGTH_FIELD_SIZE
|
||||||
|
offsets.append(pos)
|
||||||
|
|
||||||
|
pos += nalu_size
|
||||||
|
if len(data) < pos:
|
||||||
|
raise ValueError("STAP-A data is truncated")
|
||||||
|
|
||||||
|
offsets.append(len(data) + LENGTH_FIELD_SIZE)
|
||||||
|
for start, end in pairwise(offsets):
|
||||||
|
end -= LENGTH_FIELD_SIZE
|
||||||
|
output += bytes([0, 0, 0, 1])
|
||||||
|
output += data[start:end]
|
||||||
|
|
||||||
|
obj = cls(first_fragment=True)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"NAL unit type {nal_type} is not supported")
|
||||||
|
|
||||||
|
return obj, output
|
||||||
|
|
||||||
|
|
||||||
|
class H264Decoder(Decoder):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.codec = av.CodecContext.create("h264", "r")
|
||||||
|
|
||||||
|
def decode(self, encoded_frame: JitterFrame) -> List[Frame]:
|
||||||
|
try:
|
||||||
|
packet = av.Packet(encoded_frame.data)
|
||||||
|
packet.pts = encoded_frame.timestamp
|
||||||
|
packet.time_base = VIDEO_TIME_BASE
|
||||||
|
frames = self.codec.decode(packet)
|
||||||
|
except av.AVError as e:
|
||||||
|
logger.warning(
|
||||||
|
"H264Decoder() failed to decode, skipping package: " + str(e)
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
|
return frames
|
||||||
|
|
||||||
|
|
||||||
|
assert "h264_nvenc" in av.codecs_available
|
||||||
|
|
||||||
|
|
||||||
|
def create_encoder_context(
|
||||||
|
codec_name: str, width: int, height: int, bitrate: int
|
||||||
|
) -> Tuple[av.CodecContext, bool]:
|
||||||
|
# codec = av.CodecContext.create('libx264', "w")
|
||||||
|
codec = av.CodecContext.create('h264_nvenc', "w")
|
||||||
|
codec.width = width
|
||||||
|
codec.height = height
|
||||||
|
codec.bit_rate = bitrate
|
||||||
|
# codec.bit_rate = 6000000
|
||||||
|
|
||||||
|
codec.pix_fmt = "yuv420p"
|
||||||
|
codec.framerate = fractions.Fraction(MAX_FRAME_RATE, 1)
|
||||||
|
codec.time_base = fractions.Fraction(1, MAX_FRAME_RATE)
|
||||||
|
codec.options = dict(
|
||||||
|
# delay='0',
|
||||||
|
# crf='1',
|
||||||
|
# profile="baseline",
|
||||||
|
# level="31",
|
||||||
|
# tune="zerolatency", # does nothing using h264_omx
|
||||||
|
gpu=gpu,
|
||||||
|
preset='fast',
|
||||||
|
)
|
||||||
|
|
||||||
|
# codec.options = {
|
||||||
|
# "profile": "baseline",
|
||||||
|
# "level": "31",
|
||||||
|
# "tune": "zerolatency", # does nothing using h264_omx
|
||||||
|
# }
|
||||||
|
codec.open()
|
||||||
|
return codec, True
|
||||||
|
return codec, codec_name == "h264_omx"
|
||||||
|
|
||||||
|
|
||||||
|
class H264NVENCEncoder(Encoder):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.buffer_data = b""
|
||||||
|
self.buffer_pts: Optional[int] = None
|
||||||
|
self.codec: Optional[av.CodecContext] = None
|
||||||
|
self.codec_buffering = False
|
||||||
|
self.__target_bitrate = MAX_BITRATE
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _packetize_fu_a(data: bytes) -> List[bytes]:
|
||||||
|
available_size = PACKET_MAX - FU_A_HEADER_SIZE
|
||||||
|
payload_size = len(data) - NAL_HEADER_SIZE
|
||||||
|
num_packets = math.ceil(payload_size / available_size)
|
||||||
|
num_larger_packets = payload_size % num_packets
|
||||||
|
package_size = payload_size // num_packets
|
||||||
|
|
||||||
|
f_nri = data[0] & (0x80 | 0x60) # fni of original header
|
||||||
|
nal = data[0] & 0x1F
|
||||||
|
|
||||||
|
fu_indicator = f_nri | NAL_TYPE_FU_A
|
||||||
|
|
||||||
|
fu_header_end = bytes([fu_indicator, nal | 0x40])
|
||||||
|
fu_header_middle = bytes([fu_indicator, nal])
|
||||||
|
fu_header_start = bytes([fu_indicator, nal | 0x80])
|
||||||
|
fu_header = fu_header_start
|
||||||
|
|
||||||
|
packages = []
|
||||||
|
offset = NAL_HEADER_SIZE
|
||||||
|
while offset < len(data):
|
||||||
|
if num_larger_packets > 0:
|
||||||
|
num_larger_packets -= 1
|
||||||
|
payload = data[offset: offset + package_size + 1]
|
||||||
|
offset += package_size + 1
|
||||||
|
else:
|
||||||
|
payload = data[offset: offset + package_size]
|
||||||
|
offset += package_size
|
||||||
|
|
||||||
|
if offset == len(data):
|
||||||
|
fu_header = fu_header_end
|
||||||
|
|
||||||
|
packages.append(fu_header + payload)
|
||||||
|
|
||||||
|
fu_header = fu_header_middle
|
||||||
|
assert offset == len(data), "incorrect fragment data"
|
||||||
|
|
||||||
|
return packages
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _packetize_stap_a(
|
||||||
|
data: bytes, packages_iterator: Iterator[bytes]
|
||||||
|
) -> Tuple[bytes, bytes]:
|
||||||
|
counter = 0
|
||||||
|
available_size = PACKET_MAX - STAP_A_HEADER_SIZE
|
||||||
|
|
||||||
|
stap_header = NAL_TYPE_STAP_A | (data[0] & 0xE0)
|
||||||
|
|
||||||
|
payload = bytes()
|
||||||
|
try:
|
||||||
|
nalu = data # with header
|
||||||
|
while len(nalu) <= available_size and counter < 9:
|
||||||
|
stap_header |= nalu[0] & 0x80
|
||||||
|
|
||||||
|
nri = nalu[0] & 0x60
|
||||||
|
if stap_header & 0x60 < nri:
|
||||||
|
stap_header = stap_header & 0x9F | nri
|
||||||
|
|
||||||
|
available_size -= LENGTH_FIELD_SIZE + len(nalu)
|
||||||
|
counter += 1
|
||||||
|
payload += pack("!H", len(nalu)) + nalu
|
||||||
|
nalu = next(packages_iterator)
|
||||||
|
|
||||||
|
if counter == 0:
|
||||||
|
nalu = next(packages_iterator)
|
||||||
|
except StopIteration:
|
||||||
|
nalu = None
|
||||||
|
|
||||||
|
if counter <= 1:
|
||||||
|
return data, nalu
|
||||||
|
else:
|
||||||
|
return bytes([stap_header]) + payload, nalu
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _split_bitstream(buf: bytes) -> Iterator[bytes]:
|
||||||
|
# Translated from: https://github.com/aizvorski/h264bitstream/blob/master/h264_nal.c#L134
|
||||||
|
i = 0
|
||||||
|
while True:
|
||||||
|
# Find the start of the NAL unit.
|
||||||
|
#
|
||||||
|
# NAL Units start with the 3-byte start code 0x000001 or
|
||||||
|
# the 4-byte start code 0x00000001.
|
||||||
|
i = buf.find(b"\x00\x00\x01", i)
|
||||||
|
if i == -1:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Jump past the start code
|
||||||
|
i += 3
|
||||||
|
nal_start = i
|
||||||
|
|
||||||
|
# Find the end of the NAL unit (end of buffer OR next start code)
|
||||||
|
i = buf.find(b"\x00\x00\x01", i)
|
||||||
|
if i == -1:
|
||||||
|
yield buf[nal_start: len(buf)]
|
||||||
|
return
|
||||||
|
elif buf[i - 1] == 0:
|
||||||
|
# 4-byte start code case, jump back one byte
|
||||||
|
yield buf[nal_start: i - 1]
|
||||||
|
else:
|
||||||
|
yield buf[nal_start:i]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _packetize(cls, packages: Iterator[bytes]) -> List[bytes]:
|
||||||
|
packetized_packages = []
|
||||||
|
|
||||||
|
packages_iterator = iter(packages)
|
||||||
|
package = next(packages_iterator, None)
|
||||||
|
while package is not None:
|
||||||
|
if len(package) > PACKET_MAX:
|
||||||
|
packetized_packages.extend(cls._packetize_fu_a(package))
|
||||||
|
package = next(packages_iterator, None)
|
||||||
|
else:
|
||||||
|
packetized, package = cls._packetize_stap_a(package, packages_iterator)
|
||||||
|
packetized_packages.append(packetized)
|
||||||
|
|
||||||
|
return packetized_packages
|
||||||
|
|
||||||
|
def _encode_frame(
|
||||||
|
self, frame: av.VideoFrame, force_keyframe: bool
|
||||||
|
) -> Iterator[bytes]:
|
||||||
|
if self.codec and (
|
||||||
|
frame.width != self.codec.width
|
||||||
|
or frame.height != self.codec.height
|
||||||
|
# we only adjust bitrate if it changes by over 10%
|
||||||
|
or abs(self.target_bitrate - self.codec.bit_rate) / self.codec.bit_rate
|
||||||
|
> 0.1
|
||||||
|
):
|
||||||
|
self.buffer_data = b""
|
||||||
|
self.buffer_pts = None
|
||||||
|
self.codec = None
|
||||||
|
|
||||||
|
if force_keyframe:
|
||||||
|
# force a complete image
|
||||||
|
frame.pict_type = av.video.frame.PictureType.I
|
||||||
|
else:
|
||||||
|
# reset the picture type, otherwise no B-frames are produced
|
||||||
|
frame.pict_type = av.video.frame.PictureType.NONE
|
||||||
|
# print(self.codec)
|
||||||
|
if self.codec is None:
|
||||||
|
self.codec, self.codec_buffering = create_encoder_context(
|
||||||
|
"h264_nvenc",
|
||||||
|
frame.width,
|
||||||
|
frame.height,
|
||||||
|
bitrate=self.target_bitrate,
|
||||||
|
)
|
||||||
|
# try:
|
||||||
|
# self.codec, self.codec_buffering = create_encoder_context(
|
||||||
|
# "h264_omx", frame.width, frame.height, bitrate=self.target_bitrate
|
||||||
|
# )
|
||||||
|
# except Exception:
|
||||||
|
# self.codec, self.codec_buffering = create_encoder_context(
|
||||||
|
# "h264_nvenc",
|
||||||
|
# frame.width,
|
||||||
|
# frame.height,
|
||||||
|
# bitrate=self.target_bitrate,
|
||||||
|
# )
|
||||||
|
|
||||||
|
data_to_send = b""
|
||||||
|
for package in self.codec.encode(frame):
|
||||||
|
package_bytes = bytes(package)
|
||||||
|
if self.codec_buffering:
|
||||||
|
# delay sending to ensure we accumulate all packages
|
||||||
|
# for a given PTS
|
||||||
|
if package.pts == self.buffer_pts:
|
||||||
|
self.buffer_data += package_bytes
|
||||||
|
else:
|
||||||
|
data_to_send += self.buffer_data
|
||||||
|
self.buffer_data = package_bytes
|
||||||
|
self.buffer_pts = package.pts
|
||||||
|
else:
|
||||||
|
data_to_send += package_bytes
|
||||||
|
|
||||||
|
if data_to_send:
|
||||||
|
yield from self._split_bitstream(data_to_send)
|
||||||
|
|
||||||
|
def encode(
|
||||||
|
self, frame: Frame, force_keyframe: bool = False
|
||||||
|
) -> Tuple[List[bytes], int]:
|
||||||
|
assert isinstance(frame, av.VideoFrame)
|
||||||
|
packages = self._encode_frame(frame, force_keyframe)
|
||||||
|
timestamp = convert_timebase(frame.pts, frame.time_base, VIDEO_TIME_BASE)
|
||||||
|
return self._packetize(packages), timestamp
|
||||||
|
|
||||||
|
def pack(self, packet: Packet) -> Tuple[List[bytes], int]:
|
||||||
|
assert isinstance(packet, av.Packet)
|
||||||
|
packages = self._split_bitstream(bytes(packet))
|
||||||
|
timestamp = convert_timebase(packet.pts, packet.time_base, VIDEO_TIME_BASE)
|
||||||
|
return self._packetize(packages), timestamp
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_bitrate(self) -> int:
|
||||||
|
"""
|
||||||
|
Target bitrate in bits per second.
|
||||||
|
"""
|
||||||
|
return self.__target_bitrate
|
||||||
|
|
||||||
|
@target_bitrate.setter
|
||||||
|
def target_bitrate(self, bitrate: int) -> None:
|
||||||
|
# print(bitrate)
|
||||||
|
return
|
||||||
|
bitrate = max(MIN_BITRATE, min(bitrate, MAX_BITRATE))
|
||||||
|
self.__target_bitrate = bitrate
|
||||||
|
|
||||||
|
|
||||||
|
def h264_depayload(payload: bytes) -> bytes:
|
||||||
|
descriptor, data = H264PayloadDescriptor.parse(payload)
|
||||||
|
return data
|
||||||
101
src/Msg.py
Normal file
101
src/Msg.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import dataclasses
|
||||||
|
import json
|
||||||
|
import struct
|
||||||
|
import time
|
||||||
|
from enum import auto, Enum
|
||||||
|
|
||||||
|
|
||||||
|
class BG(Enum):
|
||||||
|
Msg1 = auto()
|
||||||
|
Msg2 = auto()
|
||||||
|
BMMsg = auto()
|
||||||
|
TickMsg = auto()
|
||||||
|
KillMsg = auto()
|
||||||
|
StrMsg = auto()
|
||||||
|
MoveAxisMsg = auto()
|
||||||
|
ImageArgMsg = auto()
|
||||||
|
|
||||||
|
|
||||||
|
class Msg:
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def decode_base(clz, data: bytes) -> 'Msg':
|
||||||
|
# c = clz()
|
||||||
|
# c.__dict__.update(json.loads(data.decode()))
|
||||||
|
# return c
|
||||||
|
return clz(**json.loads(data.decode()))
|
||||||
|
|
||||||
|
def encode(self) -> bytes:
|
||||||
|
return json.dumps(self.__dict__).encode()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def decode(self, data: bytes) -> 'Msg':
|
||||||
|
return self.decode_base(data)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def decode_msg(msg):
|
||||||
|
eid = struct.unpack('I', msg[:4])[0]
|
||||||
|
class_: 'Msg' = globals()[BG(eid).name]
|
||||||
|
return class_.decode(msg[4:])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def eid(cls):
|
||||||
|
return struct.pack('I', BG[cls.__name__].value)
|
||||||
|
|
||||||
|
def encode_msg(self):
|
||||||
|
return self.eid() + self.encode()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class Msg1(Msg):
|
||||||
|
a: int = 0
|
||||||
|
b: int = 1
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class KillMsg(Msg):
|
||||||
|
name: str = ''
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class Msg2(Msg):
|
||||||
|
a: int = 2
|
||||||
|
b: int = 2
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class TickMsg(Msg):
|
||||||
|
time: float = 0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class StrMsg(Msg):
|
||||||
|
value: str = ''
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class MoveAxisMsg(Msg):
|
||||||
|
axis: str
|
||||||
|
value: int
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class ImageArgMsg(Msg):
|
||||||
|
sender: str
|
||||||
|
v1: int
|
||||||
|
|
||||||
|
|
||||||
|
class BMMsg(Msg):
|
||||||
|
def __init__(self, t: int, data: bytes):
|
||||||
|
self.data = data
|
||||||
|
self.t = t
|
||||||
|
|
||||||
|
def encode(self) -> bytes:
|
||||||
|
return struct.pack('I', self.t) + self.data
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def decode(clz, data: bytes) -> 'Msg':
|
||||||
|
return clz(
|
||||||
|
struct.unpack('I', data[:4])[0],
|
||||||
|
data[4:]
|
||||||
|
)
|
||||||
69
src/RfFile.py
Normal file
69
src/RfFile.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from attr import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RfFolder:
|
||||||
|
parent: Path
|
||||||
|
D: str
|
||||||
|
L: int = 30
|
||||||
|
C: str = 'PAR'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_path(p: Path | str) -> 'RfFolder':
|
||||||
|
p = Path(p)
|
||||||
|
D, L, C = p.name.split(',')
|
||||||
|
L = int(L.replace('L=', ''))
|
||||||
|
C = C.replace('C=', '')
|
||||||
|
return RfFolder(p.parent, D, L, C)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return f'{self.D},L={self.L},C={self.C}'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
return self.parent / self.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def all(self):
|
||||||
|
return [RfFile.from_path(f) for f in self.path.glob('*.bin')]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RfFile:
|
||||||
|
folder: RfFolder
|
||||||
|
X: int = None
|
||||||
|
Y: int = None
|
||||||
|
Z: int = None
|
||||||
|
S: int = None
|
||||||
|
E: int = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_path(p: Path | str) -> 'RfFile':
|
||||||
|
p = Path(p)
|
||||||
|
folder = RfFolder.from_path(p.parent)
|
||||||
|
return RfFile(folder, **{(c := pair.split('='))[0]: int(c[1]) for pair in p.stem.split(',')})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
arr = []
|
||||||
|
d = self.__dict__.copy()
|
||||||
|
# print(d)
|
||||||
|
d.__delitem__('folder')
|
||||||
|
for k in d:
|
||||||
|
if d[k] is not None:
|
||||||
|
arr.append(f'{k}={d[k]}')
|
||||||
|
filename = ','.join(arr) + '.bin'
|
||||||
|
# print(filename)
|
||||||
|
return self.folder.path / filename
|
||||||
|
|
||||||
|
|
||||||
|
def test2():
|
||||||
|
r = RfFile.from_path('/run/media/lambda/b86dccdc-f134-464b-a310-6575ee9ae85c/cap4/TEST1,L=30,C=PAR/S=925,E=4.bin')
|
||||||
|
print(r)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test2()
|
||||||
3
src/config.py
Normal file
3
src/config.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
socket1 = '127.0.0.1:5003'
|
||||||
|
video_height = 1920
|
||||||
|
video_width = 1080
|
||||||
50
src/nodes/Beamformer.py
Normal file
50
src/nodes/Beamformer.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
import zmq
|
||||||
|
import cupy as cp
|
||||||
|
|
||||||
|
from Msg import BMMsg, ImageArgMsg, KillMsg
|
||||||
|
from config import socket1, video_width, video_height
|
||||||
|
from nodes.Node import Node
|
||||||
|
|
||||||
|
|
||||||
|
class Beamformer(Node):
|
||||||
|
topics = [ImageArgMsg]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(Beamformer, self).__init__()
|
||||||
|
self.arg = ImageArgMsg('', 1400)
|
||||||
|
|
||||||
|
def process(self, data: bytes):
|
||||||
|
if data is None:
|
||||||
|
return
|
||||||
|
b = np.frombuffer(data, dtype=np.int16)
|
||||||
|
b = b.reshape((256, 1502)).astype(np.float32)
|
||||||
|
b = b[:, :self.arg.v1]
|
||||||
|
b -= b.min()
|
||||||
|
b /= b.max()
|
||||||
|
b *= 255
|
||||||
|
b = b.astype(np.uint8)
|
||||||
|
b = cv2.rotate(b, cv2.ROTATE_90_CLOCKWISE)
|
||||||
|
b = cv2.resize(b, (video_width, video_height))
|
||||||
|
b = cv2.cvtColor(b, cv2.COLOR_GRAY2RGBA, b)
|
||||||
|
self.send(BMMsg(0, b.tobytes()))
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
s2 = self.context.socket(zmq.PULL)
|
||||||
|
s2.setsockopt(zmq.CONFLATE, 1)
|
||||||
|
s2.connect(f"tcp://{socket1}")
|
||||||
|
self.c.poller.register(s2, zmq.POLLIN)
|
||||||
|
buffer = None
|
||||||
|
while True:
|
||||||
|
socks = dict(self.c.poller.poll())
|
||||||
|
if s2 in socks and socks[s2] == zmq.POLLIN:
|
||||||
|
buffer = s2.recv()
|
||||||
|
if self.c.sub in socks and socks[self.c.sub] == zmq.POLLIN:
|
||||||
|
r = self.recv()
|
||||||
|
if isinstance(r, KillMsg):
|
||||||
|
if r.name == '':
|
||||||
|
return
|
||||||
|
if isinstance(r, ImageArgMsg):
|
||||||
|
self.arg = r
|
||||||
|
self.process(buffer)
|
||||||
26
src/nodes/Broker.py
Normal file
26
src/nodes/Broker.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import threading
|
||||||
|
|
||||||
|
import zmq
|
||||||
|
from zmq import ContextTerminated
|
||||||
|
|
||||||
|
from nodes.Node import Node
|
||||||
|
from Msg import KillMsg
|
||||||
|
|
||||||
|
|
||||||
|
class Broker(Node):
|
||||||
|
def loop(self):
|
||||||
|
def t():
|
||||||
|
while True:
|
||||||
|
r:KillMsg = self.recv()
|
||||||
|
if r.name == '':
|
||||||
|
self.context.term()
|
||||||
|
break
|
||||||
|
threading.Thread(target=t, daemon=True).start()
|
||||||
|
frontend = self.context.socket(zmq.XSUB)
|
||||||
|
backend = self.context.socket(zmq.XPUB)
|
||||||
|
frontend.bind(f"tcp://*:{self.fp}")
|
||||||
|
backend.bind(f"tcp://*:{self.bp}")
|
||||||
|
try:
|
||||||
|
zmq.proxy(frontend, backend)
|
||||||
|
except ContextTerminated as e:
|
||||||
|
return
|
||||||
63
src/nodes/BusClient.py.bak
Normal file
63
src/nodes/BusClient.py.bak
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
from abc import abstractmethod
|
||||||
|
|
||||||
|
import zmq
|
||||||
|
from zmq import Context, Socket
|
||||||
|
|
||||||
|
from clients.Msg import Msg, KillMsg
|
||||||
|
|
||||||
|
|
||||||
|
class BusClient:
|
||||||
|
fp = 5001
|
||||||
|
bp = 5002
|
||||||
|
topics = [KillMsg.eid()]
|
||||||
|
|
||||||
|
def __init__(self, enable_init=True):
|
||||||
|
self.context: Context = None
|
||||||
|
self.pub_socket: Socket = None
|
||||||
|
self.sub_socket: Socket = None
|
||||||
|
self.enable_init = enable_init
|
||||||
|
self.isalive = True
|
||||||
|
|
||||||
|
# def recv(self):
|
||||||
|
# return self.sub_socket.recv()
|
||||||
|
#
|
||||||
|
# def send(self, msg):
|
||||||
|
# return self.pub_socket.send(msg)
|
||||||
|
|
||||||
|
def recv(self):
|
||||||
|
b = self.sub_socket.recv()
|
||||||
|
return Msg.decode_msg(b)
|
||||||
|
|
||||||
|
def send(self, msg: Msg):
|
||||||
|
return self.pub_socket.send(msg.encode_msg())
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def loop(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
self.context = zmq.Context()
|
||||||
|
if self.enable_init:
|
||||||
|
self.pub_socket = self.context.socket(zmq.PUB)
|
||||||
|
self.pub_socket.connect(f"tcp://127.0.0.1:{self.fp}")
|
||||||
|
self.sub_socket = self.context.socket(zmq.SUB)
|
||||||
|
self.sub_socket.connect(f"tcp://127.0.0.1:{self.bp}")
|
||||||
|
if not self.topics:
|
||||||
|
self.sub_socket.setsockopt(zmq.SUBSCRIBE, b'')
|
||||||
|
else:
|
||||||
|
for topic in self.topics:
|
||||||
|
print(f"{self.__class__.__name__} Subscribing to topic {topic}")
|
||||||
|
self.sub_socket.setsockopt(zmq.SUBSCRIBE, topic)
|
||||||
|
self.loop()
|
||||||
|
print(self.__class__.__name__, 'exiting')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def sub(cls, ctx, *msgs: type(Msg)):
|
||||||
|
s = ctx.socket(zmq.SUB)
|
||||||
|
for msg in msgs:
|
||||||
|
s.setsockopt(zmq.SUBSCRIBE, msg.eid())
|
||||||
|
s.connect(f'tcp://127.0.0.1:{cls.bp}')
|
||||||
|
return s
|
||||||
34
src/nodes/ImageCV.py
Normal file
34
src/nodes/ImageCV.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
import zmq
|
||||||
|
|
||||||
|
from PyQt6.QtWidgets import QMainWindow, QApplication
|
||||||
|
|
||||||
|
from Image import Ui_MainWindow
|
||||||
|
from RfFile import RfFolder
|
||||||
|
from Msg import StrMsg, MoveAxisMsg, KillMsg, Msg, ImageArgMsg, BMMsg
|
||||||
|
from config import video_height, video_width
|
||||||
|
from nodes.Node import Node
|
||||||
|
|
||||||
|
|
||||||
|
class ImageCV(Node):
|
||||||
|
topics = [BMMsg]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.buffer = np.zeros((video_height, video_width, 3), dtype=np.uint8) + 128
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
cv2.namedWindow('image', cv2.WINDOW_NORMAL)
|
||||||
|
while True:
|
||||||
|
socks = dict(self.c.poller.poll(0.001))
|
||||||
|
if self.c.sub in socks and socks[self.c.sub] == zmq.POLLIN:
|
||||||
|
r = self.recv()
|
||||||
|
if isinstance(r, BMMsg):
|
||||||
|
b = np.frombuffer(r.data, dtype=np.uint8)
|
||||||
|
b = np.reshape(b, (video_height, video_width, 4))
|
||||||
|
self.buffer = b
|
||||||
|
cv2.imshow('image', self.buffer)
|
||||||
|
cv2.waitKey(1)
|
||||||
58
src/nodes/ImageQt.py
Normal file
58
src/nodes/ImageQt.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import zmq
|
||||||
|
from PyQt6 import QtCore
|
||||||
|
from PyQt6.QtCore import QByteArray
|
||||||
|
from PyQt6.QtGui import QImage, QPixmap
|
||||||
|
from PyQt6.QtWidgets import QMainWindow, QApplication
|
||||||
|
|
||||||
|
from Image import Ui_MainWindow
|
||||||
|
from RfFile import RfFolder
|
||||||
|
from Msg import StrMsg, MoveAxisMsg, KillMsg, Msg, ImageArgMsg, BMMsg
|
||||||
|
from ZMQReceiver import ZMQReceiver
|
||||||
|
from config import video_height, video_width
|
||||||
|
from nodes.Node import Node
|
||||||
|
|
||||||
|
|
||||||
|
class Adv(QMainWindow, Ui_MainWindow):
|
||||||
|
def __init__(self, p: Node, parent=None):
|
||||||
|
super(Adv, self).__init__(parent)
|
||||||
|
self.p = p
|
||||||
|
self.setupUi(self)
|
||||||
|
zmq_receiver = ZMQReceiver(self)
|
||||||
|
zmq_receiver.zmq_event.connect(self.on_zmq_event)
|
||||||
|
zmq_receiver.start()
|
||||||
|
|
||||||
|
def on_zmq_event(self, msg: QByteArray):
|
||||||
|
msg = Msg.decode_msg(msg.data())
|
||||||
|
if isinstance(msg, KillMsg):
|
||||||
|
if msg.name == '':
|
||||||
|
self.close()
|
||||||
|
elif isinstance(msg, BMMsg):
|
||||||
|
# cvImg = np.frombuffer(msg.data, dtype=np.uint8).reshape((video_height, video_width, 4))[:, :, :3].copy()
|
||||||
|
# height, width, channel = cvImg.shape
|
||||||
|
# bytesPerLine = 3 * width
|
||||||
|
# qImg = QImage(cvImg.data, width, height, bytesPerLine, QImage.Format.Format_RGB888).rgbSwapped()
|
||||||
|
qImg = QImage(msg.data, video_width, video_height, 4 * video_width, QImage.Format.Format_RGB888).rgbSwapped()
|
||||||
|
self.label.setPixmap(QPixmap(qImg))
|
||||||
|
|
||||||
|
@QtCore.pyqtSlot(int)
|
||||||
|
def vc(self, v):
|
||||||
|
if self.horizontalSlider.sender() is None:
|
||||||
|
self.p.send(ImageArgMsg('ui', v))
|
||||||
|
|
||||||
|
|
||||||
|
class ImageQt(Node):
|
||||||
|
topics = [BMMsg]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
MainWindow = Adv(self)
|
||||||
|
# MainWindow.move(int(px), int(py))
|
||||||
|
# MainWindow.resize(int(sx), int(sy))
|
||||||
|
MainWindow.show()
|
||||||
|
app.exec()
|
||||||
27
src/nodes/Loader.py
Normal file
27
src/nodes/Loader.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import numpy as np
|
||||||
|
import zmq
|
||||||
|
|
||||||
|
from RfFile import RfFolder
|
||||||
|
from Msg import StrMsg, MoveAxisMsg, KillMsg
|
||||||
|
from nodes.Node import Node
|
||||||
|
|
||||||
|
|
||||||
|
class Loader(Node):
|
||||||
|
topics = [MoveAxisMsg]
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
s2 = self.context.socket(zmq.PUSH)
|
||||||
|
s2.bind("tcp://*:5003")
|
||||||
|
|
||||||
|
rff = RfFolder.from_path('/run/media/lambda/b86dccdc-f134-464b-a310-6575ee9ae85c/cap4/trim/R1,L=30,C=PAR')
|
||||||
|
all_files = rff.all
|
||||||
|
while True:
|
||||||
|
r = self.recv()
|
||||||
|
if isinstance(r, MoveAxisMsg):
|
||||||
|
for f in all_files:
|
||||||
|
if f.S == r.value:
|
||||||
|
# s2.send(np.zeros((256, 1500), dtype=np.uint16).tobytes())
|
||||||
|
s2.send(f.path.read_bytes())
|
||||||
|
elif isinstance(r, KillMsg):
|
||||||
|
if r.name == '':
|
||||||
|
break
|
||||||
52
src/nodes/MainUI.py
Normal file
52
src/nodes/MainUI.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import zmq
|
||||||
|
from PyQt6 import QtCore
|
||||||
|
from PyQt6.QtCore import QByteArray
|
||||||
|
from PyQt6.QtWidgets import QMainWindow, QApplication
|
||||||
|
|
||||||
|
from Main import Ui_MainWindow
|
||||||
|
from RfFile import RfFolder
|
||||||
|
from Msg import StrMsg, MoveAxisMsg, KillMsg, Msg, ImageArgMsg
|
||||||
|
from ZMQReceiver import ZMQReceiver
|
||||||
|
from nodes.Node import Node
|
||||||
|
|
||||||
|
|
||||||
|
class Adv(QMainWindow, Ui_MainWindow):
|
||||||
|
def __init__(self, p: Node, parent=None):
|
||||||
|
super(Adv, self).__init__(parent)
|
||||||
|
self.p = p
|
||||||
|
self.setupUi(self)
|
||||||
|
zmq_receiver = ZMQReceiver(self)
|
||||||
|
zmq_receiver.zmq_event.connect(self.on_zmq_event)
|
||||||
|
zmq_receiver.start()
|
||||||
|
self.horizontalSlider.valueChanged.connect(self.vc)
|
||||||
|
|
||||||
|
def on_zmq_event(self, msg: QByteArray):
|
||||||
|
msg = Msg.decode_msg(msg.data())
|
||||||
|
if isinstance(msg, KillMsg):
|
||||||
|
if msg.name == '':
|
||||||
|
self.close()
|
||||||
|
elif isinstance(msg, ImageArgMsg):
|
||||||
|
self.horizontalSlider.setValue(msg.v1)
|
||||||
|
|
||||||
|
@QtCore.pyqtSlot(int)
|
||||||
|
def vc(self, v):
|
||||||
|
if self.horizontalSlider.sender() is None:
|
||||||
|
self.p.send(ImageArgMsg('ui', v))
|
||||||
|
|
||||||
|
|
||||||
|
class MainUI(Node):
|
||||||
|
topics = [ImageArgMsg]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
MainWindow = Adv(self)
|
||||||
|
# MainWindow.move(int(px), int(py))
|
||||||
|
# MainWindow.resize(int(sx), int(sy))
|
||||||
|
MainWindow.show()
|
||||||
|
app.exec()
|
||||||
35
src/nodes/Node.py
Normal file
35
src/nodes/Node.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import logging
|
||||||
|
from abc import abstractmethod
|
||||||
|
|
||||||
|
import zmq
|
||||||
|
|
||||||
|
from BusClient import BusClient
|
||||||
|
from Msg import Msg, KillMsg
|
||||||
|
|
||||||
|
|
||||||
|
class Node:
|
||||||
|
fp = BusClient.fp
|
||||||
|
bp = BusClient.bp
|
||||||
|
topics = []
|
||||||
|
|
||||||
|
def __init__(self, enable_init=True):
|
||||||
|
self.enable_init = enable_init
|
||||||
|
self.isalive = True
|
||||||
|
|
||||||
|
def recv(self):
|
||||||
|
return self.c.recv()
|
||||||
|
|
||||||
|
def send(self, msg: Msg):
|
||||||
|
return self.c.send(msg)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def loop(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
self.context = zmq.Context()
|
||||||
|
if self.enable_init:
|
||||||
|
self.c = BusClient(*([KillMsg] + self.topics), poller=True)
|
||||||
|
self.loop()
|
||||||
|
print(self.__class__.__name__, 'exiting')
|
||||||
198
src/nodes/WebRTC.py
Normal file
198
src/nodes/WebRTC.py
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import zmq.asyncio
|
||||||
|
|
||||||
|
import time
|
||||||
|
from fractions import Fraction
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
import aiohttp_cors
|
||||||
|
import aiortc.rtcrtpsender
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
import zmq
|
||||||
|
from aiohttp import web, WSMessage
|
||||||
|
from aiortc import MediaStreamTrack, RTCPeerConnection, RTCSessionDescription, RTCConfiguration, RTCRtpCodecCapability
|
||||||
|
|
||||||
|
from av import VideoFrame
|
||||||
|
|
||||||
|
import H264NV
|
||||||
|
from BusClient import BusClient
|
||||||
|
from Msg import BMMsg, Msg, TickMsg, KillMsg, StrMsg, MoveAxisMsg, ImageArgMsg
|
||||||
|
from config import video_width, video_height
|
||||||
|
|
||||||
|
ROOT = os.path.dirname(__file__)
|
||||||
|
from nodes.Node import Node
|
||||||
|
|
||||||
|
web.WebSocketResponse()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_placeholder():
|
||||||
|
z = np.zeros((video_height, video_width, 4), dtype=np.uint8)
|
||||||
|
z[:, :, 3] = 0
|
||||||
|
z[:, :, :3] = 128
|
||||||
|
return z.tobytes()
|
||||||
|
|
||||||
|
|
||||||
|
class WebRTC(Node):
|
||||||
|
|
||||||
|
# def __init__(self):
|
||||||
|
# super().__init__(False)
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
self1 = self
|
||||||
|
placeholder = generate_placeholder()
|
||||||
|
|
||||||
|
class ZMQStreamTrack(MediaStreamTrack):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.c = BusClient(BMMsg, pub=False, conflare=True, poller=True)
|
||||||
|
self.kind = 'video'
|
||||||
|
self.pts = 0
|
||||||
|
self.buffer = placeholder
|
||||||
|
|
||||||
|
async def recv(self):
|
||||||
|
events = self.c.poller.poll(int(1000 / 60))
|
||||||
|
if events:
|
||||||
|
msg: BMMsg = Msg.decode_msg(events[0][0].recv())
|
||||||
|
self.buffer = msg.data
|
||||||
|
|
||||||
|
frame = VideoFrame.from_bytes(self.buffer, video_width, video_height)
|
||||||
|
frame.time_base = Fraction(1, 60)
|
||||||
|
frame.pts = self.pts
|
||||||
|
self.pts += 1
|
||||||
|
return frame
|
||||||
|
|
||||||
|
pcs = set()
|
||||||
|
|
||||||
|
async def offer(request):
|
||||||
|
params = await request.json()
|
||||||
|
offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"])
|
||||||
|
pc = RTCPeerConnection(RTCConfiguration([]))
|
||||||
|
pcs.add(pc)
|
||||||
|
rc = pc.addTransceiver(ZMQStreamTrack(), '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()
|
||||||
|
|
||||||
|
async def t3(ws, name):
|
||||||
|
# s = BusClient.sub(zmq.asyncio.Context(), KillMsg, TickMsg)
|
||||||
|
c = BusClient(KillMsg, ImageArgMsg, ctx=zmq.asyncio.Context(), pub=False)
|
||||||
|
while True:
|
||||||
|
msg = await c.recv_async()
|
||||||
|
if isinstance(msg, KillMsg):
|
||||||
|
if msg.name == name:
|
||||||
|
break
|
||||||
|
if not ws.closed:
|
||||||
|
if isinstance(msg, ImageArgMsg):
|
||||||
|
if name != msg.sender:
|
||||||
|
dc = msg.__dict__.copy()
|
||||||
|
del dc['sender']
|
||||||
|
await ws.send_str(json.dumps(dict(
|
||||||
|
type='Arg', value=dc
|
||||||
|
)))
|
||||||
|
|
||||||
|
async def t4(ws, name):
|
||||||
|
c = BusClient(ctx=zmq.asyncio.Context(), sub=False)
|
||||||
|
|
||||||
|
async for msg in ws:
|
||||||
|
msg: WSMessage = msg
|
||||||
|
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||||
|
ss = msg.data
|
||||||
|
# await c.send_async(StrMsg(ss))
|
||||||
|
j = json.loads(ss)
|
||||||
|
v = j["value"]
|
||||||
|
match j["type"]:
|
||||||
|
case "Move":
|
||||||
|
await c.send_async(MoveAxisMsg('S', v['s']))
|
||||||
|
case "Arg":
|
||||||
|
await c.send_async(ImageArgMsg(sender=name, **v))
|
||||||
|
elif msg.type == aiohttp.WSMsgType.ERROR:
|
||||||
|
print(f'ws connection closed with exception', ws.exception())
|
||||||
|
await c.send_async(KillMsg(name))
|
||||||
|
|
||||||
|
async def handle_post(request):
|
||||||
|
print(request)
|
||||||
|
# try:
|
||||||
|
# # Parse the JSON body from the request
|
||||||
|
# data = await request.json()
|
||||||
|
# print("Received data:", data)
|
||||||
|
#
|
||||||
|
# # Process the data (this is just a placeholder)
|
||||||
|
# response_data = {'status': 'success', 'received': data}
|
||||||
|
#
|
||||||
|
# # Return a JSON response
|
||||||
|
# return web.json_response(response_data, status=200)
|
||||||
|
# except Exception as e:
|
||||||
|
# # Handle errors and return a bad request response
|
||||||
|
# return web.json_response({'error': str(e)}, status=400)
|
||||||
|
return web.Response()
|
||||||
|
|
||||||
|
async def websocket_handler(request):
|
||||||
|
ws = web.WebSocketResponse()
|
||||||
|
await ws.prepare(request)
|
||||||
|
|
||||||
|
name = str(time.time())
|
||||||
|
|
||||||
|
task1 = asyncio.create_task(t3(ws, name))
|
||||||
|
task2 = asyncio.create_task(t4(ws, name))
|
||||||
|
|
||||||
|
await asyncio.gather(task1, task2)
|
||||||
|
# await task1
|
||||||
|
# await task2
|
||||||
|
|
||||||
|
# for i in range(10):
|
||||||
|
# await ws.send_str('asdasd')
|
||||||
|
# await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
return ws
|
||||||
|
|
||||||
|
f0 = aiortc.RTCRtpSender.__init__
|
||||||
|
|
||||||
|
def f1(*args, **kwargs):
|
||||||
|
f0(*args, **kwargs)
|
||||||
|
if not args[1] == 'audio':
|
||||||
|
args[0]._RTCRtpSender__encoder = H264NV.H264NVENCEncoder()
|
||||||
|
|
||||||
|
aiortc.RTCRtpSender.__init__ = f1
|
||||||
|
app = web.Application()
|
||||||
|
app.on_shutdown.append(on_shutdown)
|
||||||
|
app.router.add_post("/offer", offer)
|
||||||
|
app.router.add_post('/d', handle_post) # Define the POST route
|
||||||
|
app.add_routes([web.get('/ws', websocket_handler)])
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
103
src/rtcserver.py
Normal file
103
src/rtcserver.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from fractions import Fraction
|
||||||
|
|
||||||
|
import aiohttp_cors
|
||||||
|
import aiortc.rtcrtpsender
|
||||||
|
import zmq
|
||||||
|
from aiohttp import web
|
||||||
|
from aiortc import MediaStreamTrack, RTCPeerConnection, RTCSessionDescription, RTCConfiguration, RTCRtpCodecCapability
|
||||||
|
|
||||||
|
from av import VideoFrame
|
||||||
|
|
||||||
|
import H264NV
|
||||||
|
|
||||||
|
ROOT = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerStreamTrackx(MediaStreamTrack):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
context = zmq.Context()
|
||||||
|
self.socket = context.socket(zmq.PULL)
|
||||||
|
self.socket.bind("tcp://*:5555")
|
||||||
|
self.kind = 'video'
|
||||||
|
self.pts = 0
|
||||||
|
|
||||||
|
async def recv(self):
|
||||||
|
frame = VideoFrame.from_bytes(self.socket.recv(), 1920, 1080)
|
||||||
|
frame.time_base = Fraction(1, 120)
|
||||||
|
frame.pts = self.pts
|
||||||
|
self.pts += 1
|
||||||
|
return frame
|
||||||
|
|
||||||
|
|
||||||
|
pcs = set()
|
||||||
|
px = PlayerStreamTrackx()
|
||||||
|
|
||||||
|
|
||||||
|
async def offer(request):
|
||||||
|
params = await request.json()
|
||||||
|
offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"])
|
||||||
|
pc = RTCPeerConnection(RTCConfiguration([]))
|
||||||
|
# print(request.remote)
|
||||||
|
pcs.add(pc)
|
||||||
|
rc = pc.addTransceiver(px, '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__":
|
||||||
|
f0 = aiortc.RTCRtpSender.__init__
|
||||||
|
|
||||||
|
|
||||||
|
def f1(*args, **kwargs):
|
||||||
|
f0(*args, **kwargs)
|
||||||
|
if not args[1] == 'audio':
|
||||||
|
args[0]._RTCRtpSender__encoder = H264NV.H264NVENCEncoder()
|
||||||
|
|
||||||
|
|
||||||
|
aiortc.RTCRtpSender.__init__ = f1
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
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
|
||||||
|
)
|
||||||
38
src/ui/Image.py
Normal file
38
src/ui/Image.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Form implementation generated from reading ui file 'Image.ui'
|
||||||
|
#
|
||||||
|
# Created by: PyQt6 UI code generator 6.8.0
|
||||||
|
#
|
||||||
|
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
|
||||||
|
# run again. Do not edit this file unless you know what you are doing.
|
||||||
|
|
||||||
|
|
||||||
|
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
|
||||||
|
class Ui_MainWindow(object):
|
||||||
|
def setupUi(self, MainWindow):
|
||||||
|
MainWindow.setObjectName("MainWindow")
|
||||||
|
MainWindow.resize(800, 600)
|
||||||
|
self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
|
||||||
|
self.centralwidget.setObjectName("centralwidget")
|
||||||
|
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
|
||||||
|
self.gridLayout.setObjectName("gridLayout")
|
||||||
|
self.label = QtWidgets.QLabel(parent=self.centralwidget)
|
||||||
|
self.label.setObjectName("label")
|
||||||
|
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
|
||||||
|
MainWindow.setCentralWidget(self.centralwidget)
|
||||||
|
self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
|
||||||
|
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 22))
|
||||||
|
self.menubar.setObjectName("menubar")
|
||||||
|
MainWindow.setMenuBar(self.menubar)
|
||||||
|
self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
|
||||||
|
self.statusbar.setObjectName("statusbar")
|
||||||
|
MainWindow.setStatusBar(self.statusbar)
|
||||||
|
|
||||||
|
self.retranslateUi(MainWindow)
|
||||||
|
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||||
|
|
||||||
|
def retranslateUi(self, MainWindow):
|
||||||
|
_translate = QtCore.QCoreApplication.translate
|
||||||
|
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
|
||||||
|
self.label.setText(_translate("MainWindow", "TextLabel"))
|
||||||
41
src/ui/Image.ui
Normal file
41
src/ui/Image.ui
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>MainWindow</class>
|
||||||
|
<widget class="QMainWindow" name="MainWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>800</width>
|
||||||
|
<height>600</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>MainWindow</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="centralwidget">
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>TextLabel</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenuBar" name="menubar">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>800</width>
|
||||||
|
<height>22</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QStatusBar" name="statusbar"/>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
40
src/ui/Main.py
Normal file
40
src/ui/Main.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Form implementation generated from reading ui file 'Main.ui'
|
||||||
|
#
|
||||||
|
# Created by: PyQt6 UI code generator 6.8.0
|
||||||
|
#
|
||||||
|
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
|
||||||
|
# run again. Do not edit this file unless you know what you are doing.
|
||||||
|
|
||||||
|
|
||||||
|
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
|
||||||
|
class Ui_MainWindow(object):
|
||||||
|
def setupUi(self, MainWindow):
|
||||||
|
MainWindow.setObjectName("MainWindow")
|
||||||
|
MainWindow.resize(457, 205)
|
||||||
|
self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
|
||||||
|
self.centralwidget.setObjectName("centralwidget")
|
||||||
|
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
|
||||||
|
self.gridLayout.setObjectName("gridLayout")
|
||||||
|
self.horizontalSlider = QtWidgets.QSlider(parent=self.centralwidget)
|
||||||
|
self.horizontalSlider.setMinimum(1)
|
||||||
|
self.horizontalSlider.setMaximum(1500)
|
||||||
|
self.horizontalSlider.setOrientation(QtCore.Qt.Orientation.Horizontal)
|
||||||
|
self.horizontalSlider.setObjectName("horizontalSlider")
|
||||||
|
self.gridLayout.addWidget(self.horizontalSlider, 0, 0, 1, 1)
|
||||||
|
MainWindow.setCentralWidget(self.centralwidget)
|
||||||
|
self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
|
||||||
|
self.menubar.setGeometry(QtCore.QRect(0, 0, 457, 22))
|
||||||
|
self.menubar.setObjectName("menubar")
|
||||||
|
MainWindow.setMenuBar(self.menubar)
|
||||||
|
self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
|
||||||
|
self.statusbar.setObjectName("statusbar")
|
||||||
|
MainWindow.setStatusBar(self.statusbar)
|
||||||
|
|
||||||
|
self.retranslateUi(MainWindow)
|
||||||
|
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||||
|
|
||||||
|
def retranslateUi(self, MainWindow):
|
||||||
|
_translate = QtCore.QCoreApplication.translate
|
||||||
|
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
|
||||||
47
src/ui/Main.ui
Normal file
47
src/ui/Main.ui
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>MainWindow</class>
|
||||||
|
<widget class="QMainWindow" name="MainWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>457</width>
|
||||||
|
<height>205</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>MainWindow</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="centralwidget">
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QSlider" name="horizontalSlider">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>1500</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenuBar" name="menubar">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>457</width>
|
||||||
|
<height>22</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QStatusBar" name="statusbar"/>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
18
src/ui/ZMQReceiver.py
Normal file
18
src/ui/ZMQReceiver.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import threading
|
||||||
|
|
||||||
|
from PyQt6 import QtCore
|
||||||
|
|
||||||
|
from nodes.Node import Node
|
||||||
|
|
||||||
|
|
||||||
|
class ZMQReceiver(QtCore.QObject):
|
||||||
|
zmq_event = QtCore.pyqtSignal('QByteArray')
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
threading.Thread(target=self._execute, daemon=True).start()
|
||||||
|
|
||||||
|
def _execute(self):
|
||||||
|
node: Node = self.parent().p
|
||||||
|
while True:
|
||||||
|
msg = node.recv()
|
||||||
|
self.zmq_event.emit(msg.encode_msg())
|
||||||
11
test/kill.py
Normal file
11
test/kill.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
from BusClient import BusClient
|
||||||
|
from Msg import KillMsg
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
c = BusClient()
|
||||||
|
time.sleep(1)
|
||||||
|
for i in range(100):
|
||||||
|
c.send(KillMsg())
|
||||||
75
test/main.py
Normal file
75
test/main.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import base64
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
import zmq
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# r1 = base64.b64encode(Path('/home/lambda/Pictures/drawing.png').read_bytes()).decode()
|
||||||
|
# r1 = base64.b64encode(Path('/home/lambda/Pictures/remilia3.png').read_bytes()).decode()
|
||||||
|
# requests.post('http://localhost:12345/sendpic/test', json=dict(value='data:image/png;base64, ' + r1))
|
||||||
|
|
||||||
|
# for p in tqdm(Path('/home/lambda/Videos/pngs/New Folder/').glob('*.png')):
|
||||||
|
# r1 = base64.b64encode(p.read_bytes()).decode()
|
||||||
|
# requests.post('http://localhost:12345/sendpic/test', json=dict(value='data:image/png;base64, ' + r1))
|
||||||
|
|
||||||
|
# arr = [cv2.imread(str(img)).tobytes() for img in Path('/home/lambda/Videos/pngs/New Folder/').glob('*.png')]
|
||||||
|
arr = []
|
||||||
|
for img in tqdm(list(Path('/home/lambda/Videos/pngs/New Folder/').glob('*.png'))):
|
||||||
|
img = cv2.imread(str(img))
|
||||||
|
# img = cv2.resize(img, (1920 // 2, 1080 // 2))
|
||||||
|
|
||||||
|
img = img.reshape(1080, 1920, 3)
|
||||||
|
z = np.zeros((1080, 1920, 4), dtype=np.uint8)
|
||||||
|
z[:, :, :3] = img
|
||||||
|
img = z
|
||||||
|
img = cv2.cvtColor(img, cv2.COLOR_BGRA2RGBA)
|
||||||
|
|
||||||
|
arr.append(img.tobytes())
|
||||||
|
|
||||||
|
# img = cv2.resize(img, (1920 // 4, 1080 // 4,))
|
||||||
|
|
||||||
|
# while True:
|
||||||
|
# for p in Path('/home/lambda/Videos/pngs/New Folder/').glob('*.png'):
|
||||||
|
# img = cv2.imread(str(p))
|
||||||
|
# # print(img.shape)
|
||||||
|
# input()
|
||||||
|
# socket.send(img.tobytes())
|
||||||
|
# while True:
|
||||||
|
# for b in arr:
|
||||||
|
# input()
|
||||||
|
# socket.send(b)
|
||||||
|
lii = [0, True]
|
||||||
|
|
||||||
|
|
||||||
|
def t(li):
|
||||||
|
context = zmq.Context()
|
||||||
|
socket = context.socket(zmq.PUSH)
|
||||||
|
socket.connect("tcp://localhost:5555")
|
||||||
|
while True:
|
||||||
|
for i, b in enumerate(arr):
|
||||||
|
while True:
|
||||||
|
socket.send(arr[li[0]])
|
||||||
|
time.sleep(1 / 120)
|
||||||
|
if li[1]:
|
||||||
|
li[0] = i
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
threading.Thread(target=t, args=(lii,)).start()
|
||||||
|
|
||||||
|
# while True:
|
||||||
|
# for i, b in enumerate(arr):
|
||||||
|
# # input()
|
||||||
|
# lii[0] = i
|
||||||
|
# time.sleep(1 / 60)
|
||||||
|
while True:
|
||||||
|
input()
|
||||||
|
lii[1] = not lii[1]
|
||||||
31
test/test_process_cupy.py
Normal file
31
test/test_process_cupy.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import multiprocessing
|
||||||
|
from multiprocessing import Process
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
import cupy as cp
|
||||||
|
|
||||||
|
class TestProcessCupy:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
def t1(self):
|
||||||
|
print(cp.zeros((10, 10, 10)))
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
print(cp.zeros((10, 10, 10)))
|
||||||
|
|
||||||
|
def p1():
|
||||||
|
# import cupy as cp
|
||||||
|
print(cp.zeros((10, 10, 10)))
|
||||||
|
# print(cp.asarray(z))
|
||||||
|
if __name__ == '__main__':
|
||||||
|
def ff():
|
||||||
|
print(cp.zeros((10, 10, 10)))
|
||||||
|
tpc = TestProcessCupy()
|
||||||
|
|
||||||
|
|
||||||
|
p2 = p1
|
||||||
|
z = cp.zeros((10, 10, 10))
|
||||||
|
multiprocessing.set_start_method('spawn')
|
||||||
|
# p = Process(target=p2)
|
||||||
|
p = Process(target=tpc)
|
||||||
|
p.start()
|
||||||
|
p.join()
|
||||||
34
test/zmqlargetest.py
Normal file
34
test/zmqlargetest.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import multiprocessing
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
import zmq
|
||||||
|
|
||||||
|
from nodes.Broker import Broker
|
||||||
|
|
||||||
|
|
||||||
|
def thread2():
|
||||||
|
c = zmq.Context()
|
||||||
|
pull = c.socket(zmq.PULL)
|
||||||
|
pull.setsockopt(zmq.CONFLATE, 1)
|
||||||
|
pull.connect('tcp://127.0.0.1:5555')
|
||||||
|
while True:
|
||||||
|
print(pull.recv())
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
c = zmq.Context()
|
||||||
|
push = c.socket(zmq.PUSH)
|
||||||
|
push.bind('tcp://*:5555')
|
||||||
|
cnt = 0
|
||||||
|
t = threading.Thread(target=thread2)
|
||||||
|
t.start()
|
||||||
|
for i in range(30):
|
||||||
|
cnt += 1
|
||||||
|
push.send(str(cnt).encode())
|
||||||
|
time.sleep(0.4)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
137
test/zmqmyclasstest.py
Normal file
137
test/zmqmyclasstest.py
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import logging
|
||||||
|
import multiprocessing
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from nodes.Beamformer import Beamformer
|
||||||
|
from nodes.Broker import Broker
|
||||||
|
from nodes.ImageCV import ImageCV
|
||||||
|
from nodes.ImageQt import ImageQt
|
||||||
|
from nodes.Loader import Loader
|
||||||
|
from nodes.MainUI import MainUI
|
||||||
|
from nodes.Node import Node
|
||||||
|
from BusClient import BusClient
|
||||||
|
from Msg import Msg1, Msg2, BMMsg, TickMsg, StrMsg, KillMsg
|
||||||
|
from nodes.WebRTC import WebRTC
|
||||||
|
|
||||||
|
|
||||||
|
class M1(Node):
|
||||||
|
def loop(self):
|
||||||
|
cnt = 0
|
||||||
|
while True:
|
||||||
|
cnt += 1
|
||||||
|
self.send(str(cnt).encode())
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
class M2(Node):
|
||||||
|
def loop(self):
|
||||||
|
while True:
|
||||||
|
print(self.recv())
|
||||||
|
|
||||||
|
|
||||||
|
class M3(Node):
|
||||||
|
topics = [StrMsg]
|
||||||
|
def loop(self):
|
||||||
|
arr = []
|
||||||
|
for img in tqdm(list(Path('/home/lambda/Videos/pngs/New Folder/').glob('*.png'))):
|
||||||
|
img = cv2.imread(str(img))
|
||||||
|
# img = cv2.resize(img, (1920 // 2, 1080 // 2))
|
||||||
|
img = img.reshape(1080, 1920, 3)
|
||||||
|
|
||||||
|
z = np.zeros((1080, 1920, 4), dtype=np.uint8)
|
||||||
|
z[:, :, :3] = img
|
||||||
|
img = z
|
||||||
|
img = cv2.cvtColor(img, cv2.COLOR_BGRA2RGBA)
|
||||||
|
arr.append(img.tobytes())
|
||||||
|
|
||||||
|
# while self.isalive:
|
||||||
|
# for b in arr:
|
||||||
|
# self.send(BMMsg(0, b))
|
||||||
|
# # self.pub_socket.send(b)
|
||||||
|
# # self.send(b)
|
||||||
|
# r = self.c.poller.poll(int(1000 / 60))
|
||||||
|
# # print(r)
|
||||||
|
# if r and Msg.decode_msg(r[0][0].recv()).name == '':
|
||||||
|
# self.isalive = False
|
||||||
|
# break
|
||||||
|
# # time.sleep(1 / 60)
|
||||||
|
while self.isalive:
|
||||||
|
for b in arr:
|
||||||
|
msg = self.recv()
|
||||||
|
if isinstance(msg, KillMsg):
|
||||||
|
if msg.name == '':
|
||||||
|
self.isalive = False
|
||||||
|
break
|
||||||
|
self.send(BMMsg(0, b))
|
||||||
|
|
||||||
|
# if r and Msg.decode_msg(r[0][0].recv()).name == '':
|
||||||
|
|
||||||
|
|
||||||
|
class M4(Node):
|
||||||
|
def loop(self):
|
||||||
|
while True:
|
||||||
|
self.send(Msg1())
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
class MTIME(Node):
|
||||||
|
def loop(self):
|
||||||
|
while True:
|
||||||
|
t = time.time()
|
||||||
|
self.send(TickMsg(t))
|
||||||
|
time.sleep(10)
|
||||||
|
# print(t)
|
||||||
|
|
||||||
|
|
||||||
|
class MLISTEN(Node):
|
||||||
|
topics = [StrMsg]
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
while self.isalive:
|
||||||
|
r = self.recv()
|
||||||
|
print(r)
|
||||||
|
if isinstance(r, KillMsg) and r.name == '':
|
||||||
|
self.isalive = False
|
||||||
|
break
|
||||||
|
self.send(TickMsg(time.time()))
|
||||||
|
|
||||||
|
|
||||||
|
class M6(Node):
|
||||||
|
topics = [Msg2.eid()]
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
while True:
|
||||||
|
print(self.recv())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
multiprocessing.set_start_method('spawn')
|
||||||
|
pps = []
|
||||||
|
ps = [
|
||||||
|
Broker(),
|
||||||
|
WebRTC(),
|
||||||
|
# M3(),
|
||||||
|
MainUI(),
|
||||||
|
ImageCV(),
|
||||||
|
MLISTEN(),
|
||||||
|
Beamformer(),
|
||||||
|
Loader(),
|
||||||
|
]
|
||||||
|
for p in ps:
|
||||||
|
pps.append(multiprocessing.Process(target=p))
|
||||||
|
for p in pps:
|
||||||
|
p.start()
|
||||||
|
|
||||||
|
c = BusClient(KillMsg)
|
||||||
|
while True:
|
||||||
|
x: KillMsg = c.recv()
|
||||||
|
if x.name == '':
|
||||||
|
break
|
||||||
|
for p in pps:
|
||||||
|
p.kill()
|
||||||
Loading…
Reference in New Issue
Block a user