Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 30 additions & 20 deletions src/trame_rca/encoders/video_encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from time import time_ns
from typing import Callable, Optional

from trame_common.utils import profiler
from vtk_streaming.vtkStreamingCore import (
VTKPF_IYUV,
VTKVC_AV1,
Expand Down Expand Up @@ -38,15 +39,17 @@

def available_codecs() -> list[dict]:
"""Codecs this server can encode, server-preferred first."""
timer = profiler.Timer("rca.video.available_codecs")
result = []
for codec, name, probes in _CODECS:
if vtkEncoderFactory.CheckAvailability(codec):
result.append(
{
"codec": name,
"probes": probes,
}
)
with timer:
for codec, name, probes in _CODECS:
if vtkEncoderFactory.CheckAvailability(codec):
result.append(
{
"codec": name,
"probes": probes,
}
)
return result


Expand Down Expand Up @@ -113,6 +116,9 @@ def __init__(
codecs: Optional[list[str]] = None,
defer: bool = False,
) -> None:
self._timer_encoder = profiler.Timer("rca.video.encode")
self._timer_capture = profiler.Timer("rca.video.frame_capture")
self._timer_configure = profiler.Timer("rca.video.configure")
self._render_window = render_window
self._push_callback = push_callback
self._codecs = codecs
Expand All @@ -132,17 +138,19 @@ def codecs(self) -> Optional[list[str]]:

def configure(self, codecs: Optional[list[str]]) -> dict:
"""(Re)create the encoder for the given codec ranking. Returns :func:`describe_encoder`."""
self.release()
self._codecs = codecs
self.encoder = create_encoder(codecs)
if self.encoder is None:
raise RuntimeError(
"No suitable video encoder is available on this machine."
with self._timer_configure:
self.release()
self._codecs = codecs
self.encoder = create_encoder(codecs)
if self.encoder is None:
raise RuntimeError(
"No suitable video encoder is available on this machine."
)
self.encoder.AddObserver(
vtkVideoEncoder.EncodedVideoChunkEvent, self._on_encoded_chunk
)
self.encoder.AddObserver(
vtkVideoEncoder.EncodedVideoChunkEvent, self._on_encoded_chunk
)
self._initialize(self._render_window)

self._initialize(self._render_window)
return self.describe()

def describe(self) -> dict:
Expand Down Expand Up @@ -192,8 +200,10 @@ def encode(self, render_window: vtkRenderWindow):
return
if self._window_size != render_window.GetSize():
self._set_size(render_window_size=render_window.size)
self.frame.Capture(render_window)
self.encoder.Encode(self.frame)
with self._timer_capture:
self.frame.Capture(render_window)
with self._timer_encoder:
self.encoder.Encode(self.frame)

def release(self):
if self.encoder is not None:
Expand Down
17 changes: 11 additions & 6 deletions src/trame_rca/schedulers/image_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import asyncio
import os
from asyncio import Event, Queue, sleep
from time import monotonic
from concurrent.futures import Executor
from concurrent.futures.thread import ThreadPoolExecutor
from time import time_ns
Expand Down Expand Up @@ -115,21 +116,25 @@ async def _render(self):

interactive = self._interactive_quality
still = self._still_quality

started = monotonic()
self._render_frame(interactive)

if interactive == still:
await sleep(self._target_period_s)
# Sleep only for what is left of the _target_period_s.
await sleep(max(0.0, self._target_period_s - (monotonic() - started)))
continue

if await self._wait_for_still():
if await self._wait_for_still(started):
self._render_frame(still)

async def _wait_for_still(self):
async def _wait_for_still(self, timer_started_at):
self._request_event.clear()

for _ in range(self._n_period_until_still_render):
await sleep(self._target_period_s)
for i in range(1, self._n_period_until_still_render + 1):
# Sleep only for what is left of the i-th _target_period_s since the timer started, so that the time
# spent rendering the interactive frame (and any loop overhead) is not added on top of the wait.
deadline = timer_started_at + i * self._target_period_s
await sleep(max(0.0, deadline - monotonic()))
if self._request_event.is_set():
return False

Expand Down
5 changes: 4 additions & 1 deletion src/trame_rca/schedulers/video_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import asyncio
from asyncio import Event, sleep
from time import monotonic
from typing import TYPE_CHECKING, Callable

from trame.app import asynchronous
Expand Down Expand Up @@ -113,8 +114,10 @@ async def _render(self):
break

self._request_event.clear()
started = monotonic()
self._rca_encoder.encode(self._rca.render_window)
await sleep(self._target_period_s)
# Sleep only for what is left of the _target_period_s.
await sleep(max(0.0, self._target_period_s - (monotonic() - started)))

def _push(self, content: bytes, meta: dict, _m_time: int):
if self._push_callback is not None:
Expand Down
Loading