diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index c5bac62d..56ad11e3 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -24,6 +24,12 @@ jobs: - run: | sudo apt-get update sudo apt-get -y install libva-dev libdrm-dev + - name: Download openh264 + run: | + curl -LO http://ciscobinary.openh264.org/libopenh264-2.4.1-linux64.7.so.bz2 + bzip2 -d libopenh264-2.4.1-linux64.7.so.bz2 + mv libopenh264-2.4.1-linux64.7.so libopenh264.so + working-directory: ./test-e2e - run: rye pin ${{ matrix.python_version }} - run: rye sync - run: rye run python run.py diff --git a/.gitignore b/.gitignore index 74a8db26..f07e06e7 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,5 @@ src/sora_sdk/model_coeffs/* /wheelhouse # E2E テスト用 -.env \ No newline at end of file +.env +libopenh264.so \ No newline at end of file diff --git a/requirements-dev.lock b/requirements-dev.lock index e2e131b1..5a107b60 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -28,5 +28,5 @@ pyproject-hooks==1.0.0 pytest==8.0.0 python-dotenv==1.0.1 ruff==0.1.14 -setuptools==69.0.3 wheel==0.42.0 +setuptools==69.0.3 diff --git a/tests/README.md b/tests/README.md index d1740e36..ba911dde 100644 --- a/tests/README.md +++ b/tests/README.md @@ -6,8 +6,9 @@ ```bash TEST_SIGNALING_URL=wss://sora.example.com/signaling -TEST_CHANNEL_ID_PREFIX=sora_ +TEST_CHANNEL_ID_PREFIX=sora TEST_SECRET_KEY=secret +TEST_OPENH264_PATH=/usr/local/lib/libopenh264.so ``` ## 実行方法 diff --git a/tests/conftest.py b/tests/conftest.py index 2fb22c0e..64b62d1c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,4 +13,5 @@ def setup(): "channel_id_prefix": os.environ.get("TEST_CHANNEL_ID_PREFIX"), "secret": os.environ.get("TEST_SECRET_KEY"), "metadata": {"access_token": os.environ.get("TEST_SECRET_KEY")}, + "openh264_path": os.environ.get("TEST_OPENH264_PATH"), } diff --git a/tests/test_openh264.py b/tests/test_openh264.py new file mode 100644 index 00000000..9977b11e --- /dev/null +++ b/tests/test_openh264.py @@ -0,0 +1,111 @@ +import json +import threading +import time +from threading import Event + +import numpy as np +from sora_sdk import Sora, SoraConnection, SoraVideoSource + + +class Sendonly: + _sora: Sora = None + _connection: SoraConnection + + _connection_id: str + + # 接続した + _connected: Event = Event() + # 終了 + _closed: bool = False + + _video_height: int = 480 + _video_width: int = 640 + _video_input_thread: threading.Thread + _video_source: SoraVideoSource + + def __init__(self, openh264_path: str, signaling_urls: list, channel_id: str, metadata: dict): + print(channel_id) + self._sora = Sora(openh264=openh264_path) + self._connected = Event() + + self._video_source = self._sora.create_video_source() + + self._connection = self._sora.create_connection( + signaling_urls=signaling_urls, + role="sendonly", + channel_id=channel_id, + metadata=metadata, + audio=False, + video=True, + video_codec_type="H264", + video_source=self._video_source, + ) + + self._connection.on_set_offer = self._on_set_offer + self._connection.on_notify = self._on_notify + self._connection.on_disconnect = self._on_disconnect + + def connect(self): + self._connection.connect() + + self._video_input_thread = threading.Thread(target=self._video_input_loop, daemon=True) + self._video_input_thread.start() + + # _connected が set されるまで 30 秒待つ + assert self._connected.wait(30) + + return self + + def _video_input_loop(self): + while not self._closed: + time.sleep(1.0 / 30) + self._video_source.on_captured( + np.zeros((self._video_height, self._video_width, 3), dtype=np.uint8) + ) + + def _on_set_offer(self, raw_offer): + offer = json.loads(raw_offer) + if offer["type"] == "offer": + self.connection_id = offer["connection_id"] + print(f"Offer を受信しました: connection_id={self.connection_id}") + + def _on_notify(self, raw_message): + message = json.loads(raw_message) + if ( + message["type"] == "notify" + and message["event_type"] == "connection.created" + and message["connection_id"] == self.connection_id + ): + print(f"Sora に接続しました: connection_id={self.connection_id}") + self._connected.set() + + def _on_disconnect(self, error_code, message): + print(f"Sora から切断しました: error_code='{error_code}' message='{message}'") + self._closed = True + self._connected.clear() + + def disconnect(self): + self._connection.disconnect() + # タイムアウト指定 + self._video_input_thread.join(timeout=10) + + +def test_sendonly(setup): + signaling_urls = setup.get("signaling_urls") + channel_id_prefix = setup.get("channel_id_prefix") + metadata = setup.get("metadata") + openh264_path = setup.get("openh264_path") + + channel_id = f"{channel_id_prefix}_{__name__}" + + sendonly = Sendonly( + openh264_path, + signaling_urls, + channel_id, + metadata, + ) + sendonly.connect() + + time.sleep(5) + + sendonly.disconnect()