FrameProcessor: introduce direct mode
Direct mode avoids creating internal queues and tasks and processes frames right away. This might be useful for some very simple processors.
This commit is contained in:
20
CHANGELOG.md
20
CHANGELOG.md
@@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- Added `enable_direct_mode` argument to `FrameProcessor`. The direct mode is
|
||||||
|
for processors which require very little I/O or compute resources, that is
|
||||||
|
processors that can perform their task almost immediately. These type of
|
||||||
|
processors don't need any of the internal tasks and queues usually created by
|
||||||
|
frame processors which means overall application performance might be slightly
|
||||||
|
increased. Use with care.
|
||||||
|
|
||||||
- Added TTFB metrics for `HeyGenVideoService` and `TavusVideoService`.
|
- Added TTFB metrics for `HeyGenVideoService` and `TavusVideoService`.
|
||||||
|
|
||||||
- Added `endpoint_id` parameter to `AzureSTTService`. ([Custom EndpointId](https://docs.azure.cn/en-us/ai-services/speech-service/how-to-recognize-speech?pivots=programming-language-python#use-a-custom-endpoint))
|
- Added `endpoint_id` parameter to `AzureSTTService`. ([Custom EndpointId](https://docs.azure.cn/en-us/ai-services/speech-service/how-to-recognize-speech?pivots=programming-language-python#use-a-custom-endpoint))
|
||||||
@@ -18,6 +25,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Updated `pyproject.toml` to once again pin `numba` to `>=0.61.2` in order to
|
- Updated `pyproject.toml` to once again pin `numba` to `>=0.61.2` in order to
|
||||||
resolve package versioning issues.
|
resolve package versioning issues.
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
- Improved some frame processors performance by using the new frame processor
|
||||||
|
direct mode. In direct mode a frame processor will process frames right away
|
||||||
|
avoiding the need for internal queues and tasks. This is useful for some
|
||||||
|
simple processors. For example, in processors that wrap other processors
|
||||||
|
(e.g. `Pipeline`, `ParallelPipeline`), we add one processor before and one
|
||||||
|
after the wrapped processors (internally, you will see them as sources and
|
||||||
|
sinks). These sources and sinks don't do any special processing and they
|
||||||
|
basically forward frames. So, for these simple processors we now enable the
|
||||||
|
new direct mode which avoids creating any internal tasks (and queues) and
|
||||||
|
therefore improves performance.
|
||||||
|
|
||||||
### Other
|
### Other
|
||||||
|
|
||||||
- Improving the latency of the `HeyGenVideoService`.
|
- Improving the latency of the `HeyGenVideoService`.
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class ParallelPipelineSource(FrameProcessor):
|
|||||||
upstream_queue: Queue for collecting upstream frames from this branch.
|
upstream_queue: Queue for collecting upstream frames from this branch.
|
||||||
push_frame_func: Function to push frames to the parent parallel pipeline.
|
push_frame_func: Function to push frames to the parent parallel pipeline.
|
||||||
"""
|
"""
|
||||||
super().__init__()
|
super().__init__(enable_direct_mode=True)
|
||||||
self._up_queue = upstream_queue
|
self._up_queue = upstream_queue
|
||||||
self._push_frame_func = push_frame_func
|
self._push_frame_func = push_frame_func
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ class ParallelPipelineSink(FrameProcessor):
|
|||||||
downstream_queue: Queue for collecting downstream frames from this branch.
|
downstream_queue: Queue for collecting downstream frames from this branch.
|
||||||
push_frame_func: Function to push frames to the parent parallel pipeline.
|
push_frame_func: Function to push frames to the parent parallel pipeline.
|
||||||
"""
|
"""
|
||||||
super().__init__()
|
super().__init__(enable_direct_mode=True)
|
||||||
self._down_queue = downstream_queue
|
self._down_queue = downstream_queue
|
||||||
self._push_frame_func = push_frame_func
|
self._push_frame_func = push_frame_func
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class PipelineSource(FrameProcessor):
|
|||||||
Args:
|
Args:
|
||||||
upstream_push_frame: Coroutine function to handle upstream frames.
|
upstream_push_frame: Coroutine function to handle upstream frames.
|
||||||
"""
|
"""
|
||||||
super().__init__()
|
super().__init__(enable_direct_mode=True)
|
||||||
self._upstream_push_frame = upstream_push_frame
|
self._upstream_push_frame = upstream_push_frame
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
@@ -65,7 +65,7 @@ class PipelineSink(FrameProcessor):
|
|||||||
Args:
|
Args:
|
||||||
downstream_push_frame: Coroutine function to handle downstream frames.
|
downstream_push_frame: Coroutine function to handle downstream frames.
|
||||||
"""
|
"""
|
||||||
super().__init__()
|
super().__init__(enable_direct_mode=True)
|
||||||
self._downstream_push_frame = downstream_push_frame
|
self._downstream_push_frame = downstream_push_frame
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class SyncParallelPipelineSource(FrameProcessor):
|
|||||||
Args:
|
Args:
|
||||||
upstream_queue: Queue for collecting upstream frames from the pipeline.
|
upstream_queue: Queue for collecting upstream frames from the pipeline.
|
||||||
"""
|
"""
|
||||||
super().__init__()
|
super().__init__(enable_direct_mode=True)
|
||||||
self._up_queue = upstream_queue
|
self._up_queue = upstream_queue
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
@@ -81,7 +81,7 @@ class SyncParallelPipelineSink(FrameProcessor):
|
|||||||
Args:
|
Args:
|
||||||
downstream_queue: Queue for collecting downstream frames from the pipeline.
|
downstream_queue: Queue for collecting downstream frames from the pipeline.
|
||||||
"""
|
"""
|
||||||
super().__init__()
|
super().__init__(enable_direct_mode=True)
|
||||||
self._down_queue = downstream_queue
|
self._down_queue = downstream_queue
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
|
|||||||
@@ -110,14 +110,14 @@ class PipelineTaskSource(FrameProcessor):
|
|||||||
pipeline.
|
pipeline.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, up_queue: asyncio.Queue, **kwargs):
|
def __init__(self, up_queue: asyncio.Queue):
|
||||||
"""Initialize the pipeline task source.
|
"""Initialize the pipeline task source.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
up_queue: Queue for upstream frame processing.
|
up_queue: Queue for upstream frame processing.
|
||||||
**kwargs: Additional arguments passed to the parent class.
|
**kwargs: Additional arguments passed to the parent class.
|
||||||
"""
|
"""
|
||||||
super().__init__(**kwargs)
|
super().__init__(enable_direct_mode=True)
|
||||||
self._up_queue = up_queue
|
self._up_queue = up_queue
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
@@ -144,14 +144,14 @@ class PipelineTaskSink(FrameProcessor):
|
|||||||
act on them, for example, waiting to receive an EndFrame.
|
act on them, for example, waiting to receive an EndFrame.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, down_queue: asyncio.Queue, **kwargs):
|
def __init__(self, down_queue: asyncio.Queue):
|
||||||
"""Initialize the pipeline task sink.
|
"""Initialize the pipeline task sink.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
down_queue: Queue for downstream frame processing.
|
down_queue: Queue for downstream frame processing.
|
||||||
**kwargs: Additional arguments passed to the parent class.
|
**kwargs: Additional arguments passed to the parent class.
|
||||||
"""
|
"""
|
||||||
super().__init__(**kwargs)
|
super().__init__(enable_direct_mode=True)
|
||||||
self._down_queue = down_queue
|
self._down_queue = down_queue
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ class FrameProcessor(BaseObject):
|
|||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
|
enable_direct_mode: bool = False,
|
||||||
enable_watchdog_logging: Optional[bool] = None,
|
enable_watchdog_logging: Optional[bool] = None,
|
||||||
enable_watchdog_timers: Optional[bool] = None,
|
enable_watchdog_timers: Optional[bool] = None,
|
||||||
metrics: Optional[FrameProcessorMetrics] = None,
|
metrics: Optional[FrameProcessorMetrics] = None,
|
||||||
@@ -185,6 +186,7 @@ class FrameProcessor(BaseObject):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: Optional name for this processor instance.
|
name: Optional name for this processor instance.
|
||||||
|
enable_direct_mode: Whether to process frames immediately or use internal queues.
|
||||||
enable_watchdog_logging: Whether to enable watchdog logging for tasks.
|
enable_watchdog_logging: Whether to enable watchdog logging for tasks.
|
||||||
enable_watchdog_timers: Whether to enable watchdog timers for tasks.
|
enable_watchdog_timers: Whether to enable watchdog timers for tasks.
|
||||||
metrics: Optional metrics collector for this processor.
|
metrics: Optional metrics collector for this processor.
|
||||||
@@ -196,6 +198,9 @@ class FrameProcessor(BaseObject):
|
|||||||
self._prev: Optional["FrameProcessor"] = None
|
self._prev: Optional["FrameProcessor"] = None
|
||||||
self._next: Optional["FrameProcessor"] = None
|
self._next: Optional["FrameProcessor"] = None
|
||||||
|
|
||||||
|
# Enable direct mode to skip queues and process frames right away.
|
||||||
|
self._enable_direct_mode = enable_direct_mode
|
||||||
|
|
||||||
# Enable watchdog timers for all tasks created by this frame processor.
|
# Enable watchdog timers for all tasks created by this frame processor.
|
||||||
self._enable_watchdog_timers = enable_watchdog_timers
|
self._enable_watchdog_timers = enable_watchdog_timers
|
||||||
|
|
||||||
@@ -558,7 +563,10 @@ class FrameProcessor(BaseObject):
|
|||||||
if self._cancelling:
|
if self._cancelling:
|
||||||
return
|
return
|
||||||
|
|
||||||
await self.__input_queue.put((frame, direction, callback))
|
if self._enable_direct_mode:
|
||||||
|
await self.__process_frame(frame, direction, callback)
|
||||||
|
else:
|
||||||
|
await self.__input_queue.put((frame, direction, callback))
|
||||||
|
|
||||||
async def pause_processing_frames(self):
|
async def pause_processing_frames(self):
|
||||||
"""Pause processing of queued frames."""
|
"""Pause processing of queued frames."""
|
||||||
@@ -730,6 +738,9 @@ class FrameProcessor(BaseObject):
|
|||||||
|
|
||||||
def __create_input_task(self):
|
def __create_input_task(self):
|
||||||
"""Create the frame input processing task."""
|
"""Create the frame input processing task."""
|
||||||
|
if self._enable_direct_mode:
|
||||||
|
return
|
||||||
|
|
||||||
if not self.__input_frame_task:
|
if not self.__input_frame_task:
|
||||||
self.__input_queue = FrameProcessorQueue(self.task_manager)
|
self.__input_queue = FrameProcessorQueue(self.task_manager)
|
||||||
self.__input_frame_task = self.create_task(self.__input_frame_task_handler())
|
self.__input_frame_task = self.create_task(self.__input_frame_task_handler())
|
||||||
@@ -743,6 +754,9 @@ class FrameProcessor(BaseObject):
|
|||||||
|
|
||||||
def __create_process_task(self):
|
def __create_process_task(self):
|
||||||
"""Create the non-system frame processing task."""
|
"""Create the non-system frame processing task."""
|
||||||
|
if self._enable_direct_mode:
|
||||||
|
return
|
||||||
|
|
||||||
if not self.__process_frame_task:
|
if not self.__process_frame_task:
|
||||||
self.__should_block_frames = False
|
self.__should_block_frames = False
|
||||||
if not self.__process_event:
|
if not self.__process_event:
|
||||||
@@ -759,7 +773,7 @@ class FrameProcessor(BaseObject):
|
|||||||
self.__process_frame_task = None
|
self.__process_frame_task = None
|
||||||
|
|
||||||
async def __process_frame(
|
async def __process_frame(
|
||||||
self, frame: Frame, direction: FrameDirection, callback: FrameCallback
|
self, frame: Frame, direction: FrameDirection, callback: Optional[FrameCallback]
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
# Process the frame.
|
# Process the frame.
|
||||||
|
|||||||
Reference in New Issue
Block a user