Skip to content
This repository was archived by the owner on Nov 20, 2024. It is now read-only.

Commit 0142d08

Browse files
authored
Feature: Live stream in application (#66)
* Add live stream functionality in app * Remove unnecessary newlines * Restructure start_live_stream method * Rename variables and add temp fix comment
1 parent 939392c commit 0142d08

2 files changed

Lines changed: 96 additions & 4 deletions

File tree

examples/g3pycontroller/__main__.py

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22
import logging
33
from typing import List, Optional, Set, Tuple, cast
44

5+
import numpy as np
56
from eventkinds import AppEventKind, ControlEventKind
67
from kivy.app import App
8+
from kivy.clock import Clock
79
from kivy.core.window import Window
10+
from kivy.graphics import Rectangle
11+
from kivy.graphics.texture import Texture
812
from kivy.lang.builder import Builder
913
from kivy.properties import BooleanProperty
1014
from kivy.uix.label import Label
@@ -47,7 +51,6 @@
4751
text: "Connect"
4852
on_press: app.send_app_event(AppEventKind.ENTER_CONTROL_SESSION)
4953
50-
5154
<UserMessagePopup>:
5255
size_hint: None, None
5356
size: 400, 200
@@ -107,8 +110,19 @@
107110
108111
<LiveScreen>:
109112
BoxLayout:
110-
Label:
111-
text: "Here you can see your glasses in action."
113+
Widget:
114+
id: display
115+
size_hint_x: 0.8
116+
size_hint_y: 1
117+
BoxLayout:
118+
orientation: "vertical"
119+
size_hint_x: 0.2
120+
Button:
121+
text: "Start"
122+
on_press: app.send_control_event(ControlEventKind.START_LIVE)
123+
Button:
124+
text: "Stop"
125+
on_press: app.send_control_event(ControlEventKind.STOP_LIVE)
112126
113127
<SelectableList>:
114128
viewclass: 'SelectableLabel'
@@ -242,7 +256,8 @@ def set_recording_status(self, is_recording: bool) -> None:
242256

243257

244258
class LiveScreen(Screen):
245-
pass
259+
def clear(self, *args):
260+
self.ids.display.canvas.clear()
246261

247262

248263
class UserMessagePopup(Popup):
@@ -256,6 +271,8 @@ def __init__(self, **kwargs):
256271
self.tasks: Set[asyncio.Task] = set()
257272
self.app_events: asyncio.Queue[AppEventKind] = asyncio.Queue()
258273
self.control_events: asyncio.Queue[ControlEventKind] = asyncio.Queue()
274+
self.live_stream_task: Optional[asyncio.Task] = None
275+
self.read_frames_task: Optional[asyncio.Task] = None
259276
self.add_widget(DiscoveryScreen(name="discovery"))
260277
self.add_widget(ControlScreen(name="control"))
261278

@@ -391,8 +408,81 @@ async def handle_control_event(self, g3: Glasses3, event: ControlEventKind) -> N
391408
await g3.recorder.stop()
392409
case ControlEventKind.DELETE_RECORDING:
393410
await self.delete_selected_recording(g3)
411+
case ControlEventKind.START_LIVE:
412+
self.start_live_stream(g3)
413+
case ControlEventKind.STOP_LIVE:
414+
await self.stop_live_stream()
394415
self.get_screen("control").set_task_running_status(False)
395416

417+
def start_live_stream(self, g3: Glasses3) -> None:
418+
async def live_stream():
419+
async with g3.stream_rtsp() as streams:
420+
async with streams.scene_camera.decode() as decoded_stream:
421+
live_screen = self.get_screen("control").ids.sm.get_screen("live")
422+
Window.bind(on_resize=live_screen.clear)
423+
self.latest_frame = await decoded_stream.get()
424+
self.read_frames_task = self.create_task(
425+
update_frame(decoded_stream, streams), name="update_frame"
426+
)
427+
self.scheduling_delay = 1 / 25
428+
Clock.schedule_once(draw_frame, self.scheduling_delay)
429+
await self.read_frames_task
430+
431+
async def update_frame(decoded_stream, streams):
432+
while True:
433+
self.latest_frame = await decoded_stream.get()
434+
logging.debug(streams.scene_camera.stats)
435+
436+
def draw_frame(dt):
437+
"""NOTE: The scheduling if frame redraw would ideally be made with Clock.schedule_interval at 25Hz, but due to performance issues a dynamic scheduling delay is implemented as a temporary fix."""
438+
display = self.get_screen("control").ids.sm.get_screen("live").ids.display
439+
update_scheduling_delay(dt)
440+
if self.read_frames_task is not None:
441+
if not self.read_frames_task.done():
442+
Clock.schedule_once(draw_frame, self.scheduling_delay)
443+
with display.canvas:
444+
image = np.flip(self.latest_frame.to_ndarray(format="bgr24"), 0)
445+
texture = Texture.create(
446+
size=(image.shape[1], image.shape[0]), colorfmt="bgr"
447+
)
448+
image = np.reshape(image, -1)
449+
texture.blit_buffer(image, colorfmt="bgr", bufferfmt="ubyte")
450+
Rectangle(
451+
texture=texture,
452+
pos=(0, (display.top - display.width * 9 / 16) / 2),
453+
size=(display.width, display.width * 9 / 16),
454+
)
455+
456+
def update_scheduling_delay(last_delay):
457+
if last_delay > self.scheduling_delay:
458+
self.scheduling_delay = last_delay
459+
elif last_delay < self.scheduling_delay:
460+
self.scheduling_delay -= (self.scheduling_delay - last_delay) / 2
461+
462+
def live_stream_task_running() -> bool:
463+
if self.live_stream_task is not None:
464+
return not self.live_stream_task.done()
465+
else:
466+
return False
467+
468+
if live_stream_task_running():
469+
logging.info("Task not started: live_stream_task already running.")
470+
else:
471+
self.live_stream_task = self.create_task(
472+
live_stream(), name="live_stream_task"
473+
)
474+
475+
async def stop_live_stream(self) -> None:
476+
if self.read_frames_task is not None:
477+
if not self.read_frames_task.cancelled():
478+
await self.cancel_task(self.read_frames_task)
479+
if self.live_stream_task is not None:
480+
if not self.live_stream_task.cancelled():
481+
await self.cancel_task(self.live_stream_task)
482+
live_screen = self.get_screen("control").ids.sm.get_screen("live")
483+
Window.unbind(on_resize=live_screen.clear)
484+
live_screen.clear()
485+
396486
async def delete_selected_recording(self, g3: Glasses3) -> None:
397487
selected = (
398488
self.get_screen("control")

examples/g3pycontroller/eventkinds.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ class ControlEventKind(Enum):
55
START_RECORDING = auto()
66
STOP_RECORDING = auto()
77
DELETE_RECORDING = auto()
8+
START_LIVE = auto()
9+
STOP_LIVE = auto()
810

911

1012
class AppEventKind(Enum):

0 commit comments

Comments
 (0)