Migrate to WHIP (#1120)

* migrate to whip part1

* migrate to whip part2

* migrate to whip part3

* migrate to whip part4

* migrate to whip/whep with bidirectional

* migrate to whip/whep with bidirectional

* use next library

* add _next to lid datachannel files

* cleanup and add todos

* use internal helper rtcGetPayloadTypesForCodec

* fix lint

* refactor decode()

* move logic to codecs

* move logic to codecs

* break encoders and decoders into multiple files

* break encoders and decoders into multiple files

* cleanup more

* drop action for stream endpoints, keep type for self documentation

* restore the v4 store

* fix: align frame_width and frame_height to even in both collect() and read_resolution() in both decoders.

---------

Co-authored-by: harisreedhar <h4harisreedhar.s.s@gmail.com>
This commit is contained in:
Henry Ruhs
2026-05-18 16:16:06 +02:00
committed by GitHub
parent c48c238f88
commit c00ea92f35
25 changed files with 969 additions and 633 deletions
+21 -35
View File
@@ -1,19 +1,18 @@
import tempfile
import threading
from functools import partial
from typing import Iterator
from unittest.mock import patch
import pytest
from starlette.testclient import TestClient
from facefusion import metadata, rtc, session_manager, state_manager
from facefusion import metadata, rtc, rtc_store, session_manager, state_manager
from facefusion.apis import asset_store
from facefusion.apis.core import create_api
from facefusion.core import common_pre_check
from facefusion.download import conditional_download
from facefusion.hash_helper import create_hash
from facefusion.libraries import datachannel as datachannel_module
from facefusion.types import VideoCodec
from .assert_helper import get_test_example_file, get_test_examples_directory
@@ -37,6 +36,7 @@ def before_all() -> None:
def before_each() -> None:
session_manager.SESSIONS.clear()
asset_store.clear()
rtc_store.clear()
@pytest.fixture(scope = 'module')
@@ -45,16 +45,6 @@ def test_client() -> Iterator[TestClient]:
yield test_client
@pytest.fixture(scope = 'function')
def create_event() -> threading.Event:
return threading.Event()
@pytest.mark.helper
def set_event(session_id : str, media_buffer : bytes, timestamp : int, event : threading.Event) -> None:
event.set()
def test_stream_image(test_client : TestClient) -> None:
create_session_response = test_client.post('/session', json =
{
@@ -85,7 +75,7 @@ def test_stream_image(test_client : TestClient) -> None:
assert select_response.status_code == 200
with test_client.websocket_connect('/stream?mode=image', subprotocols =
with test_client.websocket_connect('/stream?type=image&action=process', subprotocols =
[
'access_token.' + access_token
]) as websocket:
@@ -96,7 +86,7 @@ def test_stream_image(test_client : TestClient) -> None:
@pytest.mark.parametrize('video_codec', [ 'av1', 'vp8' ])
def test_stream_video(test_client : TestClient, create_event : threading.Event, video_codec : str) -> None:
def test_stream_video(test_client : TestClient, video_codec : VideoCodec) -> None:
create_session_response = test_client.post('/session', json =
{
'client_version': metadata.get('version')
@@ -124,27 +114,23 @@ def test_stream_video(test_client : TestClient, create_event : threading.Event,
'Authorization': 'Bearer ' + access_token
})
with patch('facefusion.rtc.send_video_to_peers', side_effect = partial(set_event, event = create_event)):
with test_client.websocket_connect('/stream?mode=video&codec=' + video_codec, subprotocols =
[
'access_token.' + access_token
]) as websocket:
websocket.send_bytes(chr(1).encode() + source_content)
websocket.receive_text()
peer_connection = rtc.create_peer_connection()
peer_connection = rtc.create_peer_connection()
rtc.add_video_track(peer_connection, 'recvonly', 'vp8', 96)
rtc.add_audio_track(peer_connection, 'recvonly', 'opus', 111)
sdp_offer = rtc.create_sdp_offer(peer_connection)
datachannel_module.create_static_library().rtcDeletePeerConnection(peer_connection)
stream_response = test_client.post('/stream', content = sdp_offer, headers =
{
'Authorization': 'Bearer ' + access_token,
'Content-Type': 'application/sdp'
})
if video_codec == 'av1':
rtc.add_video_track(peer_connection, 'sendrecv', video_codec, 35)
if video_codec == 'vp8':
rtc.add_video_track(peer_connection, 'sendrecv', video_codec, 96)
assert stream_response.status_code == 201
rtc.add_audio_track(peer_connection, 'sendrecv', 'opus', 111)
sdp_offer = rtc.create_sdp_offer(peer_connection)
datachannel_module.create_static_library().rtcDeletePeerConnection(peer_connection)
create_event.wait(timeout = 10)
with patch('facefusion.rtc.send_video'):
stream_response = test_client.post('/stream?type=video&action=process', content = sdp_offer, headers =
{
'Authorization': 'Bearer ' + access_token,
'Content-Type': 'application/sdp'
})
assert create_event.is_set()
assert stream_response.status_code == 201
assert 'm=video' in stream_response.text
+61
View File
@@ -0,0 +1,61 @@
from unittest.mock import patch
import cv2
import pytest
from tests.assert_helper import get_test_example_file, get_test_examples_directory
from facefusion import state_manager
from facefusion.codecs.aom_decoder import create, decode, destroy, read_resolution
from facefusion.codecs.aom_encoder import create as create_encoder, encode
from facefusion.download import conditional_download
from facefusion.libraries import aom as aom_module
from facefusion.vision import read_video_frame
@pytest.fixture(scope = 'module', autouse = True)
def before_all() -> None:
state_manager.init_item('download_providers', [ 'github', 'huggingface' ])
conditional_download(get_test_examples_directory(), [ 'https://github.com/facefusion/facefusion-assets/releases/download/examples-3.0.0/target-240p.mp4' ])
aom_module.pre_check()
#TODO: needs review
def test_create() -> None:
assert create()
#TODO: needs review
def test_decode() -> None:
vision_frame = read_video_frame(get_test_example_file('target-240p.mp4'))
video_buffer = cv2.cvtColor(vision_frame, cv2.COLOR_BGR2YUV_I420).tobytes()
video_resolution = (vision_frame.shape[1], vision_frame.shape[0])
aom_encoder = create_encoder(video_resolution, 1000, 1, 0)
encoded_buffer = encode(aom_encoder, video_buffer, video_resolution, 0)
decode_resolution = read_resolution(create(), encoded_buffer)
assert len(decode(create(), encoded_buffer)) == decode_resolution[0] * decode_resolution[1] * 3 // 2
assert decode(create(), bytes()) == bytes()
#TODO: needs review
def test_read_resolution() -> None:
vision_frame = read_video_frame(get_test_example_file('target-240p.mp4'))
video_buffer = cv2.cvtColor(vision_frame, cv2.COLOR_BGR2YUV_I420).tobytes()
video_resolution = (vision_frame.shape[1], vision_frame.shape[0])
aom_encoder = create_encoder(video_resolution, 1000, 1, 0)
encoded_buffer = encode(aom_encoder, video_buffer, video_resolution, 0)
assert read_resolution(create(), encoded_buffer)[0] >= video_resolution[0]
assert read_resolution(create(), encoded_buffer)[1] >= video_resolution[1]
assert read_resolution(create(), bytes()) is None
#TODO: needs review
def test_destroy() -> None:
aom_decoder = create()
with patch.object(aom_module.create_static_library(), 'aom_codec_destroy') as mock:
destroy(aom_decoder)
mock.assert_called_once_with(aom_decoder)
@@ -5,7 +5,7 @@ import pytest
from tests.assert_helper import get_test_example_file, get_test_examples_directory
from facefusion import state_manager
from facefusion.codecs.aom import create_aom_encoder, destroy_aom_encoder, encode_aom_buffer
from facefusion.codecs.aom_encoder import create, destroy, encode
from facefusion.common_helper import is_linux, is_macos, is_windows
from facefusion.download import conditional_download
from facefusion.hash_helper import create_hash
@@ -22,27 +22,27 @@ def before_all() -> None:
aom_module.pre_check()
def test_create_aom_encoder() -> None:
assert create_aom_encoder(1000, 8, 16, (320, 240))
assert create_aom_encoder(0, 0, 0, (0, 0)) is None
def test_create() -> None:
assert create((320, 240), 1000, 8, 16)
assert create((0, 0), 0, 0, 0) is None
def test_encode_aom_buffer() -> None:
def test_encode() -> None:
vision_frame = read_video_frame(get_test_example_file('target-240p.mp4'))
video_buffer = cv2.cvtColor(vision_frame, cv2.COLOR_BGR2YUV_I420).tobytes()
video_resolution = (vision_frame.shape[1], vision_frame.shape[0])
aom_encoder = create_aom_encoder(1000, 1, 0, video_resolution)
aom_encoder = create(video_resolution, 1000, 1, 0)
if is_linux() or is_windows():
assert create_hash(encode_aom_buffer(aom_encoder, video_buffer, video_resolution, 3)) == '3ab6cc31'
assert create_hash(encode(aom_encoder, video_buffer, video_resolution, 3)) == '3ab6cc31'
if is_macos():
pytest.skip()
def test_destroy_aom_encoder() -> None:
aom_encoder = create_aom_encoder(1000, 8, 16, (320, 240))
def test_destroy() -> None:
aom_encoder = create((320, 240), 1000, 8, 16)
with patch.object(aom_module.create_static_library(), 'aom_codec_destroy') as mock:
destroy_aom_encoder(aom_encoder)
destroy(aom_encoder)
mock.assert_called_once_with(aom_encoder)
+47
View File
@@ -0,0 +1,47 @@
from unittest.mock import patch
import numpy
import pytest
from tests.assert_helper import get_test_example_file, get_test_examples_directory
from facefusion import state_manager
from facefusion.codecs.opus_decoder import create, decode, destroy
from facefusion.codecs.opus_encoder import create as create_encoder, encode
from facefusion.download import conditional_download
from facefusion.ffmpeg import read_audio_buffer
from facefusion.libraries import opus as opus_module
@pytest.fixture(scope = 'module', autouse = True)
def before_all() -> None:
state_manager.init_item('download_providers', [ 'github', 'huggingface' ])
conditional_download(get_test_examples_directory(), [ 'https://github.com/facefusion/facefusion-assets/releases/download/examples-3.0.0/source.mp3' ])
opus_module.pre_check()
#TODO: needs review
def test_create() -> None:
assert create(48000, 2)
assert create(0, 0) is None
#TODO: needs review
def test_decode() -> None:
audio_buffer = read_audio_buffer(get_test_example_file('source.mp3'), 48000, 16, 2)
audio_sample = numpy.frombuffer(audio_buffer, dtype = numpy.int16).astype(numpy.float32) / 32768.0
opus_encoder = create_encoder(48000, 2)
encoded_buffer = encode(opus_encoder, audio_sample.tobytes(), 960)
opus_decoder = create(48000, 2)
assert len(decode(opus_decoder, encoded_buffer, 960, 2)) == 960 * 2 * 4
#TODO: needs review
def test_destroy() -> None:
opus_decoder = create(48000, 2)
with patch.object(opus_module.create_static_library(), 'opus_decoder_destroy') as mock:
destroy(opus_decoder)
mock.assert_called_once_with(opus_decoder)
@@ -5,7 +5,7 @@ import pytest
from tests.assert_helper import get_test_example_file, get_test_examples_directory
from facefusion import state_manager
from facefusion.codecs.opus import create_opus_encoder, destroy_opus_encoder, encode_opus_buffer
from facefusion.codecs.opus_encoder import create, destroy, encode
from facefusion.common_helper import is_linux, is_macos, is_windows
from facefusion.download import conditional_download
from facefusion.ffmpeg import read_audio_buffer
@@ -22,26 +22,26 @@ def before_all() -> None:
opus_module.pre_check()
def test_create_opus_encoder() -> None:
assert create_opus_encoder(48000, 2)
assert create_opus_encoder(0, 0) is None
def test_create() -> None:
assert create(48000, 2)
assert create(0, 0) is None
def test_encode_opus_buffer() -> None:
def test_encode() -> None:
audio_buffer = read_audio_buffer(get_test_example_file('source.mp3'), 48000, 16, 2)
audio_sample = numpy.frombuffer(audio_buffer, dtype = numpy.int16).astype(numpy.float32) / 32768.0
opus_encoder = create_opus_encoder(48000, 2)
opus_encoder = create(48000, 2)
if is_linux() or is_windows():
assert create_hash(encode_opus_buffer(opus_encoder, audio_sample.tobytes(), 960)) == '8abe71cf'
assert create_hash(encode(opus_encoder, audio_sample.tobytes(), 960)) == '8abe71cf'
if is_macos():
pytest.skip()
def test_destroy_opus_encoder() -> None:
opus_encoder = create_opus_encoder(48000, 2)
def test_destroy() -> None:
opus_encoder = create(48000, 2)
with patch.object(opus_module.create_static_library(), 'opus_encoder_destroy') as mock:
destroy_opus_encoder(opus_encoder)
destroy(opus_encoder)
mock.assert_called_once_with(opus_encoder)
+61
View File
@@ -0,0 +1,61 @@
from unittest.mock import patch
import cv2
import pytest
from tests.assert_helper import get_test_example_file, get_test_examples_directory
from facefusion import state_manager
from facefusion.codecs.vpx_decoder import create, decode, destroy, read_resolution
from facefusion.codecs.vpx_encoder import create as create_encoder, encode
from facefusion.download import conditional_download
from facefusion.libraries import vpx as vpx_module
from facefusion.vision import read_video_frame
@pytest.fixture(scope = 'module', autouse = True)
def before_all() -> None:
state_manager.init_item('download_providers', [ 'github', 'huggingface' ])
conditional_download(get_test_examples_directory(), [ 'https://github.com/facefusion/facefusion-assets/releases/download/examples-3.0.0/target-240p.mp4' ])
vpx_module.pre_check()
#TODO: needs review
def test_create() -> None:
assert create()
#TODO: needs review
def test_decode() -> None:
vision_frame = read_video_frame(get_test_example_file('target-240p.mp4'))
video_buffer = cv2.cvtColor(vision_frame, cv2.COLOR_BGR2YUV_I420).tobytes()
video_resolution = (vision_frame.shape[1], vision_frame.shape[0])
vpx_encoder = create_encoder(video_resolution, 1000, 1, 0)
encoded_buffer = encode(vpx_encoder, video_buffer, video_resolution, 0)
vpx_decoder = create()
assert len(decode(vpx_decoder, encoded_buffer)) == video_resolution[0] * video_resolution[1] * 3 // 2
assert decode(vpx_decoder, bytes()) == bytes()
#TODO: needs review
def test_read_resolution() -> None:
vision_frame = read_video_frame(get_test_example_file('target-240p.mp4'))
video_buffer = cv2.cvtColor(vision_frame, cv2.COLOR_BGR2YUV_I420).tobytes()
video_resolution = (vision_frame.shape[1], vision_frame.shape[0])
vpx_encoder = create_encoder(video_resolution, 1000, 1, 0)
encoded_buffer = encode(vpx_encoder, video_buffer, video_resolution, 0)
vpx_decoder = create()
assert read_resolution(vpx_decoder, encoded_buffer) == video_resolution
assert read_resolution(vpx_decoder, bytes()) is None
#TODO: needs review
def test_destroy() -> None:
vpx_decoder = create()
with patch.object(vpx_module.create_static_library(), 'vpx_codec_destroy') as mock:
destroy(vpx_decoder)
mock.assert_called_once_with(vpx_decoder)
@@ -5,7 +5,7 @@ import pytest
from tests.assert_helper import get_test_example_file, get_test_examples_directory
from facefusion import state_manager
from facefusion.codecs.vpx import create_vpx_encoder, destroy_vpx_encoder, encode_vpx_buffer
from facefusion.codecs.vpx_encoder import create, destroy, encode
from facefusion.common_helper import is_linux, is_macos, is_windows
from facefusion.download import conditional_download
from facefusion.hash_helper import create_hash
@@ -22,27 +22,27 @@ def before_all() -> None:
vpx_module.pre_check()
def test_create_vpx_encoder() -> None:
assert create_vpx_encoder(1000, 8, 16, (320, 240))
assert create_vpx_encoder(0, 0, 0, (0, 0)) is None
def test_create() -> None:
assert create((320, 240), 1000, 8, 16)
assert create((0, 0), 0, 0, 0) is None
def test_encode_vpx_buffer() -> None:
def test_encode() -> None:
vision_frame = read_video_frame(get_test_example_file('target-240p.mp4'))
video_buffer = cv2.cvtColor(vision_frame, cv2.COLOR_BGR2YUV_I420).tobytes()
video_resolution = (vision_frame.shape[1], vision_frame.shape[0])
vpx_encoder = create_vpx_encoder(1000, 1, 0, video_resolution)
vpx_encoder = create(video_resolution, 1000, 1, 0)
if is_linux() or is_windows():
assert create_hash(encode_vpx_buffer(vpx_encoder, video_buffer, video_resolution, 3)) == 'ce133a1f'
assert create_hash(encode(vpx_encoder, video_buffer, video_resolution, 3)) == 'ce133a1f'
if is_macos():
pytest.skip()
def test_destroy_vpx_encoder() -> None:
vpx_encoder = create_vpx_encoder(1000, 8, 16, (320, 240))
def test_destroy() -> None:
vpx_encoder = create((320, 240), 1000, 8, 16)
with patch.object(vpx_module.create_static_library(), 'vpx_codec_destroy') as mock:
destroy_vpx_encoder(vpx_encoder)
destroy(vpx_encoder)
mock.assert_called_once_with(vpx_encoder)
+39 -29
View File
@@ -4,7 +4,7 @@ import pytest
from facefusion import state_manager
from facefusion.libraries import datachannel as datachannel_module, opus as opus_module, vpx as vpx_module
from facefusion.rtc import add_audio_track, add_video_track, create_peer_connection, create_sdp_answer, create_sdp_offer, delete_peers, detect_sdp_media, send_audio_to_peers, send_video_to_peers, set_remote_description
from facefusion.rtc import add_audio_track, add_video_track, create_peer_connection, create_sdp_answer, create_sdp_offer, delete_peers, get_payload_type, send_audio, send_video, set_remote_description
from facefusion.types import RtcPeer
@@ -57,48 +57,56 @@ def test_create_sdp_answer() -> None:
assert 'm=video' in sdp_answer
assert 'VP8/90000' in sdp_answer
assert 'a=ssrc:42 cname:video' in sdp_answer
assert 'm=audio' in sdp_answer
assert 'opus/48000/2' in sdp_answer
assert 'a=ssrc:43 cname:audio' in sdp_answer
assert 'a=recvonly' in sdp_answer
assert datachannel_library.rtcDeletePeerConnection(sender_peer_connection) == 0
assert datachannel_library.rtcDeletePeerConnection(receiver_peer_connection) == 0
def test_send_audio_to_peers() -> None:
def test_send_video() -> None:
datachannel_library = datachannel_module.create_static_library()
peer_connection = create_peer_connection()
audio_track = add_audio_track(peer_connection, 'sendonly', 'opus', 111)
rtc_peers : List[RtcPeer] =\
[
video_track = add_video_track(peer_connection, 'sendonly', 'vp8', 96)
rtc_peer : RtcPeer =\
{
'peer_connection': peer_connection,
'video':
{
'peer_connection': peer_connection,
'video_track': 0,
'audio_track': audio_track
'sender_track': video_track,
'receiver_track': video_track,
'codec': 'vp8'
}
]
}
send_audio_to_peers(rtc_peers, bytes(960), 0)
send_video(rtc_peer, bytes(1024), 0)
datachannel_library.rtcDeletePeerConnection(peer_connection)
def test_send_video_to_peers() -> None:
def test_send_audio() -> None:
datachannel_library = datachannel_module.create_static_library()
peer_connection = create_peer_connection()
video_track = add_video_track(peer_connection, 'sendonly', 'vp8', 96)
rtc_peers : List[RtcPeer] =\
[
audio_track = add_audio_track(peer_connection, 'sendonly', 'opus', 111)
rtc_peer : RtcPeer =\
{
'peer_connection': peer_connection,
'video':
{
'peer_connection': peer_connection,
'video_track': video_track,
'audio_track': 0
'sender_track': 0,
'receiver_track': 0,
'codec': 'vp8'
},
'audio':
{
'sender_track': audio_track,
'receiver_track': audio_track,
'codec': 'opus'
}
]
}
send_video_to_peers(rtc_peers, bytes(1024), 0)
send_audio(rtc_peer, bytes(960), 0)
datachannel_library.rtcDeletePeerConnection(peer_connection)
@@ -110,8 +118,12 @@ def test_delete_peers() -> None:
[
{
'peer_connection': peer_connection,
'video_track': 0,
'audio_track': 0
'video':
{
'sender_track': 0,
'receiver_track': 0,
'codec': 'vp8'
}
}
]
@@ -120,16 +132,14 @@ def test_delete_peers() -> None:
assert datachannel_library.rtcDeletePeerConnection(peer_connection) == -1
def test_detect_sdp_media() -> None:
def test_get_payload_type() -> None:
peer_connection = create_peer_connection()
add_video_track(peer_connection, 'sendonly', 'vp8', 96)
add_audio_track(peer_connection, 'sendonly', 'opus', 111)
sdp_offer = create_sdp_offer(peer_connection)
sdp_payload = detect_sdp_media(sdp_offer)
assert sdp_payload.get('video').get('codec') == 'vp8'
assert sdp_payload.get('video').get('payload_type') == 96
assert sdp_payload.get('audio').get('codec') == 'opus'
assert sdp_payload.get('audio').get('payload_type') == 111
assert get_payload_type(sdp_offer, 'vp8') == 96
assert get_payload_type(sdp_offer, 'opus') == 111
assert get_payload_type(sdp_offer, 'av1') == 0
datachannel_module.create_static_library().rtcDeletePeerConnection(peer_connection)
-238
View File
@@ -1,238 +0,0 @@
import asyncio
import queue
from typing import Any, Optional
from unittest.mock import AsyncMock, MagicMock, patch
import cv2
import numpy
import pytest
from numpy.typing import NDArray
from starlette.websockets import WebSocketState
from facefusion.apis.stream_helper import encode_audio_loop, encode_video_loop, handle_video_stream
from facefusion.hash_helper import create_hash
from facefusion.types import VideoCodec, VisionFrame
def _make_handler_websocket(events : list[Any]) -> MagicMock:
mock = MagicMock()
mock.scope = {}
mock.client_state = WebSocketState.CONNECTED
mock.accept = AsyncMock()
mock.send_text = AsyncMock()
mock.close = AsyncMock()
mock.receive = AsyncMock(side_effect = events)
return mock
def _make_video_packet(frame : NDArray[Any]) -> bytes:
_, encoded = cv2.imencode('.jpg', frame)
return b'\x01' + encoded.tobytes()
def _make_audio_packet(samples : NDArray[Any]) -> bytes:
return b'\x02' + samples.tobytes()
@pytest.mark.parametrize('video_codec', [ 'av1', 'vp8' ])
def test_encode_video_loop(video_codec : VideoCodec) -> None:
frame = numpy.full((64, 64, 3), 128, dtype = numpy.uint8)
small_frame = numpy.full((64, 64, 3), 128, dtype = numpy.uint8)
large_frame = numpy.full((128, 128, 3), 128, dtype = numpy.uint8)
black_frame = numpy.zeros((64, 64, 3), dtype = numpy.uint8)
prefix = 'facefusion.apis.stream_helper.'
create_name = prefix + 'create_aom_encoder'
encode_name = prefix + 'encode_aom_buffer'
destroy_name = prefix + 'destroy_aom_encoder'
if video_codec == 'vp8':
create_name = prefix + 'create_vpx_encoder'
encode_name = prefix + 'encode_vpx_buffer'
destroy_name = prefix + 'destroy_vpx_encoder'
vision_frame_queue : queue.Queue[Optional[VisionFrame]] = queue.Queue()
vision_frame_queue.put(frame)
vision_frame_queue.put(None)
with patch(prefix + 'process_vision_frame', return_value = frame), \
patch(create_name, return_value = MagicMock()), \
patch(encode_name, return_value = b'encoded'), \
patch(destroy_name), \
patch(prefix + 'rtc_store') as mock_rtc_store, \
patch(prefix + 'rtc') as mock_rtc:
mock_rtc_store.get_peers.return_value = [ MagicMock() ]
encode_video_loop(video_codec, vision_frame_queue, 'session-1', (64, 64))
mock_rtc.send_video_to_peers.assert_called_once()
vision_frame_queue = queue.Queue()
vision_frame_queue.put(frame)
vision_frame_queue.put(frame)
vision_frame_queue.put(frame)
vision_frame_queue.put(None)
with patch(prefix + 'process_vision_frame', return_value = frame), \
patch(create_name, return_value = MagicMock()), \
patch(encode_name, return_value = b'encoded'), \
patch(destroy_name), \
patch(prefix + 'rtc_store') as mock_rtc_store, \
patch(prefix + 'rtc') as mock_rtc:
mock_rtc_store.get_peers.return_value = [ MagicMock() ]
encode_video_loop(video_codec, vision_frame_queue, 'session-1', (64, 64))
assert mock_rtc.send_video_to_peers.call_count == 3
vision_frame_queue = queue.Queue()
vision_frame_queue.put(black_frame)
with patch(create_name, return_value = MagicMock()), \
patch(destroy_name) as mock_destroy, \
patch(prefix + 'rtc') as mock_rtc:
encode_video_loop(video_codec, vision_frame_queue, 'session-1', (64, 64))
mock_rtc.send_video_to_peers.assert_not_called()
mock_destroy.assert_called_once()
vision_frame_queue = queue.Queue()
vision_frame_queue.put(frame)
vision_frame_queue.put(None)
with patch(prefix + 'process_vision_frame', return_value = frame), \
patch(create_name, return_value = MagicMock()), \
patch(encode_name, return_value = b''), \
patch(destroy_name), \
patch(prefix + 'rtc_store'), \
patch(prefix + 'rtc') as mock_rtc:
encode_video_loop(video_codec, vision_frame_queue, 'session-1', (64, 64))
mock_rtc.send_video_to_peers.assert_not_called()
vision_frame_queue = queue.Queue()
vision_frame_queue.put(small_frame)
vision_frame_queue.put(None)
with patch(prefix + 'process_vision_frame', return_value = large_frame), \
patch(create_name, return_value = MagicMock()) as mock_create, \
patch(encode_name, return_value = b'encoded'), \
patch(destroy_name) as mock_destroy, \
patch(prefix + 'rtc_store') as mock_rtc_store, \
patch(prefix + 'rtc') as mock_rtc:
mock_rtc_store.get_peers.return_value = [ MagicMock() ]
encode_video_loop(video_codec, vision_frame_queue, 'session-1', (64, 64))
assert mock_create.call_count == 2
assert mock_destroy.call_count == 2
mock_rtc.send_video_to_peers.assert_called_once()
vision_frame_queue = queue.Queue()
vision_frame_queue.put(frame)
vision_frame_queue.put(None)
with patch(create_name, return_value = None), \
patch(prefix + 'rtc') as mock_rtc:
encode_video_loop(video_codec, vision_frame_queue, 'session-1', (64, 64))
mock_rtc.send_video_to_peers.assert_not_called()
# TODO: refine test
def test_encode_audio_loop() -> None:
audio_chunk = numpy.zeros(1920, dtype = numpy.float32).tobytes()
audio_chunk_queue : queue.Queue[Optional[bytes]] = queue.Queue()
audio_chunk_queue.put(audio_chunk)
audio_chunk_queue.put(None)
with patch('facefusion.apis.stream_helper.create_opus_encoder', return_value = MagicMock()), \
patch('facefusion.apis.stream_helper.encode_opus_buffer', return_value = b'encoded'), \
patch('facefusion.apis.stream_helper.destroy_opus_encoder'), \
patch('facefusion.apis.stream_helper.rtc_store') as mock_rtc_store, \
patch('facefusion.apis.stream_helper.rtc') as mock_rtc:
mock_rtc_store.get_peers.return_value = [ MagicMock() ]
encode_audio_loop('opus', audio_chunk_queue, 'session-1')
mock_rtc.send_audio_to_peers.assert_called_once()
assert mock_rtc.send_audio_to_peers.call_args[0][2] == 0
audio_chunk_queue = queue.Queue()
audio_chunk_queue.put(audio_chunk)
audio_chunk_queue.put(audio_chunk)
audio_chunk_queue.put(None)
with patch('facefusion.apis.stream_helper.create_opus_encoder', return_value = MagicMock()), \
patch('facefusion.apis.stream_helper.encode_opus_buffer', return_value = b'encoded'), \
patch('facefusion.apis.stream_helper.destroy_opus_encoder'), \
patch('facefusion.apis.stream_helper.rtc_store') as mock_rtc_store, \
patch('facefusion.apis.stream_helper.rtc') as mock_rtc:
mock_rtc_store.get_peers.return_value = [ MagicMock() ]
encode_audio_loop('opus', audio_chunk_queue, 'session-1')
assert mock_rtc.send_audio_to_peers.call_count == 2
assert mock_rtc.send_audio_to_peers.call_args_list[0][0][2] == 0
assert mock_rtc.send_audio_to_peers.call_args_list[1][0][2] == 960
audio_chunk_queue = queue.Queue()
audio_chunk_queue.put(audio_chunk)
audio_chunk_queue.put(None)
with patch('facefusion.apis.stream_helper.create_opus_encoder', return_value = MagicMock()), \
patch('facefusion.apis.stream_helper.encode_opus_buffer', return_value = b''), \
patch('facefusion.apis.stream_helper.destroy_opus_encoder'), \
patch('facefusion.apis.stream_helper.rtc_store'), \
patch('facefusion.apis.stream_helper.rtc') as mock_rtc:
encode_audio_loop('opus', audio_chunk_queue, 'session-1')
mock_rtc.send_audio_to_peers.assert_not_called()
audio_chunk_queue = queue.Queue()
audio_chunk_queue.put(audio_chunk)
audio_chunk_queue.put(None)
with patch('facefusion.apis.stream_helper.create_opus_encoder', return_value = MagicMock()), \
patch('facefusion.apis.stream_helper.encode_opus_buffer', return_value = None), \
patch('facefusion.apis.stream_helper.destroy_opus_encoder') as mock_destroy, \
patch('facefusion.apis.stream_helper.rtc_store'), \
patch('facefusion.apis.stream_helper.rtc'):
encode_audio_loop('opus', audio_chunk_queue, 'session-1')
mock_destroy.assert_called_once()
audio_chunk_queue = queue.Queue()
audio_chunk_queue.put(b'')
with patch('facefusion.apis.stream_helper.create_opus_encoder', return_value = MagicMock()), \
patch('facefusion.apis.stream_helper.destroy_opus_encoder') as mock_destroy, \
patch('facefusion.apis.stream_helper.rtc') as mock_rtc:
encode_audio_loop('opus', audio_chunk_queue, 'session-1')
mock_rtc.send_audio_to_peers.assert_not_called()
mock_destroy.assert_called_once()
# TODO: refine test
def test_handle_video_stream() -> None:
frame = numpy.full((64, 64, 3), 128, dtype = numpy.uint8)
video_packet = _make_video_packet(frame)
audio_packet = _make_audio_packet(numpy.zeros(1920, dtype = numpy.float32))
websocket = _make_handler_websocket([ {'type': 'websocket.receive', 'bytes': video_packet}, {'type': 'websocket.disconnect'} ])
with patch('facefusion.apis.stream_helper.get_sec_websocket_protocol', return_value = 'proto'), \
patch('facefusion.apis.stream_helper.extract_access_token', return_value = 'token'), \
patch('facefusion.apis.stream_helper.session_manager.find_session_id', return_value = 'session-1'), \
patch('facefusion.apis.stream_helper.session_context.set_session_id'), \
patch('facefusion.apis.stream_helper.state_manager.get_item', return_value = 30), \
patch('facefusion.apis.stream_helper.encode_video_loop') as mock_loop, \
patch('facefusion.apis.stream_helper.encode_audio_loop'), \
patch('facefusion.apis.stream_helper.rtc_store') as mock_rtc:
asyncio.run(handle_video_stream(websocket))
websocket.accept.assert_called_once_with(subprotocol = 'proto')
websocket.send_text.assert_called_once_with('ready')
websocket.close.assert_called_once()
mock_rtc.init_peers.assert_called_once_with('session-1')
mock_rtc.delete_peers.assert_called_once_with('session-1')
_, _, loop_session_id, loop_resolution = mock_loop.call_args[0]
assert loop_session_id == 'session-1'
assert loop_resolution == (64, 64)
websocket = _make_handler_websocket([ {'type': 'websocket.receive', 'bytes': video_packet}, {'type': 'websocket.disconnect'} ])
with patch('facefusion.apis.stream_helper.get_sec_websocket_protocol', return_value = 'proto'), \
patch('facefusion.apis.stream_helper.extract_access_token', return_value = 'token'), \
patch('facefusion.apis.stream_helper.session_manager.find_session_id', return_value = None), \
patch('facefusion.apis.stream_helper.session_context.set_session_id'), \
patch('facefusion.apis.stream_helper.rtc_store') as mock_rtc:
asyncio.run(handle_video_stream(websocket))
websocket.accept.assert_called_once()
websocket.send_text.assert_not_called()
mock_rtc.init_peers.assert_not_called()
websocket = _make_handler_websocket([ {'type': 'websocket.receive', 'bytes': video_packet}, {'type': 'websocket.receive', 'bytes': audio_packet}, {'type': 'websocket.disconnect'} ])
with patch('facefusion.apis.stream_helper.get_sec_websocket_protocol', return_value = 'proto'), \
patch('facefusion.apis.stream_helper.extract_access_token', return_value = 'token'), \
patch('facefusion.apis.stream_helper.session_manager.find_session_id', return_value = 'session-1'), \
patch('facefusion.apis.stream_helper.session_context.set_session_id'), \
patch('facefusion.apis.stream_helper.state_manager.get_item', return_value = 30), \
patch('facefusion.apis.stream_helper.encode_video_loop'), \
patch('facefusion.apis.stream_helper.encode_audio_loop') as mock_audio_loop, \
patch('facefusion.apis.stream_helper.rtc_store'):
asyncio.run(handle_video_stream(websocket))
audio_queue = mock_audio_loop.call_args[0][1]
assert create_hash(audio_queue.get_nowait()) == '6d72f0fc'