FrameProcessor: fix system frame higher priorty and use a PriortyQueue
This commit is contained in:
@@ -18,6 +18,11 @@ 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.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed an issue that would cause system frames to not be processed with higher
|
||||||
|
priority than other frames. This could cause slower interruption times.
|
||||||
|
|
||||||
### Other
|
### Other
|
||||||
|
|
||||||
- Improving the latency of the `HeyGenVideoService`.
|
- Improving the latency of the `HeyGenVideoService`.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ management, and frame flow control mechanisms.
|
|||||||
import asyncio
|
import asyncio
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Awaitable, Callable, Coroutine, List, Optional, Sequence
|
from typing import Any, Awaitable, Callable, Coroutine, List, Optional, Sequence, Tuple
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ from pipecat.observers.base_observer import BaseObserver, FramePushed
|
|||||||
from pipecat.processors.metrics.frame_processor_metrics import FrameProcessorMetrics
|
from pipecat.processors.metrics.frame_processor_metrics import FrameProcessorMetrics
|
||||||
from pipecat.utils.asyncio.task_manager import BaseTaskManager
|
from pipecat.utils.asyncio.task_manager import BaseTaskManager
|
||||||
from pipecat.utils.asyncio.watchdog_event import WatchdogEvent
|
from pipecat.utils.asyncio.watchdog_event import WatchdogEvent
|
||||||
|
from pipecat.utils.asyncio.watchdog_priority_queue import WatchdogPriorityQueue
|
||||||
from pipecat.utils.asyncio.watchdog_queue import WatchdogQueue
|
from pipecat.utils.asyncio.watchdog_queue import WatchdogQueue
|
||||||
from pipecat.utils.base_object import BaseObject
|
from pipecat.utils.base_object import BaseObject
|
||||||
|
|
||||||
@@ -54,6 +55,9 @@ class FrameDirection(Enum):
|
|||||||
UPSTREAM = 2
|
UPSTREAM = 2
|
||||||
|
|
||||||
|
|
||||||
|
FrameCallback = Callable[["FrameProcessor", Frame, FrameDirection], Awaitable[None]]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FrameProcessorSetup:
|
class FrameProcessorSetup:
|
||||||
"""Configuration parameters for frame processor initialization.
|
"""Configuration parameters for frame processor initialization.
|
||||||
@@ -71,7 +75,7 @@ class FrameProcessorSetup:
|
|||||||
watchdog_timers_enabled: bool = False
|
watchdog_timers_enabled: bool = False
|
||||||
|
|
||||||
|
|
||||||
class FrameProcessorQueue(WatchdogQueue):
|
class FrameProcessorQueue(WatchdogPriorityQueue):
|
||||||
"""A priority queue for systems frames and other frames.
|
"""A priority queue for systems frames and other frames.
|
||||||
|
|
||||||
This is a specialized queue for frame processors that separates and
|
This is a specialized queue for frame processors that separates and
|
||||||
@@ -88,6 +92,9 @@ class FrameProcessorQueue(WatchdogQueue):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
HIGH_PRIORITY = 1
|
||||||
|
LOW_PRIORITY = 2
|
||||||
|
|
||||||
def __init__(self, manager: BaseTaskManager):
|
def __init__(self, manager: BaseTaskManager):
|
||||||
"""Initialize the FrameProcessorQueue.
|
"""Initialize the FrameProcessorQueue.
|
||||||
|
|
||||||
@@ -96,11 +103,10 @@ class FrameProcessorQueue(WatchdogQueue):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
super().__init__(manager)
|
super().__init__(manager)
|
||||||
self.__event = WatchdogEvent(manager)
|
self.__counter = 0
|
||||||
self.__main_queue = WatchdogQueue(manager)
|
self.__system_counter = 0
|
||||||
self.__system_queue = WatchdogQueue(manager)
|
|
||||||
|
|
||||||
async def put(self, item: Any):
|
async def put(self, item: Tuple[Frame, FrameDirection, FrameCallback]):
|
||||||
"""Put an item into the appropriate queue.
|
"""Put an item into the appropriate queue.
|
||||||
|
|
||||||
System frames (`SystemFrame`) are placed into the system queue and all others
|
System frames (`SystemFrame`) are placed into the system queue and all others
|
||||||
@@ -110,11 +116,13 @@ class FrameProcessorQueue(WatchdogQueue):
|
|||||||
item (Any): The item to enqueue.
|
item (Any): The item to enqueue.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if isinstance(item, SystemFrame):
|
frame, _, _ = item
|
||||||
await self.__system_queue.put(item)
|
if isinstance(frame, SystemFrame):
|
||||||
|
await super().put((self.HIGH_PRIORITY, self.__system_counter, item))
|
||||||
|
self.__system_counter += 1
|
||||||
else:
|
else:
|
||||||
await self.__main_queue.put(item)
|
await super().put((self.LOW_PRIORITY, self.__counter, item))
|
||||||
self.__event.set()
|
self.__counter += 1
|
||||||
|
|
||||||
async def get(self) -> Any:
|
async def get(self) -> Any:
|
||||||
"""Retrieve the next item from the queue.
|
"""Retrieve the next item from the queue.
|
||||||
@@ -126,38 +134,9 @@ class FrameProcessorQueue(WatchdogQueue):
|
|||||||
Any: The next item from the system or main queue.
|
Any: The next item from the system or main queue.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Wait for an item in any of the queues if they are empty.
|
_, _, item = await super().get()
|
||||||
if self.__main_queue.empty() and self.__system_queue.empty():
|
|
||||||
await self.__event.wait()
|
|
||||||
|
|
||||||
# Prioritize system frames.
|
|
||||||
if self.__system_queue.qsize() > 0:
|
|
||||||
item = await self.__system_queue.get()
|
|
||||||
self.__system_queue.task_done()
|
|
||||||
else:
|
|
||||||
item = await self.__main_queue.get()
|
|
||||||
self.__main_queue.task_done()
|
|
||||||
|
|
||||||
# Clear the event only if all queues are empty.
|
|
||||||
if self.__main_queue.empty() and self.__system_queue.empty():
|
|
||||||
self.__event.clear()
|
|
||||||
|
|
||||||
return item
|
return item
|
||||||
|
|
||||||
def cancel(self):
|
|
||||||
"""Cancel both internal queues.
|
|
||||||
|
|
||||||
This method is used to stop processing and release any pending tasks
|
|
||||||
in both the system and main queues. Typically used during shutdown
|
|
||||||
or cleanup to prevent further processing of frames.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.__main_queue.cancel()
|
|
||||||
self.__system_queue.cancel()
|
|
||||||
|
|
||||||
|
|
||||||
FrameCallback = Callable[["FrameProcessor", Frame, FrameDirection], Awaitable[None]]
|
|
||||||
|
|
||||||
|
|
||||||
class FrameProcessor(BaseObject):
|
class FrameProcessor(BaseObject):
|
||||||
"""Base class for all frame processors in the pipeline.
|
"""Base class for all frame processors in the pipeline.
|
||||||
@@ -254,9 +233,7 @@ class FrameProcessor(BaseObject):
|
|||||||
# called. To resume processing frames we need to call
|
# called. To resume processing frames we need to call
|
||||||
# `resume_processing_frames()` which will wake up the event.
|
# `resume_processing_frames()` which will wake up the event.
|
||||||
self.__should_block_frames = False
|
self.__should_block_frames = False
|
||||||
self.__process_event = None
|
|
||||||
self.__process_frame_task: Optional[asyncio.Task] = None
|
self.__process_frame_task: Optional[asyncio.Task] = None
|
||||||
self.__process_queue = None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def id(self) -> int:
|
def id(self) -> int:
|
||||||
@@ -745,9 +722,7 @@ class FrameProcessor(BaseObject):
|
|||||||
"""Create the non-system frame processing task."""
|
"""Create the non-system frame processing task."""
|
||||||
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:
|
self.__process_event = WatchdogEvent(self.task_manager)
|
||||||
self.__process_event = WatchdogEvent(self.task_manager)
|
|
||||||
self.__process_event.clear()
|
|
||||||
self.__process_queue = WatchdogQueue(self.task_manager)
|
self.__process_queue = WatchdogQueue(self.task_manager)
|
||||||
self.__process_frame_task = self.create_task(self.__process_frame_task_handler())
|
self.__process_frame_task = self.create_task(self.__process_frame_task_handler())
|
||||||
|
|
||||||
@@ -790,10 +765,12 @@ class FrameProcessor(BaseObject):
|
|||||||
f"{self}: __process_queue is None when processing frame {frame.name}"
|
f"{self}: __process_queue is None when processing frame {frame.name}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.__input_queue.task_done()
|
||||||
|
|
||||||
async def __process_frame_task_handler(self):
|
async def __process_frame_task_handler(self):
|
||||||
"""Handle non-system frames from the process queue."""
|
"""Handle non-system frames from the process queue."""
|
||||||
while True:
|
while True:
|
||||||
if self.__should_block_frames and self.__process_event:
|
if self.__should_block_frames:
|
||||||
logger.trace(f"{self}: frame processing paused")
|
logger.trace(f"{self}: frame processing paused")
|
||||||
await self.__process_event.wait()
|
await self.__process_event.wait()
|
||||||
self.__process_event.clear()
|
self.__process_event.clear()
|
||||||
@@ -803,3 +780,5 @@ class FrameProcessor(BaseObject):
|
|||||||
(frame, direction, callback) = await self.__process_queue.get()
|
(frame, direction, callback) = await self.__process_queue.get()
|
||||||
|
|
||||||
await self.__process_frame(frame, direction, callback)
|
await self.__process_frame(frame, direction, callback)
|
||||||
|
|
||||||
|
self.__process_queue.task_done()
|
||||||
|
|||||||
Reference in New Issue
Block a user