---
lang: ja
path: user-guide/linux-sdk/quickstart
labels: ユーザーガイド/Linux SDK/クイックスタート
metaTitle: クイックスタート ｜ Linux SDK ｜ ユーザーガイド ｜ SkyWay（スカイウェイ）
---

# 🚀 クイックスタート

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

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

- 基本的な 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やPulseAudioなど、デバイスに関する操作に制限がある場合があります。
> また、Ubuntu24.04/aarch64ではv4l2loopbackが動作しない可能性があります。
>
> v4l2loopbackが利用できない環境においては映像受信を行わないようにしていただくか、[OpenCVを用いた実装](/ja/docs/cookbook/linux-sdk/opencv/)を利用する必要があります。
>
> PulseAudioが利用できない環境においては[PulseAudioを無効化する](/ja/docs/cookbook/linux-sdk/disable-pulseaudio)必要があります。
> その場合も、[任意の音声の入力](/ja/docs/cookbook/linux-sdk/pcm-audio-input/)や、[音声フレームの取得](/ja/docs/cookbook/linux-sdk/pcm-audio-output/)が可能です。

## アプリケーションの概要

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

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

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

```shell
./app.out <room_name> [renderer_device_name]
```

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

- 第1引数で指定した名前のRoomに入室する (このクイックスタートでは P2PRoom を利用します)
- 他のメンバーに対して、起動後に選択したカメラの映像をPublishする
- 他のメンバーに対して、ソースコード内で指定したマイクの音声をPublishする
- 他のメンバーに対して、一定間隔で文字列データをPublishする
- 他のメンバーがPublishしている映像を、第2引数で指定した仮想デバイスに出力する
- 他のメンバーがPublishしている音声を、ソースコード内で指定したデバイスで再生する
- 他のメンバーがPublishしているデータを、コンソールログに出力する

## ディレクトリの準備とLinux SDKのダウンロード

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

```shell
skyway-quickstart/
├── examples/
│   ├── CMakeLists.txt
│   ├── quickstart/
│   │   ├── CMakeLists.txt
│   │   ├── main.cpp
│   │   ├── example_room.cpp
│   │   └── example_room.hpp
│   └── libs/ (Linux SDKのバイナリ)
└── include/ (Linux SDKのヘッダファイル群)
```

このセクションでは、libsディレクトリとincludeディレクトリに、Linux SDKのバイナリとヘッダーファイル群を配置します。
`main.cpp`, `example_room.cpp`, `example_room.hpp` と、2つの `CMakeLists.txt` はクイックスタート内で実装します。

LinuxSDKのバイナリは[GitHubのリリース](https://github.com/skyway/linux-sdk/releases/)からダウンロードし、libsディレクトリに配置してください。
アプリを動作させる端末のアーキテクチャに合わせ、 `skyway-libs-amd64.zip` か `skyway-libs-aarch64.zip` をご利用ください。

LinuxSDKのヘッダーファイル群は下記のコマンド等を利用し、LinuxSDKの[GitHubリポジトリ](https://github.com/skyway/linux-sdk)からincludeフォルダをダウンロードし、includeディレクトリとして配置してください。

```shell
cd skyway-quickstart
curl -L https://github.com/skyway/linux-sdk/archive/refs/heads/main.tar.gz \
  | tar -xzf - --strip-components=1 'linux-sdk-main/include'
```


## 環境構築

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

> Ubuntu 20.04をご利用の場合は `libstdc++-12-dev` のインストールは不要です。

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

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

```shell
pulseaudio --start
```

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

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

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

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

```shell
sudo modprobe v4l2loopback video_nr=42,43 exclusive_caps=1,1
```

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

```shell
v4l2-ctl --list-devices
```


## CMakeLists.txtの作成

まずは、 `examples/CMakeLists.txt` を以下の内容で作成します。

```cmake:CMakeLists.txt
cmake_minimum_required(VERSION 3.10.2)

project("examples")

# External libraries
add_library( skyway-linux-core STATIC IMPORTED )
set_target_properties( skyway-linux-core
        PROPERTIES
        IMPORTED_LOCATION
        ${CMAKE_CURRENT_SOURCE_DIR}/libs/libskyway-linux-core.a)

add_library( skyway-linux-room STATIC IMPORTED )
set_target_properties( skyway-linux-room
        PROPERTIES
        IMPORTED_LOCATION
        ${CMAKE_CURRENT_SOURCE_DIR}/libs/libskyway-linux-room.a)

# List of definitions
set( SKYWAY_LINUX_DEFINITIONS
        WEBRTC_LINUX=1
        WEBRTC_POSIX=1)

# List of libs
set( SKYWAY_LINUX_LIBS
        skyway-linux-room
        skyway-linux-core
        pthread
        atomic
        avcodec
        X11
        glib-2.0
        gio-2.0
        gobject-2.0
        dl)

# List of includes
set( SKYWAY_LINUX_INCLUDES
        ${CMAKE_CURRENT_SOURCE_DIR}/../include/external/libmediasoupclient/include
        ${CMAKE_CURRENT_SOURCE_DIR}/../include/external/libsdptransform
        ${CMAKE_CURRENT_SOURCE_DIR}/../include/external/libwebrtc
        ${CMAKE_CURRENT_SOURCE_DIR}/../include/external/libskyway/include
        ${CMAKE_CURRENT_SOURCE_DIR}/../include/external/libwebrtc/third_party/abseil-cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/../include/external/libwebrtc/third_party/boringssl/src/include
        ${CMAKE_CURRENT_SOURCE_DIR}/../include/external/boost
        ${CMAKE_CURRENT_SOURCE_DIR}/../include)

add_subdirectory("quickstart")
```

次に、 `examples/quickstart/CMakeLists.txt` を以下の内容で作成します。

```cmake:quickstart/CMakeLists.txt
cmake_minimum_required(VERSION 3.10.2)

project("quickstart")

enable_language(CXX)
set(CMAKE_CXX_COMPILER "/usr/bin/clang++" CACHE STRING "clang++ compile" FORCE)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(app.out main.cpp example_room.cpp)
target_compile_definitions(app.out PRIVATE ${SKYWAY_LINUX_DEFINITIONS})
target_include_directories(app.out PRIVATE ${SKYWAY_LINUX_INCLUDES})
target_link_libraries(app.out ${SKYWAY_LINUX_LIBS})
```

## main.cppの作成

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

```cpp: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のAppIdとSecretKeyを環境変数から取得します。
    const char* app_id     = getenv("SKYWAY_APP_ID");
    const char* secret_key = getenv("SKYWAY_SECRET_KEY");
    if (!app_id || !secret_key) {
        std::cerr << "Please set SKYWAY_APP_ID and SKYWAY_SECRET_KEY environment variables."
                  << std::endl;
        return -1;
    }

    std::string room_name           = argv[1];
    std::string video_output_device = "";
    if (argc == 3) {
        video_output_device = argv[2];
    }

    auto example_room = ExampleRoom(video_output_device);

    // [1] SkyWayの利用を開始します。

    // [2] Roomに入室します。

    // [3] Roomの他のメンバーがPublishしている映像や音声をSubscribeします。

    // [4] 映像や音声をPublishします。

    // キー入力を待ちます。
    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 を作成します。

```cpp: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);

    // SkyWayの利用を開始します。
    bool Setup(const std::string& app_id, const std::string& secret_key);

    // P2PRoomを検索/作成し、入室します。
    bool JoinRoom(const std::string& room_name);

    // Video/Audio/DataをPublishします。
    void Publish();

    // 指定のPublicationをSubscribeします。
    bool Subscribe(std::shared_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::shared_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:
    // 指定のPublicationをSubscribeしているかチェックします。
    bool IsSubscribed(std::shared_ptr<skyway::room::interface::RoomPublication> publication);

    std::shared_ptr<skyway::room::p2p::P2PRoom> p2proom_;
    std::shared_ptr<skyway::room::p2p::LocalP2PRoomMember> room_member_;
    std::unique_ptr<skyway::media::V4l2VideoRenderer> renderer_;
    std::shared_ptr<skyway::core::stream::local::LocalDataStream> data_stream_;
    std::string renderer_device_name_;
    std::vector<std::unique_ptr<std::thread>> threads_;
    std::atomic<bool> is_leaving_;
};
```

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

```cpp:example_room.cpp
// example_room.cpp

#include "example_room.hpp"
#include <iostream>

ExampleRoom::ExampleRoom(const std::string& renderer_device_name)
    : 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) {}
```

## SkyWay の開始処理

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

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

```cpp:main.cpp
// main.cpp
    // Roomのセットアップ処理を行います。
    if (!example_room.Setup(app_id, secret_key)) {
        return -1;
    }
```

次に、example_room.cpp に `Setup()` メソッドを実装します。

SkyWayを利用するためには、SkyWay Auth Token を用いて `SkyWayContext` をセットアップする必要があります。
SkyWayAuthTokenは本来サーバサイドで生成するため、LinuxSDKにはトークン生成機能はありません。

このクイックスタートでは、開発環境専用のAPIである `skyway::Context::SetupForDev` を用いて初期化するため、SkyWay Auth Tokenの作成は省略します。
認証認可について、詳しくは[こちら](/ja/docs/user-guide/authentication/)をご覧ください。

> skyway::Context::SetupForDev は開発環境での利用が想定されているAPIです。 本番環境では、シークレットキーを秘匿するためskyway::Context::Setupをご利用ください。

```cpp:example_room.cpp
// example_room.cpp
// SkyWayの利用を開始します。
bool ExampleRoom::Setup(const std::string& app_id, const std::string& secret_key) {
    skyway::Context::SkyWayOptions context_options{};

    // SkyWayのログレベルの設定が行えます。
    context_options.log_level = skyway::global::interface::Logger::kWarn;

    // SkyWayのAppIdとSecretKeyを使用して、SkyWayContextをセットアップします。
    // 本番環境ではContext::Setupを使用してください。
    if (!skyway::Context::SetupForDev(app_id, secret_key, nullptr, context_options)) {
        std::cerr << "- [Error] setup failed." << std::endl;
        return false;
    }
    return true;
}
```

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

ログレベルは警告以上の情報を出力するkWarning、デバッグ情報も出力するkDebugなどがあります。詳細は[Linux SDK APIリファレンス](https://linux-sdk.api-reference.skyway.ntt.com/classskyway_1_1global_1_1interface_1_1_logger.html)にて確認できます。


## Room の作成 / Roomへの入室

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

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

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

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

```cpp:example_room.cpp
// example_room.cpp

// P2PRoomを検索/作成し、入室します。
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().value() << 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にメンバーを入室させます。
    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] の部分に以下を追加します。

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

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

```cpp:example_room.cpp
// 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

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

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

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

```cpp:main.cpp
// main.cpp
    // Roomのメンバーに対してVideo/Audio/DataをPublishします。
    example_room.Publish();
```

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

```cpp:example_room.cpp
//example_room.cpp
// Video/Audio/DataをPublishします。
void ExampleRoom::Publish() {
    // ビデオデバイスを列挙し、任意のデバイスをPublishします。

    // デフォルトで設定されている音声入力デバイスをPublishします。

    // DataStreamをPublishします。
}
```

### VideoのPublish

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

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

```cpp:example_room.cpp
    // ビデオデバイスを列挙し、任意のデバイスを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_options {};
            auto publication = room_member_->Publish(video_stream, publication_options);
            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のPublish

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

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

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

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

```cpp:example_room.cpp
    // デフォルトで設定されている音声入力デバイスをPublishします。
    auto audio_devices = skyway::media::DeviceManager::GetRecordDevices();
    if (audio_devices.size() > 0) {
        auto device = audio_devices[0];
        skyway::media::DeviceManager::SetRecordingDevice(device);
        auto audio_stream = skyway::media::StreamFactory::CreateAudioStream();
        skyway::room::interface::LocalRoomMember::PublicationOptions publication_options {};
        auto publication = room_member_->Publish(audio_stream, publication_options);
        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のPublish

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

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

```cpp:example_room.cpp
    // 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);
            std::cout << "- 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_options {};
    auto publication = room_member_->Publish(data_stream_, publication_options);
    if (publication) {
        std::cout << "- DataStream Published" << std::endl;
        std::cout << "  - Publication Id: " << publication->Id() << std::endl;
    }
```

## Video/Audio/DataのSubscribeと出力

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

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

```cpp:main.cpp
// main.cpp
    // 同じRoomでPublishされているVideo/Audio/Data全てSubscribeします。
    example_room.SubscribeAll();
```

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

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

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

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

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

```cpp:example_room.cpp
// example_room.cpp
// 指定のPublicationをSubscribeします。
bool ExampleRoom::Subscribe(std::shared_ptr<skyway::room::interface::RoomPublication> publication) {
    if (room_member_->Id() == publication->Publisher()->Id()) {
        // 自身がPublishしたPublicationはSubscribeできないので無視します。
        return false;
    }
    if (this->IsSubscribed(publication)) {
        // 既にSubscribeしている場合は無視します。
        return false;
    }

    // PublicationのContentTypeに応じてメディアを出力します。
    skyway::room::interface::LocalRoomMember::SubscriptionOptions subscription_options {};
    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;
}
```

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

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

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

```cpp:example_room.cpp
// example_room.cpp
// 指定のPublicationをSubscribeしているかチェックします。
bool ExampleRoom::IsSubscribed(
    std::shared_ptr<skyway::room::interface::RoomPublication> publication) {
    auto subscriptions = publication->Subscriptions();
    auto find =
        std::find_if(subscriptions.begin(),
                     subscriptions.end(),
                     [&](std::shared_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` )が入っています。

```cpp:example_room.cpp
// 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_options);
        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]` を出力デバイスとしています。

```cpp:example_room.cpp
// 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_options);
            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は後述するイベントリスナーを使って出力します。

```cpp:example_room.cpp
// example_room.cpp
        // Data を出力します。
        auto subscription = room_member_->Subscribe(publication->Id(), subscription_options);
        if (!subscription) {
            return false;
        }
        auto data_stream =
            std::dynamic_pointer_cast<skyway::core::stream::remote::RemoteDataStream>(
                subscription->Stream());
        // [7]
        // 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のイベントリスナーと、[7]で登録していたDataStreamのイベントリスナーを実装します。

このクイックスタートでは、Example::Room クラスがイベントリスナーのクラスを継承し、必要なメソッドをoverrideすることで、各イベント発生時の動作を定義します。

### OnStreamPublished()

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

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

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

```cpp:example_room.cpp
// example_room.cpp
// Impl skyway::room::interface::Room::EventListener
void ExampleRoom::OnStreamPublished(
    std::shared_ptr<skyway::room::interface::RoomPublication> publication) {
    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()` はそれぞれ文字列とバイナリを送信されたときに発火します。

```cpp:example_room.cpp
// example_room.cpp
// Impl skyway::core::stream::remote::RemoteDataStream::Listener
void ExampleRoom::OnData(const std::string& data) {
    std::cout << "- [Event] DataStream Message Received: " << data << std::endl;
}

void ExampleRoom::OnDataBuffer(const uint8_t* data, size_t length) {
    std::cout << "- [Event] DataStream BinaryData Received: Length=" << length << std::endl;
};
```

## ビルド

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

```shell
cd examples
cmake -B build
cd build
make -j
```

examples/build/quickstart/app.out が生成された実行ファイルです。

## アプリケーションの実行

アプリケーションの実行には、SkyWayへの登録とアプリケーションID/シークレットキーの払い出しが必要です。
SkyWayのログイン、登録は[SkyWay コンソール](https://console.skyway.ntt.com/login/)から行えます。

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

```shell
export SKYWAY_APP_ID="<アプリケーションID>"
export SKYWAY_SECRET_KEY="<シークレットキー>"
./app.out test_room /dev/video42
```

```shell
export SKYWAY_APP_ID="<アプリケーションID>"
export SKYWAY_SECRET_KEY="<シークレットキー>"
./app.out test_room /dev/video43
```

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

その後、使用するビデオデバイスのIndex番号を入力してPublishします。
他のAudio、Dataは自動でPublishされます。
```shell
- 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をインストール後、下記のコマンドを実行してください。

```shell
ffplay /dev/video42
```

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

- 削除： `sudo modprobe -r v4l2loopback`
- 作成： `sudo modprobe v4l2loopback video_nr=42,43 exclusive_caps=1,1`

完成したアプリケーションのソースコードは[こちらから確認できます。](https://github.com/skyway/linux-sdk/tree/main/examples/)

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