Skip to content

Commit 4cd52c1

Browse files
committed
Refactor video decoding and processing logic
1 parent 1d530ab commit 4cd52c1

File tree

2 files changed

+110
-92
lines changed

2 files changed

+110
-92
lines changed

examples/net_device.py

+106-90
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import platform
2-
import subprocess
3-
42
import cv2
53
import numpy as np
6-
7-
from pyorbbecsdk import (Pipeline, Context, Config, OBSensorType,
8-
OBFormat, OBError)
4+
import av
5+
import io
6+
import threading
7+
import time
8+
import pygame
9+
import os
10+
from pyorbbecsdk import (Pipeline, Context, Config, OBSensorType, OBFormat, OBError)
911
from utils import frame_to_bgr_image
1012

1113
ESC_KEY = 27
1214

13-
# Only Femto Mega and Gemini2 XL support this sample
14-
1515
def get_stream_profile(pipeline, sensor_type, width, height, fmt, fps):
1616
profile_list = pipeline.get_stream_profile_list(sensor_type)
1717
try:
@@ -20,40 +20,55 @@ def get_stream_profile(pipeline, sensor_type, width, height, fmt, fps):
2020
profile = profile_list.get_default_video_stream_profile()
2121
return profile
2222

23-
24-
def decode_h265_frame(color_frame, color_format='hevc'):
25-
# This function is only supported on Linux.
26-
# and requires ffmpeg to be installed.
27-
if color_format == 'h265':
28-
color_format = 'hevc'
29-
elif color_format == 'h264':
30-
color_format = 'h264' # Actually, this remains unchanged but added for clarity.
31-
32-
cmd_in = [
33-
'ffmpeg',
34-
'-f', color_format,
35-
'-i', 'pipe:',
36-
'-f', 'rawvideo',
37-
'-pix_fmt', 'bgr24',
38-
'pipe:'
39-
]
40-
41-
byte_data = color_frame.get_data().tobytes()
42-
43-
proc = subprocess.Popen(cmd_in, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
44-
out, err = proc.communicate(input=byte_data)
45-
46-
if proc.returncode != 0:
47-
raise ValueError(f'FFmpeg did not run successfully: {err.decode()}')
48-
if len(out) == 0:
49-
return None
50-
decoded_frame = np.frombuffer(out, dtype=np.uint8).reshape(color_frame.get_height(), color_frame.get_width(), 3)
51-
return decoded_frame
52-
23+
def decode_h26x_frame(decoder, byte_data):
24+
try:
25+
packet = av.Packet(byte_data)
26+
frames = decoder.decode(packet)
27+
for frame in frames:
28+
return frame.to_ndarray(format='bgr24')
29+
except av.AVError as e:
30+
print(f"Decoding error: {e}")
31+
return None
32+
33+
class FrameProcessor(threading.Thread):
34+
def __init__(self, decoder, display_width, display_height):
35+
super().__init__()
36+
self.decoder = decoder
37+
self.latest_frame = None
38+
self.processed_frame = None
39+
self.lock = threading.Lock()
40+
self.running = True
41+
self.daemon = True
42+
self.display_width = display_width
43+
self.display_height = display_height
44+
45+
def run(self):
46+
while self.running:
47+
with self.lock:
48+
if self.latest_frame is not None:
49+
color_image = decode_h26x_frame(self.decoder, self.latest_frame)
50+
if color_image is not None:
51+
# Resize the image to 1080p
52+
resized_image = cv2.resize(color_image, (self.display_width, self.display_height))
53+
rgb_image = cv2.cvtColor(resized_image, cv2.COLOR_BGR2RGB)
54+
self.processed_frame = rgb_image
55+
self.latest_frame = None
56+
time.sleep(0.001)
57+
58+
def update_frame(self, frame):
59+
with self.lock:
60+
self.latest_frame = frame
61+
62+
def get_processed_frame(self):
63+
with self.lock:
64+
return self.processed_frame
65+
66+
def stop(self):
67+
self.running = False
5368

5469
def main():
5570
ctx = Context()
56-
ip = input("Enter the ip address of the device (default: 192.168.1.10): ") or "192.168.1.10"
71+
ip = input("Enter the IP address of the device (default: 192.168.1.10): ") or "192.168.1.10"
5772
device = ctx.create_net_device(ip, 8090)
5873
if device is None:
5974
print("Failed to create net device")
@@ -62,65 +77,66 @@ def main():
6277
config = Config()
6378
pipeline = Pipeline(device)
6479

65-
# Setup color stream
66-
color_profile = get_stream_profile(pipeline, OBSensorType.COLOR_SENSOR, 1280, 0, OBFormat.MJPG, 10)
80+
# Set up 4K capture
81+
color_profile = get_stream_profile(pipeline, OBSensorType.COLOR_SENSOR, 3840, 2160, OBFormat.H264, 25)
6782
config.enable_stream(color_profile)
6883

69-
# Setup depth stream
70-
depth_profile = get_stream_profile(pipeline, OBSensorType.DEPTH_SENSOR, 640, 0, OBFormat.Y16, 10)
71-
config.enable_stream(depth_profile)
72-
7384
pipeline.start(config)
74-
warning_printed = False
7585

86+
color_codec_name = 'h264' if color_profile.get_format() == OBFormat.H264 else 'hevc'
7687
try:
77-
while True:
78-
frames = pipeline.wait_for_frames(100)
79-
if not frames:
80-
continue
81-
82-
color_frame = frames.get_color_frame()
83-
depth_frame = frames.get_depth_frame()
84-
85-
if color_frame and color_frame.get_format() in [OBFormat.H265, OBFormat.H264]:
86-
if platform.system() == 'Linux':
87-
color_format = 'h265' if color_frame.get_format() == OBFormat.H265 else 'h264'
88-
color_image = decode_h265_frame(color_frame, color_format)
89-
else:
90-
if not warning_printed:
91-
print("H264 and H265 are not supported on this system.")
92-
warning_printed = True
93-
color_image = None
94-
elif color_frame:
95-
color_image = frame_to_bgr_image(color_frame)
96-
else:
97-
color_image = None
98-
99-
if depth_frame:
100-
depth_data = np.frombuffer(depth_frame.get_data(), dtype=np.uint16).reshape(depth_frame.get_height(),
101-
depth_frame.get_width())
102-
scale = depth_frame.get_depth_scale()
103-
depth_data = (depth_data * scale).astype(np.uint16)
104-
depth_image = cv2.normalize(depth_data, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
105-
depth_image = cv2.applyColorMap(depth_image, cv2.COLORMAP_JET)
106-
else:
107-
depth_image = None
108-
109-
if color_image is not None and depth_image is not None:
110-
target_size = (640, 480)
111-
images_to_show = [img for img in [color_image, depth_image] if img is not None]
112-
# Resize each image to 640x480
113-
images_to_show = [cv2.resize(img, target_size) for img in images_to_show]
114-
115-
cv2.imshow("net_device", np.hstack(images_to_show))
116-
key = cv2.waitKey(1)
117-
if key in [ord('q'), ESC_KEY]:
88+
decoder = av.codec.CodecContext.create(color_codec_name, 'r')
89+
except av.AVError as e:
90+
print(f"Failed to create decoder for {color_codec_name}: {e}")
91+
pipeline.stop()
92+
return
93+
94+
# Set display resolution to 720p
95+
display_width, display_height = 1280, 720
96+
frame_processor = FrameProcessor(decoder, display_width, display_height)
97+
frame_processor.start()
98+
99+
pygame.init()
100+
screen = pygame.display.set_mode((display_width, display_height))
101+
pygame.display.set_caption("4K Net Device Viewer (720p Display)")
102+
clock = pygame.time.Clock()
103+
104+
running = True
105+
try:
106+
while running:
107+
for event in pygame.event.get():
108+
if event.type == pygame.QUIT:
109+
running = False
110+
elif event.type == pygame.KEYDOWN:
111+
if event.key == pygame.K_ESCAPE:
112+
running = False
113+
114+
if not running:
118115
break
119-
except KeyboardInterrupt:
120-
pass
116+
117+
frames = pipeline.wait_for_frames(100)
118+
if frames:
119+
color_frame = frames.get_color_frame()
120+
if color_frame:
121+
byte_data = color_frame.get_data()
122+
if len(byte_data) > 0:
123+
frame_processor.update_frame(byte_data)
124+
125+
processed_frame = frame_processor.get_processed_frame()
126+
if processed_frame is not None:
127+
surf = pygame.surfarray.make_surface(processed_frame.swapaxes(0, 1))
128+
screen.blit(surf, (0, 0))
129+
pygame.display.flip()
130+
131+
clock.tick(30) # Limit to 30 FPS
132+
121133
finally:
134+
print("Stopping frame processor...")
135+
frame_processor.stop()
136+
print("Stopping pipeline...")
122137
pipeline.stop()
123-
138+
print("Exiting the program...")
139+
os._exit(0)
124140

125141
if __name__ == "__main__":
126-
main()
142+
main()

requirements.txt

+4-2
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,7 @@ pybind11==2.11.0
22
pybind11-global==2.11.0
33
opencv-python
44
numpy<2.0 # see https://github.com/orbbec/pyorbbecsdk/issues/47
5-
plyfile
6-
open3d
5+
plyfile # for saving point cloud
6+
open3d # for visualization point cloud
7+
av # for h264 decoding
8+
pygame # for visualization

0 commit comments

Comments
 (0)