transports(output): create an downstream push frame task
This commit is contained in:
@@ -39,8 +39,7 @@ class BaseInputTransport(FrameProcessor):
|
|||||||
self._audio_in_queue = queue.Queue()
|
self._audio_in_queue = queue.Queue()
|
||||||
|
|
||||||
# Create push frame task. This is the task that will push frames in
|
# Create push frame task. This is the task that will push frames in
|
||||||
# order. So, a transport guarantees that all frames are pushed in the
|
# order. We also guarantee that all frames are pushed in the same task.
|
||||||
# same task.
|
|
||||||
self._create_push_task()
|
self._create_push_task()
|
||||||
|
|
||||||
async def start(self, frame: StartFrame):
|
async def start(self, frame: StartFrame):
|
||||||
|
|||||||
@@ -71,6 +71,10 @@ class BaseOutputTransport(FrameProcessor):
|
|||||||
|
|
||||||
self._sink_thread = loop.run_in_executor(None, self._sink_thread_handler)
|
self._sink_thread = loop.run_in_executor(None, self._sink_thread_handler)
|
||||||
|
|
||||||
|
# Create push frame task. This is the task that will push frames in
|
||||||
|
# order. We also guarantee that all frames are pushed in the same task.
|
||||||
|
self._create_push_task()
|
||||||
|
|
||||||
async def stop(self):
|
async def stop(self):
|
||||||
if not self._running:
|
if not self._running:
|
||||||
return
|
return
|
||||||
@@ -101,9 +105,14 @@ class BaseOutputTransport(FrameProcessor):
|
|||||||
await self._sink_thread
|
await self._sink_thread
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
|
#
|
||||||
|
# Out-of-band frames like (CancelFrame or StartInterruptionFrame) are
|
||||||
|
# pushed immediately. Other frames require order so they are put in the
|
||||||
|
# sink queue.
|
||||||
|
#
|
||||||
if isinstance(frame, StartFrame):
|
if isinstance(frame, StartFrame):
|
||||||
await self.start(frame)
|
await self.start(frame)
|
||||||
await self.push_frame(frame, direction)
|
self._sink_queue.put(frame)
|
||||||
# EndFrame is managed in the queue handler.
|
# EndFrame is managed in the queue handler.
|
||||||
elif isinstance(frame, CancelFrame):
|
elif isinstance(frame, CancelFrame):
|
||||||
await self.stop()
|
await self.stop()
|
||||||
@@ -111,10 +120,8 @@ class BaseOutputTransport(FrameProcessor):
|
|||||||
elif isinstance(frame, StartInterruptionFrame) or isinstance(frame, StopInterruptionFrame):
|
elif isinstance(frame, StartInterruptionFrame) or isinstance(frame, StopInterruptionFrame):
|
||||||
await self._handle_interruptions(frame)
|
await self._handle_interruptions(frame)
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
elif self._frame_managed_by_sink(frame):
|
|
||||||
self._sink_queue.put(frame)
|
|
||||||
else:
|
else:
|
||||||
await self.push_frame(frame, direction)
|
self._sink_queue.put(frame)
|
||||||
|
|
||||||
# If we are finishing, wait here until we have stopped, otherwise we might
|
# If we are finishing, wait here until we have stopped, otherwise we might
|
||||||
# close things too early upstream. We need this event because we don't
|
# close things too early upstream. We need this event because we don't
|
||||||
@@ -122,19 +129,14 @@ class BaseOutputTransport(FrameProcessor):
|
|||||||
if isinstance(frame, CancelFrame) or isinstance(frame, EndFrame):
|
if isinstance(frame, CancelFrame) or isinstance(frame, EndFrame):
|
||||||
await self._stopped_event.wait()
|
await self._stopped_event.wait()
|
||||||
|
|
||||||
def _frame_managed_by_sink(self, frame: Frame):
|
|
||||||
return (isinstance(frame, AudioRawFrame)
|
|
||||||
or isinstance(frame, ImageRawFrame)
|
|
||||||
or isinstance(frame, SpriteFrame)
|
|
||||||
or isinstance(frame, TransportMessageFrame)
|
|
||||||
or isinstance(frame, EndFrame))
|
|
||||||
|
|
||||||
async def _handle_interruptions(self, frame: Frame):
|
async def _handle_interruptions(self, frame: Frame):
|
||||||
if not self._allow_interruptions:
|
if not self._allow_interruptions:
|
||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(frame, StartInterruptionFrame):
|
if isinstance(frame, StartInterruptionFrame):
|
||||||
self._is_interrupted.set()
|
self._is_interrupted.set()
|
||||||
|
self._push_frame_task.cancel()
|
||||||
|
self._create_push_task()
|
||||||
elif isinstance(frame, StopInterruptionFrame):
|
elif isinstance(frame, StopInterruptionFrame):
|
||||||
self._is_interrupted.clear()
|
self._is_interrupted.clear()
|
||||||
|
|
||||||
@@ -152,12 +154,6 @@ class BaseOutputTransport(FrameProcessor):
|
|||||||
try:
|
try:
|
||||||
frame = self._sink_queue.get(timeout=1)
|
frame = self._sink_queue.get(timeout=1)
|
||||||
|
|
||||||
if isinstance(frame, EndFrame):
|
|
||||||
# Send all remaining audio before stopping (multiple of 10ms of audio).
|
|
||||||
self._send_audio_truncated(buffer, bytes_size_10ms)
|
|
||||||
future = asyncio.run_coroutine_threadsafe(self.stop(), self.get_event_loop())
|
|
||||||
future.result()
|
|
||||||
|
|
||||||
if not self._is_interrupted.is_set():
|
if not self._is_interrupted.is_set():
|
||||||
if isinstance(frame, AudioRawFrame):
|
if isinstance(frame, AudioRawFrame):
|
||||||
if self._params.audio_out_enabled:
|
if self._params.audio_out_enabled:
|
||||||
@@ -169,17 +165,50 @@ class BaseOutputTransport(FrameProcessor):
|
|||||||
self._set_camera_images(frame.images)
|
self._set_camera_images(frame.images)
|
||||||
elif isinstance(frame, TransportMessageFrame):
|
elif isinstance(frame, TransportMessageFrame):
|
||||||
self.send_message(frame)
|
self.send_message(frame)
|
||||||
|
else:
|
||||||
|
future = asyncio.run_coroutine_threadsafe(
|
||||||
|
self._internal_push_frame(frame), self.get_event_loop())
|
||||||
|
future.result()
|
||||||
else:
|
else:
|
||||||
# Send any remaining audio
|
# Send any remaining audio
|
||||||
self._send_audio_truncated(buffer, bytes_size_10ms)
|
self._send_audio_truncated(buffer, bytes_size_10ms)
|
||||||
buffer = bytearray()
|
buffer = bytearray()
|
||||||
|
|
||||||
|
if isinstance(frame, EndFrame):
|
||||||
|
# Send all remaining audio before stopping (multiple of 10ms of audio).
|
||||||
|
self._send_audio_truncated(buffer, bytes_size_10ms)
|
||||||
|
future = asyncio.run_coroutine_threadsafe(self.stop(), self.get_event_loop())
|
||||||
|
future.result()
|
||||||
|
|
||||||
self._sink_queue.task_done()
|
self._sink_queue.task_done()
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
pass
|
pass
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
logger.error(f"Error processing sink queue: {e}")
|
logger.error(f"Error processing sink queue: {e}")
|
||||||
|
|
||||||
|
#
|
||||||
|
# Push frames task
|
||||||
|
#
|
||||||
|
|
||||||
|
def _create_push_task(self):
|
||||||
|
loop = self.get_event_loop()
|
||||||
|
self._push_frame_task = loop.create_task(self._push_frame_task_handler())
|
||||||
|
self._push_queue = asyncio.Queue()
|
||||||
|
|
||||||
|
async def _internal_push_frame(
|
||||||
|
self,
|
||||||
|
frame: Frame | None,
|
||||||
|
direction: FrameDirection | None = FrameDirection.DOWNSTREAM):
|
||||||
|
await self._push_queue.put((frame, direction))
|
||||||
|
|
||||||
|
async def _push_frame_task_handler(self):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
(frame, direction) = await self._push_queue.get()
|
||||||
|
await self.push_frame(frame, direction)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
break
|
||||||
|
|
||||||
#
|
#
|
||||||
# Camera out
|
# Camera out
|
||||||
#
|
#
|
||||||
|
|||||||
Reference in New Issue
Block a user