🚀 クイックスタート

このクイックスタートでは SkyWay SDK for Linux®︎ (以下、SDK for Linux ) を利用したシンプルなアプリケーションを構築します。

この記事は以下の知識があることを前提としています。

  • 基本的な Linux コマンド
  • 基本的な C++ の構文
  • CMake の使い方

想定環境:

  • OS : Ubuntu 22.04 LTS
  • audio: PulseAudio
  • video: Video for Linux(V4L)
  • cmake: minimum version 3.10.2
  • clang: 14.0.0

また、アプリケーションの動作確認にマイク・カメラが必要です。

注意 Dockerなどのコンテナ環境では、v4l2loopbackなど、デバイスに関する操作に制限がある場合があります。

アプリケーションの概要

このクイックスタートではCLI操作のシンプルな通話アプリケーションを作成します。

作成するアプリケーションの実行例を以下に示します。

第1引数にルーム名、第2引数に受信した映像の出力先となるデバイスを指定します。

$ ./app.out test-room /dev/video42

このアプリケーションでは以下の機能を実装します。

  • test-room という名前のRoomに入室する
  • 他のメンバーに対して、起動後に選択したカメラの映像を送信する
  • 他のメンバーに対して、ソースコード内で指定したマイクの音声を送信する
  • 他のメンバーが送信している映像を /dev/video42 に設定されている仮想デバイスに出力する
  • 他のメンバーが送信している音声をソースコード内で指定したデバイスで再生する

/dev/video42 にはv4l2loopbackを用いて作成した仮想デバイスがあることを想定しています。仮想デバイスの設定方法については"環境構築"の項目で説明いたします。

アプリケーションのディレクトリ構成を以下に示します。

examples/ ├── CMakeLists.txt ├── quickstart/ │ ├── CMakeLists.txt │ ├── main.cpp │ ├── example_room.cpp │ └── example_room.hpp └── libs/ (ダウンロード・展開する) ├── ... ...

このクイックスタートでは main.cpp, example_room.cpp, example_room.hpp を実装します。

2つあるCMakeListsはそれぞれGitHubからダウンロードしてください。

libsディレクトリはGitHubのリリースからダウンロード、展開してください。 アプリを動作させる端末のアーキテクチャに合わせ、 skyway-libs-amd64.zipskyway-libs-aarch64.zip をご利用ください。

環境構築

依存パッケージをaptでインストールします。

apt update apt install \ clang \ cmake \ libstdc++-12-dev \ libavcodec-dev \ libglib2.0-dev \ libx11-dev \ make \ pulseaudio \ v4l2loopback-dkms \ v4l-utils

SDK for Linux の利用に際して、PulseAudioの起動が必要です。 PulseAudioは以下のコマンドで起動することができます。

pulseaudio --start

受信した映像の出力先として、v4l2loopbackで仮想デバイスを作成します。

確認時はアプリケーションで相互に映像を送信するため、2つ用意します。

オプションの exclusive_caps=1 は必須です。video_nr=42,43 は任意の空いている番号を利用してください。

以下のコマンドで仮想デバイスを作成します。

modprobe v4l2loopback video_nr=42,43 exclusive_caps=1,1

ビデオデバイスは以下のコマンドで確認できます。

v4l2-ctl --list-devices

トークンの準備

アプリケーションを実装する前に、動作に必要な認証用トークンを取得します。

トークンの生成にはSkyWayへのログインが必要です。

SkyWayのログイン、登録はこちらのページから行えます。

認証用トークンの発行についてはこちらをご覧ください。

本クイックスタートではトークンが環境変数 SKYWAY_AUTH_TOKEN に設定されていると想定します。

以下のコマンドで環境変数にトークンを設定します。 <token> は発行したトークンで置き換えてください。

export SKYWAY_AUTH_TOKEN=<token>

main.cppの作成

アプリケーションの概要で示した main.cpp を実装します。

実装するアプリケーションは1個または2個の引数をとり、指定したRoomに入室して映像・音声の送受信を行います。

以下のコマンドで実行することを想定しています。

app.out <room_name> [renderer_device_name]

room_name は入室するRoomの名前を指定します。

renderer_device_name は任意の引数で、映像出力デバイスの絶対パスを指定します。映像出力デバイスを指定された場合のみ受信した映像をメディア再生ツールなどから確認できます。 renderer_device_name で指定されるデバイスは v4l2loopback を用いて作成された仮想デバイスであることを前提としています。

ℹ️ Room に関してはこちらをご覧ください。 本クイックスタートでは P2PRoom を利用します。

まずは main.cpp を以下の内容で作成します。

// main.cpp #include <iostream> // 各機能はexample_room.cpp に実装します #include "example_room.hpp" int main(int argc, char* argv[]) { if (argc < 2 || argc > 3) { std::cerr << "app.out <room_name> [renderer_device_name]" << std::endl; return -1; } // SkyWayのAuthTokenを環境変数から取得します。 const char* token = getenv("SKYWAY_AUTH_TOKEN"); if (!token) { std::cerr << "Please set SKYWAY_AUTH_TOKEN environment variable." << std::endl; return -1; } // 引数を変数に格納します。 std::string room_name = argv[1]; std::string video_output_device = ""; if (argc == 3) { video_output_device = argv[2]; } // 第二引数はイベント関連のログのON/OFFです。 // 文字列やバイナリを送受信したい場合はtrueにしてください。 auto example_room = ExampleRoom(video_output_device, true); // [1] SkyWayの利用を開始します。 // [2] Roomに入室します。 // [3] Roomの他のメンバーが送信している映像や音声を受信します。 // [4] 映像や音声を送信します。 // キー入力を待ちます。 std::cout << "- Press ENTER key to close..." << std::endl; std::cin.ignore(); std::cin.get(); // [5] Roomから退室しSkyWayの利用を終了します。 return 0; }

example_room.cpp(hpp) の作成

main.cpp で利用した ExampleRoom クラスを実装します。

以下のような example_room.hpp を作成します。

// example_room.hpp #include <skyway/context.hpp> #include <skyway/core/stream/remote/data_stream.hpp> #include <skyway/media/device_manager.hpp> #include <skyway/media/stream_factory.hpp> #include <skyway/media/v4l2_video_renderer.hpp> #include <skyway/room/p2p/p2p_room.hpp> // Roomの操作を行うクラスです。 class ExampleRoom : public skyway::room::interface::Room::EventListener, public skyway::core::stream::remote::RemoteDataStream::Listener { public: ExampleRoom(const std::string& renderer_device_name, bool is_notify); // SkyWayの利用を開始します。 bool Setup(const std::string& token); // P2PRoomを検索/作成し、Joinします。 bool JoinRoom(const std::string& room_name); // Video/Audio/DataをPublishします。 void Publish(); // 指定のpublicationをsubscribeします。 bool Subscribe(std::unique_ptr<skyway::room::interface::RoomPublication> publication); // P2PRoomに存在するPublication全てに対してSubscribeを試みます。 void SubscribeAll(); // P2PRoomから退出します。 bool LeaveRoom(); // SkyWayの利用を終了します。 void Dispose(); // Impl skyway::room::interface::Room::EventListener void OnStreamPublished( std::unique_ptr<skyway::room::interface::RoomPublication> publication) override; // Impl skyway::core::stream::remote::RemoteDataStream::Listener void OnData(const std::string& data) override; void OnDataBuffer(const uint8_t* data, size_t length) override; private: std::shared_ptr<skyway::room::p2p::P2PRoom> p2proom_; std::unique_ptr<skyway::room::p2p::LocalP2PRoomMember> room_member_; std::vector<std::unique_ptr<std::thread>> threads_; // 別のスレッドで利用するためのStreamを表すメンバ std::shared_ptr<skyway::core::stream::local::LocalDataStream> data_stream_; // Roomからの退室処理中であることを表すbool bool is_leaving_; // logを出力するかどうかを表すbool // true ならイベント発火時にログを出力する bool is_notify_; std::unique_ptr<skyway::media::V4l2VideoRenderer> renderer_; std::string renderer_device_name_; // 指定のPublicationをSubscribeしているかチェックします。 bool IsSubscribed(skyway::room::interface::RoomPublication* publication); };

以下の内容で example_room.cpp を作成します。各メソッドは後の項目で順に実装します。

// example_room.cpp #include "example_room.hpp" #include <iostream> ExampleRoom::ExampleRoom(const std::string& renderer_device_name, bool is_notify) : p2proom_(nullptr), room_member_(nullptr), renderer_(nullptr), data_stream_(nullptr), renderer_device_name_(renderer_device_name), threads_(std::vector<std::unique_ptr<std::thread>>()), is_leaving_(false), is_notify_(is_notify) {}

SkyWay の開始処理

Setup() はSkyWayを利用するための準備を行うメソッドです。

ℹ️ SkyWayの利用を開始する際は必ずSetup()を実行する必要があります。

main.cpp の[1]の部分に以下を追加します。

// main.cpp // token を使ってRoomのセットアップ処理を行います。 if (!example_room.Setup(token)) { return -1; }

example_room.cpp に以下を追記します。

context_options.log_level を変更することでログレベルを設定できます。

ログレベルは警告以上の情報を出力するkWarning、デバッグ情報も出力するkDebugなどがあります。詳細はSDK for Linux APIリファレンスにて確認できます。

// example_room.cpp // SkyWayの利用を開始します。 bool ExampleRoom::Setup(const std::string& token) { skyway::Context::SkyWayOptions context_options{}; // SkyWayのログレベルの設定が行えます。 // context_options.log_level = skyway::global::interface::Logger::kInfo; if (!skyway::Context::Setup(token, nullptr, context_options)) { std::cerr << "- [Error] setup failed." << std::endl; return false; } return true; }

Room の作成 / Roomへの参加

JoinRoom() は指定したRoomに入るメソッドです。

main.cpp の[2]の部分に以下を追加します。

// main.cpp // room_name で指定したRoomに入ります。 if (!example_room.JoinRoom(room_name)) { return -1; }

example_room.cpp に以下のように追記します。

// example_room.cpp // P2PRoomを検索/作成し、Joinします。 bool ExampleRoom::JoinRoom(const std::string& room_name) { // P2PRoomを検索/作成します。 skyway::room::interface::RoomInitOptions room_init; room_init.name = room_name; p2proom_ = skyway::room::p2p::P2PRoom::FindOrCreate(room_init); if (!p2proom_) { std::cerr << "- [Error] room failed." << std::endl; return false; } // [6] // P2PRoomにイベントリスナー(skyway::room::interface::Room::EventListenerの実装)を登録します。 p2proom_->AddEventListener(this); // P2PRoomの情報を出力します。 std::cout << "# Room" << std::endl; if (p2proom_->Name()) { std::cout << "- Name: " << p2proom_->Name().get() << std::endl; } std::cout << "- Id: " << p2proom_->Id() << std::endl; // P2PRoomに既にいるメンバーの一覧を表示します。 std::cout << "- Room Members" << std::endl; for (auto& members : p2proom_->Members()) { std::cout << " - Id: " << members->Id() << std::endl; } // P2PRoomにメンバーをJoinさせます。 skyway::room::interface::RoomMemberInitOptions room_options; room_member_ = p2proom_->Join(room_options); if (!room_member_) { std::cerr << "- [Error] p2proom join failed." << std::endl; return false; } std::cout << "- LocalRoomMember Joined" << std::endl; std::cout << " - Id: " << room_member_->Id() << std::endl; return true; }

[6] ではイベントリスナーの追加をしています。詳細は後の項目で実装します。

Roomから退室 / SkyWayの終了処理

アプリケーションの終了時の処理を実装します。

main.cpp の [5] の部分に以下を追加します。

// main.cpp example_room.LeaveRoom(); example_room.Dispose();

以下のように example_room.cpp にLeaveRoom()Dispose() を実装します。

// example_room.cpp // P2PRoomから退出します。 bool ExampleRoom::LeaveRoom() { if (!room_member_) { return false; } is_leaving_ = true; // P2PRoomに紐づくイベントリスナーの登録を解除します。 if (p2proom_) { p2proom_->RemoveEventListener(this); } // 各threadの終了を待ちます。 for (auto& th : threads_) { if (th && th->joinable()) { th->join(); } } is_leaving_ = false; return room_member_->Leave(); } // SkyWayの利用を終了します。 void ExampleRoom::Dispose() { skyway::Context::Dispose(); }

threads_ は後の項目で実装する他のスレッドでの処理を管理するためのメンバです。 room_member_->Leave() の実行後は room_member_ の操作が行えなくなるため、Roomから退出する前にスレッドを終了しています。

Video/Audio/Dataの送信

Publish() メソッドを実装します。

このメソッドでは Video Stream, Audio Stream, Data Stream の3つの機能を使って映像/音声/データを送信します。

main.cpp の[4]の部分に以下を追記します。

// main.cpp // Roomのメンバーに対して映像,音声,メッセージを送信します。 example_room.Publish();

example_room.cppに以下のように追記します。ここに各Streamの送信を実装します。

//example_room.cpp // Video/Audio/DataをPublishします。 void ExampleRoom::Publish() { // // ビデオデバイスを列挙し、選択されたデバイスをpublishします。 // // // 音声入力デバイスをPublishします。 // // // DataStreamをPublishします。 // }

Videoの送信

ExampleRoom::Publish() はVideoStreamの送信を行います。

利用できるデバイスを列挙し、送信したいデバイスを選択できるように実装します。

// ビデオデバイスを列挙し、任意のデバイスをpublishします。 auto video_devices = skyway::media::DeviceManager::GetVideoDevices(); if (video_devices.size() > 0) { std::cout << "- VideoDevices" << std::endl; for (auto device : video_devices) { std::cout << " - Index: " << device.index << " Name: " << device.name << std::endl; } // デバイスのindex番号を入力します。 int device_index; std::cout << "- Enter the index of the video device to be published: "; std::cin >> device_index; if (device_index >= 0 && device_index < video_devices.size()) { auto video_stream = skyway::media::StreamFactory::CreateVideoStream(video_devices[device_index]); skyway::room::interface::LocalRoomMember::PublicationOptions publication_opttions {}; auto publication = room_member_->Publish(video_stream, publication_opttions); if (publication) { std::cout << " - VideoStream Published" << std::endl; std::cout << " - Publication Id: " << publication->Id() << std::endl; } } else { std::cout << " - Out of range" << std::endl; } }

Audioの送信

ExampleRoom::Publish() にAudioStreamの送信を実装します。

利用できる音声入力デバイスの一覧は skyway::media::DeviceManager::GetRecordDevices() で取得できます。

上記で取得できるデバイスのリストは[0]の要素にデフォルトのデバイスが格納され、[1]以降に各デバイスが列挙されています。

今回の実装ではデフォルトのデバイスである audio_devices[0] を指定しています。

// 音声入力デバイスをPublishします。 auto audio_devices = skyway::media::DeviceManager::GetRecordDevices(); if (audio_devices.size() > 0) { // index で入力デバイスを指定します。 auto device = audio_devices[0]; skyway::media::DeviceManager::SetRecordingDevice(device); auto audio_stream = skyway::media::StreamFactory::CreateAudioStream(); skyway::room::interface::LocalRoomMember::PublicationOptions publication_opttions {}; auto publication = room_member_->Publish(audio_stream, publication_opttions); if (publication) { std::cout << "- AudioStream Published" << std::endl; std::cout << " - Device Index: " << device.index << std::endl; std::cout << " - Device Name: " << device.name << std::endl; std::cout << " - Publication Id: " << publication->Id() << std::endl; } }

Dataの送信

ExampleRoom::Publish() にDataStreamの送信を実装します。

このアプリケーションでは文字列を一定間隔で送信します。

// DataStreamをPublishします。 data_stream_ = skyway::media::StreamFactory::CreateDataStream(); // 一定間隔でメッセージを送信するthreadを作成し、Threadsに追加します。 auto data_thread = std::make_unique<std::thread>([this] { auto count = 0; while (!is_leaving_) { auto data = "send msg: " + std::to_string(count); data_stream_->Write(data); if (is_notify_) { std::cout << "<!-- [Event] DataStream Message Send: " << data << " -->" << std::endl; } count++; std::this_thread::sleep_for(std::chrono::seconds(1)); } }); threads_.emplace_back(std::move(data_thread)); skyway::room::interface::LocalRoomMember::PublicationOptions publication_opttions {}; auto publication = room_member_->Publish(data_stream_, publication_opttions); if (publication) { std::cout << "- DataStream Published" << std::endl; std::cout << " - Publication Id: " << publication->Id() << std::endl; }

Video/Audio/Dataの受信, 出力

SubscribeAll() および Subscribe() メソッドを実装します。

main.cpp の[3]に以下を追記します。

// main.cpp // 同じRoomで送信されている映像,音声,メッセージを全て受信します。 example_room.SubscribeAll();

SubscribeAll()は以下のようにSubscribe()を利用してexample_room.cppに実装します。

// example_room.cpp // P2PRoomに存在するPublication全てに対してSubscribeを試みます。 void ExampleRoom::SubscribeAll() { for (auto& publication : p2proom_->Publications()) { this->Subscribe(std::move(publication)); } }

Subscribe()SubscribeAll()からコールされるメソッドで、特定のPublicationを購読します。

受け取った Publication がVideo/Audio/Dataの内、どのstreamを持つかは publication->ContentType() で判別します。

example_room.cpp に以下のように実装します。

// example_room.cpp // 指定のpublicationをsubscribeします。 bool ExampleRoom::Subscribe(std::unique_ptr<skyway::room::interface::RoomPublication> publication) { if (room_member_->Id() == publication->Publisher()->Id()) { // 自身がPublishしたPublicationはSubscribeできないので無視します。 return false; } if (this->IsSubscribed(publication.get())) { // 既にSubscribeしている場合は無視します。 return false; } // PublicationのContentTypeに応じてメディアを出力します。 skyway::room::interface::LocalRoomMember::SubscriptionOptions subscription_opttions {}; if (publication->ContentType() == skyway::model::ContentType::kVideo) { // // Video を出力します。 // } else if (publication->ContentType() == skyway::model::ContentType::kAudio) { // // Audio を出力します。 // } else if (publication->ContentType() == skyway::model::ContentType::kData) { // // Data を出力します。 // } return true; }

自身の PublicationSubscribe できません。 publication->Publisher()->Id() が自身のIdと等しい場合はSubscribeしないように実装しています。

Video/Audio/Data Stream の出力は後の項目で実装します。

Subscribe() の途中でコールしている IsSubscribed() メソッドを以下のように実装します。

// example_room.cpp // 指定のPublicationをSubscribeしているかチェックします。 bool ExampleRoom::IsSubscribed(skyway::room::interface::RoomPublication* publication) { auto subscriptions = publication->Subscriptions(); auto find = std::find_if(subscriptions.begin(), subscriptions.end(), [&](std::unique_ptr<skyway::room::interface::RoomSubscription>& subscription) { return subscription->Subscriber()->Id() == room_member_->Id(); }); return find != subscriptions.end(); }

Videoの出力

ExampleRoom::Subscribe() にVideoの出力を実装します。

Videoの出力先はアプリケーションの起動時にオプションの引数で受け取った renderer_device_name_ にあたります。

renderer_device_name_ にはアプリケーションの起動時に引数として受け取った出力先デバイスの絶対パス(例えば /dev/video42 )が入っています。

// example_room.cpp // Video を出力します。 if (renderer_device_name_ == "") { // 出力先を指定していない場合はSubscribeしません。 std::cout << "- VideoStream Subscribe Canceled" << std::endl; std::cout << " - Video device not specified" << std::endl; return false; } if (renderer_) { // 既に映像を出力している場合はSubscribeしません。 std::cout << "- VideoStream Subscribe Canceled" << std::endl; std::cout << " - Already set renderer" << std::endl; return false; } auto subscription = room_member_->Subscribe(publication->Id(), subscription_opttions); if (!subscription) { return false; } auto stream = std::dynamic_pointer_cast<skyway::core::stream::remote::RemoteVideoStream>( subscription->Stream()); // 映像を出力デバイスに書き込みます。 skyway::media::V4l2VideoRendererOptions monitor_opt; monitor_opt.scaled_width = 1920; monitor_opt.scaled_height = 1080; renderer_ = std::make_unique<skyway::media::V4l2VideoRenderer>(renderer_device_name_, monitor_opt); renderer_->Render(stream); std::cout << "- VideoStream Subscribed" << std::endl; std::cout << " - Device : " << renderer_device_name_ << std::endl; std::cout << " - Specified Width: " << monitor_opt.scaled_width << std::endl; std::cout << " - Specified Height: " << monitor_opt.scaled_height << std::endl; std::cout << " - Publication Id: " << publication->Id() << std::endl; std::cout << " - Subscription Id: " << subscription->Id() << std::endl;

Audioの出力

Audio を特定のデバイスから出力するように実装します。

利用できる音声出力デバイスの一覧は skyway::media::DeviceManager::GetPlayoutDevices() で取得できます。

音声入力デバイスと同様に、取得できるデバイスのリストは[0]の要素にデフォルトのデバイスが格納され、[1]以降に各デバイスが列挙されています。

下記実装ではデフォルトのデバイスである devices[0] を出力デバイスとしています。

// example_room.cpp // Audio を出力します。 auto devices = skyway::media::DeviceManager::GetPlayoutDevices(); if (devices.size() > 0) { auto device = devices[0]; skyway::media::DeviceManager::SetPlayoutDevice(device); // 音声はSubscribeと同時に再生されます。 auto subscription = room_member_->Subscribe(publication->Id(), subscription_opttions); if (!subscription) { return false; } std::cout << "- AudioStream Subscribed" << std::endl; std::cout << " - Name: " << device.name << std::endl; std::cout << " - Publication Id: " << publication->Id() << std::endl; std::cout << " - Subscription Id: " << subscription->Id() << std::endl; }

Dataの出力

Dataは後述するイベントリスナーを使って出力します。

// example_room.cpp // Data を出力します。 auto subscription = room_member_->Subscribe(publication->Id(), subscription_opttions); if (!subscription) { return false; } auto data_stream = std::dynamic_pointer_cast<skyway::core::stream::remote::RemoteDataStream>( subscription->Stream()); // P2PRoomにイベントリスナー(skyway::core::stream::remote::RemoteDataStream::Listenerの実装)を登録します。 data_stream->AddListener(this); std::cout << "- DataStream Subscribed" << std::endl; std::cout << " - Publication Id: " << publication->Id() << std::endl; std::cout << " - Subscription Id: " << subscription->Id() << std::endl;

イベントリスナー

example_room.cpp の[6]で登録していたRoomのイベントリスナーを実装します。

Example::Room クラスにイベントリスナーのクラスを継承させ、必要なメソッドをoverrideすることでイベントリスナーを設定します。

OnStreamPublished()

OnStreamPublished() はいずれかのMemberが新しく送信を開始したとき(publicationが作られたとき)に発火します。

このイベントリスナーを使って他のメンバーが映像や音声、データの送信を始めたときに自動的にSubscribeするよう実装します。

example_room.cpp に以下のように実装します。

// example_room.cpp // Impl skyway::room::interface::Room::EventListener void ExampleRoom::OnStreamPublished( std::unique_ptr<skyway::room::interface::RoomPublication> publication) { if (is_notify_) { std::cout << "<!-- [Event] StreamPublished: Id " << publication->Id() << "-->" << std::endl; } // イベントリスナーからSkyWayの操作を行う場合は別スレッドで実行する必要があります。 auto subscribe_thread = std::make_unique<std::thread>( [this, threads_publication = std::move(publication)]() mutable { this->Subscribe(std::move(threads_publication)); }); threads_.emplace_back(std::move(subscribe_thread)); }

注意 各イベントリスナー内では直接、SkyWayの操作や処理をブロックするような処理は行わないでください。 そのような処理を行う場合は他のスレッドへ処理を移譲してください。

OnData(), OnDataBuffer()

OnData(), OnDataBuffer() はそれぞれ文字列とバイナリを送信されたときに発火します。

// example_room.cpp // Impl skyway::core::stream::remote::RemoteDataStream::Listener void ExampleRoom::OnData(const std::string& data) { if (is_notify_) { std::cout << "<!-- [Event] DataStream Message Recieved: " << data << "-->" << std::endl; } } void ExampleRoom::OnDataBuffer(const uint8_t* data, size_t length) { if (is_notify_) { std::cout << "<!-- [Event] DataStream BinaryData Recieved: Length=" << length << "-->" << std::endl; } }

ビルドと実行

examplesディレクトリからcmake, makeを実行します。

cd examples cmake -B build cd build make -j

examples/build/quickstart/app.out が実行ファイルです。

シェルを2つ起動してそれぞれで以下のコマンドを実行してください。

./app.out test_room /dev/video42
./app.out test_room /dev/video43

アプリを実行すると指定したRoomにJoinします。

その後、送信に使用するVideoDeviceのIndex番号を入力してPublishします。 他のAudio、Dataは自動でPublishされます。

- VideoDevices - Index: 0 Name: DeviceA - Index: 1 Name: DeviceB - Index: 2 Name: DeviceC - Enter the index of the video device to be published: 1 <- DeviceBの映像をPublishする

Publish後、もう一方のアプリではVideo、Audio、Dataが自動でSubscribeされます。

Subscribe後、映像、音声、データの送受信ができていることを確認します。

受信した映像は指定した仮想デバイスに出力されます。 内容はffplayなどのメディア再生ツールで確認できます。

ffplayで/dev/video42の内容を確認する場合、ffplayをインストール後、下記のコマンドを実行してください。

ffplay /dev/video42

ℹ️ メディア再生ツールを使用した際に映像が映らない場合、仮想デバイスを作り直してください。

  • 削除: modprobe -r v4l2loopback
  • 作成: modprobe v4l2loopback video_nr=42,43 exclusive_caps=1,1

完成したアプリケーションのソースコードはこちらから確認できます。

商標

Linux®︎は、米国およびその他の国における Linus Torvalds の登録商標です。