diff --git a/facefusion/datachannel.py b/facefusion/datachannel.py new file mode 100644 index 00000000..13e89182 --- /dev/null +++ b/facefusion/datachannel.py @@ -0,0 +1,141 @@ +import ctypes +from functools import lru_cache +from typing import Dict, Optional + +from facefusion.common_helper import is_linux, is_macos, is_windows +from facefusion.filesystem import resolve_relative_path +from facefusion.types import DownloadSet + +LOG_CB_TYPE = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p) + + +@lru_cache +def create_static_download_set() -> Dict[str, DownloadSet]: # TODO: replace once conda package is in place + binary_name = resolve_binary_file() + + return\ + { + 'hashes': + { + 'datachannel': + { + 'url': 'https://huggingface.co/bluefoxcreation/libdatachannel/resolve/main/linux-x64-openssl-h264-vp8-av1-opus-libdatachannel-0.24.1.so.hash', # TODO: use url with dynamic binary_name + 'path': resolve_relative_path('../.assets/binaries/' + binary_name + '.hash') + } + }, + 'sources': + { + 'datachannel': + { + 'url': 'https://huggingface.co/bluefoxcreation/libdatachannel/resolve/main/linux-x64-openssl-h264-vp8-av1-opus-libdatachannel-0.24.1.so', # TODO: use url with dynamic binary_name + 'path': resolve_relative_path('../.assets/binaries/' + binary_name) + } + } + } + + +def resolve_binary_file() -> Optional[str]: + if is_linux(): + return 'linux-x64-openssl-h264-vp8-av1-opus-libdatachannel-0.24.1.so' + if is_macos(): + return 'macos-universal-openssl-h264-vp8-av1-opus-libdatachannel-0.24.1.dylib' + if is_windows(): + return 'windows-x64-openssl-h264-vp8-av1-opus-datachannel-0.24.1.dll' + return None + + +def create_rtc_configuration() -> ctypes.Structure: + return type('RTC_CONFIGURATION', (ctypes.Structure,), + { + '_fields_': + [ + ('iceServers', ctypes.POINTER(ctypes.c_char_p)), + ('iceServersCount', ctypes.c_int), + ('proxyServer', ctypes.c_char_p), + ('bindAddress', ctypes.c_char_p), + ('certificateType', ctypes.c_int), + ('iceTransportPolicy', ctypes.c_int), + ('enableIceTcp', ctypes.c_bool), + ('enableIceUdpMux', ctypes.c_bool), + ('disableAutoNegotiation', ctypes.c_bool), + ('forceMediaTransport', ctypes.c_bool), + ('portRangeBegin', ctypes.c_ushort), + ('portRangeEnd', ctypes.c_ushort), + ('mtu', ctypes.c_int), + ('maxMessageSize', ctypes.c_int) + ] + })() + + +def create_rtc_packetizer_init() -> ctypes.Structure: + return type('RTC_PACKETIZER_INIT', (ctypes.Structure,), + { + '_fields_': + [ + ('ssrc', ctypes.c_uint32), + ('cname', ctypes.c_char_p), + ('payloadType', ctypes.c_uint8), + ('clockRate', ctypes.c_uint32), + ('sequenceNumber', ctypes.c_uint16), + ('timestamp', ctypes.c_uint32), + ('maxFragmentSize', ctypes.c_uint16) + ] + })() + + +@lru_cache +def create_static_datachannel_library() -> Optional[ctypes.CDLL]: + binary_path = create_static_download_set().get('sources').get('datachannel').get('path') + + if binary_path: + datachannel_library = ctypes.CDLL(binary_path) + return init_ctypes(datachannel_library) + + return None + + +def init_ctypes(datachannel_library : ctypes.CDLL) -> ctypes.CDLL: + datachannel_library.rtcInitLogger.argtypes = [ ctypes.c_int, LOG_CB_TYPE ] + datachannel_library.rtcInitLogger.restype = None + datachannel_library.rtcInitLogger(4, LOG_CB_TYPE(0)) + + datachannel_library.rtcCreatePeerConnection.restype = ctypes.c_int + + datachannel_library.rtcDeletePeerConnection.argtypes = [ ctypes.c_int ] + datachannel_library.rtcDeletePeerConnection.restype = ctypes.c_int + + datachannel_library.rtcSetLocalDescription.argtypes = [ ctypes.c_int, ctypes.c_char_p ] + datachannel_library.rtcSetLocalDescription.restype = ctypes.c_int + + datachannel_library.rtcSetRemoteDescription.argtypes = [ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p ] + datachannel_library.rtcSetRemoteDescription.restype = ctypes.c_int + + datachannel_library.rtcAddTrack.argtypes = [ ctypes.c_int, ctypes.c_char_p ] + datachannel_library.rtcAddTrack.restype = ctypes.c_int + + datachannel_library.rtcSendMessage.argtypes = [ ctypes.c_int, ctypes.c_void_p, ctypes.c_int ] + datachannel_library.rtcSendMessage.restype = ctypes.c_int + + datachannel_library.rtcSetVP8Packetizer.restype = ctypes.c_int + + datachannel_library.rtcChainRtcpSrReporter.argtypes = [ ctypes.c_int ] + datachannel_library.rtcChainRtcpSrReporter.restype = ctypes.c_int + + datachannel_library.rtcSetTrackRtpTimestamp.argtypes = [ ctypes.c_int, ctypes.c_uint32 ] + datachannel_library.rtcSetTrackRtpTimestamp.restype = ctypes.c_int + + datachannel_library.rtcIsOpen.argtypes = [ ctypes.c_int ] + datachannel_library.rtcIsOpen.restype = ctypes.c_bool + + datachannel_library.rtcChainRtcpNackResponder.argtypes = [ ctypes.c_int, ctypes.c_uint ] + datachannel_library.rtcChainRtcpNackResponder.restype = ctypes.c_int + + datachannel_library.rtcGetLocalDescription.argtypes = [ ctypes.c_int, ctypes.c_char_p, ctypes.c_int ] + datachannel_library.rtcGetLocalDescription.restype = ctypes.c_int + + datachannel_library.rtcSetLocalDescription.argtypes = [ ctypes.c_int, ctypes.c_char_p ] + datachannel_library.rtcSetLocalDescription.restype = ctypes.c_int + + datachannel_library.rtcSetOpusPacketizer.restype = ctypes.c_int + + return datachannel_library diff --git a/facefusion/rtc.py b/facefusion/rtc.py index 08386a15..e9278f2d 100644 --- a/facefusion/rtc.py +++ b/facefusion/rtc.py @@ -1,48 +1,10 @@ import ctypes import time -from functools import lru_cache -from typing import Dict, List, Optional +from typing import List, Optional -from facefusion.common_helper import is_linux, is_macos, is_windows +from facefusion.datachannel import create_rtc_configuration, create_rtc_packetizer_init, create_static_datachannel_library, create_static_download_set from facefusion.download import conditional_download_hashes, conditional_download_sources -from facefusion.filesystem import resolve_relative_path -from facefusion.rtc_bindings import RTC_CONFIGURATION, RTC_PACKETIZER_INIT, init_ctypes -from facefusion.types import DownloadSet, MediaDirection, PeerConnection, RtcAudioTrack, RtcPeer, RtcVideoTrack, SdpAnswer, SdpOffer - - -def resolve_binary_file() -> Optional[str]: - if is_linux(): - return 'linux-x64-openssl-h264-vp8-av1-opus-libdatachannel-0.24.1.so' - if is_macos(): - return 'macos-universal-openssl-h264-vp8-av1-opus-libdatachannel-0.24.1.dylib' - if is_windows(): - return 'windows-x64-openssl-h264-vp8-av1-opus-datachannel-0.24.1.dll' - return None - - -@lru_cache -def create_static_download_set() -> Dict[str, DownloadSet]: # TODO: replace once conda package is in place - binary_name = resolve_binary_file() - - return\ - { - 'hashes': - { - 'datachannel': - { - 'url': 'https://huggingface.co/bluefoxcreation/libdatachannel/resolve/main/linux-x64-openssl-h264-vp8-av1-opus-libdatachannel-0.24.1.so.hash', # TODO: use url with dynamic binary_name - 'path': resolve_relative_path('../.assets/binaries/' + binary_name + '.hash') - } - }, - 'sources': - { - 'datachannel': - { - 'url': 'https://huggingface.co/bluefoxcreation/libdatachannel/resolve/main/linux-x64-openssl-h264-vp8-av1-opus-libdatachannel-0.24.1.so', # TODO: use url with dynamic binary_name - 'path': resolve_relative_path('../.assets/binaries/' + binary_name) - } - } - } +from facefusion.types import MediaDirection, PeerConnection, RtcAudioTrack, RtcPeer, RtcVideoTrack, SdpAnswer, SdpOffer def pre_check() -> bool: @@ -53,17 +15,6 @@ def pre_check() -> bool: return conditional_download_sources(download_set.get('sources')) -@lru_cache -def create_static_rtc_library() -> Optional[ctypes.CDLL]: - binary_path = create_static_download_set().get('sources').get('datachannel').get('path') - - if binary_path: - rtc_library = ctypes.CDLL(binary_path) - return init_ctypes(rtc_library) - - return None - - def create_peer_connection( ice_servers : Optional[ctypes.Array[ctypes.c_char_p]] = None, ice_servers_count : int = 0, proxy_server : Optional[bytes] = None, @@ -78,8 +29,8 @@ def create_peer_connection( max_packet_size : int = 0, max_message_size : int = 0) -> PeerConnection: - rtc_library = create_static_rtc_library() - rtc_configuration = RTC_CONFIGURATION() + datachannel_library = create_static_datachannel_library() + rtc_configuration = create_rtc_configuration() rtc_configuration.iceServers = ice_servers rtc_configuration.iceServersCount = ice_servers_count @@ -96,7 +47,7 @@ def create_peer_connection( rtc_configuration.mtu = max_packet_size rtc_configuration.maxMessageSize = max_message_size - return rtc_library.rtcCreatePeerConnection(ctypes.byref(rtc_configuration)) + return datachannel_library.rtcCreatePeerConnection(ctypes.byref(rtc_configuration)) def build_media_description(media_type : str, payload_type : int, rtp_codec : str, media_direction : MediaDirection, media_id : int) -> bytes: @@ -112,64 +63,64 @@ def build_media_description(media_type : str, payload_type : int, rtp_codec : st def add_audio_track(peer_connection : PeerConnection, media_direction : MediaDirection) -> RtcAudioTrack: - rtc_library = create_static_rtc_library() + datachannel_library = create_static_datachannel_library() media_description = build_media_description('audio', 111, 'opus/48000/2', media_direction, 1) - audio_track = rtc_library.rtcAddTrack(peer_connection, media_description) + audio_track = datachannel_library.rtcAddTrack(peer_connection, media_description) - audio_packetizer = RTC_PACKETIZER_INIT() + audio_packetizer = create_rtc_packetizer_init() audio_packetizer.ssrc = 43 audio_packetizer.cname = b'audio' audio_packetizer.payloadType = 111 audio_packetizer.clockRate = 48000 - rtc_library.rtcSetOpusPacketizer(audio_track, ctypes.byref(audio_packetizer)) - rtc_library.rtcChainRtcpSrReporter(audio_track) + datachannel_library.rtcSetOpusPacketizer(audio_track, ctypes.byref(audio_packetizer)) + datachannel_library.rtcChainRtcpSrReporter(audio_track) return audio_track def add_video_track(peer_connection : PeerConnection, media_direction : MediaDirection) -> RtcVideoTrack: - rtc_library = create_static_rtc_library() + datachannel_library = create_static_datachannel_library() media_description = build_media_description('video', 96, 'VP8/90000', media_direction, 0) - video_track = rtc_library.rtcAddTrack(peer_connection, media_description) + video_track = datachannel_library.rtcAddTrack(peer_connection, media_description) - video_packetizer = RTC_PACKETIZER_INIT() + video_packetizer = create_rtc_packetizer_init() video_packetizer.ssrc = 42 video_packetizer.cname = b'video' video_packetizer.payloadType = 96 video_packetizer.clockRate = 90000 video_packetizer.maxFragmentSize = 1200 - rtc_library.rtcSetVP8Packetizer(video_track, ctypes.byref(video_packetizer)) - rtc_library.rtcChainRtcpSrReporter(video_track) - rtc_library.rtcChainRtcpNackResponder(video_track, 512) + datachannel_library.rtcSetVP8Packetizer(video_track, ctypes.byref(video_packetizer)) + datachannel_library.rtcChainRtcpSrReporter(video_track) + datachannel_library.rtcChainRtcpNackResponder(video_track, 512) return video_track def create_sdp(peer_connection : PeerConnection) -> Optional[SdpOffer]: - rtc_library = create_static_rtc_library() - rtc_library.rtcSetLocalDescription(peer_connection, b'offer') + datachannel_library = create_static_datachannel_library() + datachannel_library.rtcSetLocalDescription(peer_connection, b'offer') buffer_size = 16384 buffer_string = ctypes.create_string_buffer(buffer_size) - if rtc_library.rtcGetLocalDescription(peer_connection, buffer_string, buffer_size) > 0: + if datachannel_library.rtcGetLocalDescription(peer_connection, buffer_string, buffer_size) > 0: return buffer_string.value.decode() return None def negotiate_sdp(peer_connection : PeerConnection, sdp_offer : SdpOffer) -> Optional[SdpAnswer]: - rtc_library = create_static_rtc_library() - rtc_library.rtcSetRemoteDescription(peer_connection, sdp_offer.encode(), b'offer') + datachannel_library = create_static_datachannel_library() + datachannel_library.rtcSetRemoteDescription(peer_connection, sdp_offer.encode(), b'offer') buffer_size = 16384 buffer_string = ctypes.create_string_buffer(buffer_size) wait_limit = time.monotonic() + 5 while time.monotonic() < wait_limit: - if rtc_library.rtcGetLocalDescription(peer_connection, buffer_string, buffer_size) > 0: + if datachannel_library.rtcGetLocalDescription(peer_connection, buffer_string, buffer_size) > 0: return buffer_string.value.decode() time.sleep(0.05) @@ -195,7 +146,7 @@ def handle_whep_offer(peers : List[RtcPeer], sdp_offer : SdpOffer) -> Optional[S def send_to_peers(peers : List[RtcPeer], data : bytes) -> None: - rtc_library = create_static_rtc_library() + datachannel_library = create_static_datachannel_library() if peers: timestamp = int(time.monotonic() * 90000) & 0xFFFFFFFF @@ -205,32 +156,32 @@ def send_to_peers(peers : List[RtcPeer], data : bytes) -> None: for rtc_peer in peers: video_track_id = rtc_peer.get('video_track') - if video_track_id and rtc_library.rtcIsOpen(video_track_id): - rtc_library.rtcSetTrackRtpTimestamp(video_track_id, timestamp) - rtc_library.rtcSendMessage(video_track_id, data_buffer, data_total) + if video_track_id and datachannel_library.rtcIsOpen(video_track_id): + datachannel_library.rtcSetTrackRtpTimestamp(video_track_id, timestamp) + datachannel_library.rtcSendMessage(video_track_id, data_buffer, data_total) return None def delete_peers(peers : List[RtcPeer]) -> None: - rtc_library = create_static_rtc_library() + datachannel_library = create_static_datachannel_library() for rtc_peer in peers: peer_connection_id = rtc_peer.get('peer_connection') if peer_connection_id: - rtc_library.rtcDeletePeerConnection(peer_connection_id) + datachannel_library.rtcDeletePeerConnection(peer_connection_id) peers.clear() def is_peer_connected(peers : List[RtcPeer]) -> bool: - rtc_library = create_static_rtc_library() + datachannel_library = create_static_datachannel_library() for rtc_peer in peers: video_track_id = rtc_peer.get('video_track') - if video_track_id and rtc_library.rtcIsOpen(video_track_id): + if video_track_id and datachannel_library.rtcIsOpen(video_track_id): return True return False diff --git a/facefusion/rtc_bindings.py b/facefusion/rtc_bindings.py deleted file mode 100644 index 81621e51..00000000 --- a/facefusion/rtc_bindings.py +++ /dev/null @@ -1,91 +0,0 @@ -import ctypes - -RTC_CONFIGURATION = type('RtcConfiguration', (ctypes.Structure,), -{ - '_fields_': - [ - ('iceServers', ctypes.POINTER(ctypes.c_char_p)), - ('iceServersCount', ctypes.c_int), - ('proxyServer', ctypes.c_char_p), - ('bindAddress', ctypes.c_char_p), - ('certificateType', ctypes.c_int), - ('iceTransportPolicy', ctypes.c_int), - ('enableIceTcp', ctypes.c_bool), - ('enableIceUdpMux', ctypes.c_bool), - ('disableAutoNegotiation', ctypes.c_bool), - ('forceMediaTransport', ctypes.c_bool), - ('portRangeBegin', ctypes.c_ushort), - ('portRangeEnd', ctypes.c_ushort), - ('mtu', ctypes.c_int), - ('maxMessageSize', ctypes.c_int) - ] -}) -RTC_PACKETIZER_INIT = type('RtcPacketizerInit', (ctypes.Structure,), -{ - '_fields_': - [ - ('ssrc', ctypes.c_uint32), - ('cname', ctypes.c_char_p), - ('payloadType', ctypes.c_uint8), - ('clockRate', ctypes.c_uint32), - ('sequenceNumber', ctypes.c_uint16), - ('timestamp', ctypes.c_uint32), - ('maxFragmentSize', ctypes.c_uint16), - ('nalSeparator', ctypes.c_int), - ('obuPacketization', ctypes.c_int), - ('playoutDelayId', ctypes.c_uint8), - ('playoutDelayMin', ctypes.c_uint16), - ('playoutDelayMax', ctypes.c_uint16) - ] -}) -LOG_CB_TYPE = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p) - - -def init_ctypes(rtc_library : ctypes.CDLL) -> ctypes.CDLL: - rtc_library.rtcInitLogger.argtypes = [ ctypes.c_int, LOG_CB_TYPE ] - rtc_library.rtcInitLogger.restype = None - rtc_library.rtcInitLogger(4, LOG_CB_TYPE(0)) - - rtc_library.rtcCreatePeerConnection.argtypes = [ ctypes.POINTER(RTC_CONFIGURATION) ] - rtc_library.rtcCreatePeerConnection.restype = ctypes.c_int - - rtc_library.rtcDeletePeerConnection.argtypes = [ ctypes.c_int ] - rtc_library.rtcDeletePeerConnection.restype = ctypes.c_int - - rtc_library.rtcSetLocalDescription.argtypes = [ ctypes.c_int, ctypes.c_char_p ] - rtc_library.rtcSetLocalDescription.restype = ctypes.c_int - - rtc_library.rtcSetRemoteDescription.argtypes = [ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p ] - rtc_library.rtcSetRemoteDescription.restype = ctypes.c_int - - rtc_library.rtcAddTrack.argtypes = [ ctypes.c_int, ctypes.c_char_p ] - rtc_library.rtcAddTrack.restype = ctypes.c_int - - rtc_library.rtcSendMessage.argtypes = [ ctypes.c_int, ctypes.c_void_p, ctypes.c_int ] - rtc_library.rtcSendMessage.restype = ctypes.c_int - - rtc_library.rtcSetVP8Packetizer.argtypes = [ ctypes.c_int, ctypes.POINTER(RTC_PACKETIZER_INIT) ] - rtc_library.rtcSetVP8Packetizer.restype = ctypes.c_int - - rtc_library.rtcChainRtcpSrReporter.argtypes = [ ctypes.c_int ] - rtc_library.rtcChainRtcpSrReporter.restype = ctypes.c_int - - rtc_library.rtcSetTrackRtpTimestamp.argtypes = [ ctypes.c_int, ctypes.c_uint32 ] - rtc_library.rtcSetTrackRtpTimestamp.restype = ctypes.c_int - - rtc_library.rtcIsOpen.argtypes = [ ctypes.c_int ] - rtc_library.rtcIsOpen.restype = ctypes.c_bool - - rtc_library.rtcChainRtcpNackResponder.argtypes = [ ctypes.c_int, ctypes.c_uint ] - rtc_library.rtcChainRtcpNackResponder.restype = ctypes.c_int - - rtc_library.rtcGetLocalDescription.argtypes = [ ctypes.c_int, ctypes.c_char_p, ctypes.c_int ] - rtc_library.rtcGetLocalDescription.restype = ctypes.c_int - - rtc_library.rtcSetLocalDescription.argtypes = [ ctypes.c_int, ctypes.c_char_p ] - rtc_library.rtcSetLocalDescription.restype = ctypes.c_int - - rtc_library.rtcSetOpusPacketizer.argtypes = [ ctypes.c_int, ctypes.POINTER(RTC_PACKETIZER_INIT) ] - rtc_library.rtcSetOpusPacketizer.restype = ctypes.c_int - - return rtc_library diff --git a/tests/stream_helper.py b/tests/stream_helper.py index 6947bd99..a83faa3d 100644 --- a/tests/stream_helper.py +++ b/tests/stream_helper.py @@ -10,26 +10,26 @@ from facefusion.types import SdpOffer def create_sdp_offer() -> Optional[SdpOffer]: - rtc_library = rtc.create_static_rtc_library() + datachannel_library = rtc.create_static_datachannel_library() peer_connection = rtc.create_peer_connection(disable_auto_negotiation = True) - rtc_library.rtcAddTrack(peer_connection, rtc.build_media_description('video', 96, 'VP8/90000', 'recvonly', 0)) - rtc_library.rtcAddTrack(peer_connection, rtc.build_media_description('audio', 111, 'opus/48000/2', 'recvonly', 1)) - rtc_library.rtcSetLocalDescription(peer_connection, b'offer') + datachannel_library.rtcAddTrack(peer_connection, rtc.build_media_description('video', 96, 'VP8/90000', 'recvonly', 0)) + datachannel_library.rtcAddTrack(peer_connection, rtc.build_media_description('audio', 111, 'opus/48000/2', 'recvonly', 1)) + datachannel_library.rtcSetLocalDescription(peer_connection, b'offer') buffer_size = 16384 buffer_string = ctypes.create_string_buffer(buffer_size) wait_limit = time.monotonic() + 5 while time.monotonic() < wait_limit: - if rtc_library.rtcGetLocalDescription(peer_connection, buffer_string, buffer_size) > 0: + if datachannel_library.rtcGetLocalDescription(peer_connection, buffer_string, buffer_size) > 0: sdp = buffer_string.value.decode() - rtc_library.rtcDeletePeerConnection(peer_connection) + datachannel_library.rtcDeletePeerConnection(peer_connection) return sdp time.sleep(0.05) - rtc_library.rtcDeletePeerConnection(peer_connection) + datachannel_library.rtcDeletePeerConnection(peer_connection) return None diff --git a/tests/test_rtc.py b/tests/test_rtc.py index c0704795..167f4d30 100644 --- a/tests/test_rtc.py +++ b/tests/test_rtc.py @@ -15,14 +15,14 @@ def test_build_media_description() -> None: def test_create_peer_connection() -> None: peer_connection = rtc.create_peer_connection() - rtc_library = rtc.create_static_rtc_library() + datachannel_library = rtc.create_static_datachannel_library() assert peer_connection > 0 - assert rtc_library.rtcDeletePeerConnection(peer_connection) == 0 + assert datachannel_library.rtcDeletePeerConnection(peer_connection) == 0 def test_add_audio_track() -> None: - rtc_library = rtc.create_static_rtc_library() + datachannel_library = rtc.create_static_datachannel_library() sender_connection = rtc.create_peer_connection() sender_audio_track = rtc.add_audio_track(sender_connection, 'sendonly') @@ -40,12 +40,12 @@ def test_add_audio_track() -> None: assert 'opus/48000/2' in sdp_offer assert 'opus/48000/2' in sdp_answer - assert rtc_library.rtcDeletePeerConnection(sender_connection) == 0 - assert rtc_library.rtcDeletePeerConnection(receiver_connection) == 0 + assert datachannel_library.rtcDeletePeerConnection(sender_connection) == 0 + assert datachannel_library.rtcDeletePeerConnection(receiver_connection) == 0 def test_add_video_track() -> None: - rtc_library = rtc.create_static_rtc_library() + datachannel_library = rtc.create_static_datachannel_library() sender_connection = rtc.create_peer_connection() sender_video_track = rtc.add_video_track(sender_connection, 'sendonly') @@ -63,5 +63,5 @@ def test_add_video_track() -> None: assert 'VP8/90000' in sdp_offer assert 'VP8/90000' in sdp_answer - assert rtc_library.rtcDeletePeerConnection(sender_connection) == 0 - assert rtc_library.rtcDeletePeerConnection(receiver_connection) == 0 + assert datachannel_library.rtcDeletePeerConnection(sender_connection) == 0 + assert datachannel_library.rtcDeletePeerConnection(receiver_connection) == 0