2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

株式会社ACCESSAdvent Calendar 2022

Day 2

Bellagio OpenMAX IL で OpenMAX IL

Last updated at Posted at 2022-12-01

本記事について

  • OpenMAX IL についてざっくり説明します
  • オープンソースである Bellagio OpenMAX IL を利用し、OpenMAX IL のコンポーネントの一覧取得まで(だけ)コードを書いてみます
  • Ubuntu 22.04 x64 上で docker run --rm -it ubuntu:bionic した環境で動作確認をしています

OpenMAX

OpenMAX とは

  • 動画・音声などのマルチメディアを操作するための API
    • インターフェースのみで、実装は提供しない。C言語で言えば、ヘッダファイルのみが提供される。
  • プラットフォーム非依存・アプリケーション非依存
    • 動画再生は非常に大きなファイルが高速に処理される必要があり、ハードウェア・プラットフォームの支援が必要不可欠
      • プラットフォームに強く依存したソフトウェアは移植が困難になる。OpenMAX は、処理速度も移植性も犠牲にしないインターフェースを提供する
  • Khronos が策定
    • Khronos が策定している API には OpenMAX の他、 OpenGL、Vulkan などがある
  • ロイヤリティフリーである
    • ロイヤリティフリーでないインターフェースには、例えば HDMI がある
  • 3 つの API が定義されている
    • DL (Develop Layer)
      • メディア処理によく使われる機能の API
      • 例えば高速フーリエ変換・ハフマン符号の展開など
    • IL (Integration Layer)
      • 主にメディアコーデックなどを制御する API
      • 例えば圧縮データのデコードなど
    • AL (Application Layer)
      • 主にメディアプレーヤーなどを制御する API
      • 例えば再生開始・終了の指示など
  • 活用例として、Android の内部実装に OpenMAX IL のインターフェースが利用されている1

OpenMAX IL とは

OpenMAX IL 1.1.2 Specification clause 1.2

  • Component という単位で機能が分割され、機能追加が容易
    • ある Component はデコードを、ある Component はエンコードを、別の Component はカメラ画像を取得…など
  • API を利用して Componenet を操作するコードは IL Client と呼ばれる
  • Component はComponent 同士を直接つなぐことも可能
    • IL Client がデータを操作する必要がなくなる
      • Component の処理は GPU のメモリ空間で行われる場合が多いため、GPU のメモリ空間で別 Component に直接データを渡すことで、GPU のメモリ空間と CPU のメモリ空間を行き来するコストを削減できる
      • ハードウェアと連携しやすい API 設計になっている
    • その挙動が地中(=GPU空間)に潜っているように見えることから2、トンネルと表現されることがある(multimedia tunneling、communication tunnel)

OpenMAX IL の API について。API は core API と component API の 2 種類がある。

  • core API は Component をロードしたり破棄したりする API
  • component API は Component に対して操作する API
    • Component の状態を操作する
      • LOADED, IDLE, EXECUTING の 3 つの状態が主軸
      • 状態を変えるには、次の状態を StateSet した後、次の状態の条件を満たす操作をすることで状態が変更される
        • LOAD -> IDLE の場合、StateSet(IDLE) した後、バッファを必要数確保すると、IDLE に遷移する
        • OpenMAX IL 1.1.2 Specification 3.4 Calling Sequences にシーケンス図と共に例が記載されている
    • Component に対するデータの送信(EmptyThisBuffer関数)や受信(FillThisBuffer関数)
      • Empty は、用意したバッファを Component が消費するというイメージ3
    • その他オプション設定など

Bellagio OpenMAX IL とは

  • OpenMAX IL インターフェースを実装した OpenMAX IL Core
  • Linux で動作する
  • オープンソースで、Ubuntu や Debian であれば apt から入手できる
    • 利用可能なコンポーネントは多くない。主にカメラ等の入出力デバイスと、vorbis等の数個のデコーダのみ
    • ソースからビルド4すれば ffmpeg 実装のビデオデコーダが手に入る。ただし With some rework to do (bad) の扱いになっている

実装

  • Bellagio OpenMAX IL を利用して、 OpenMAX IL のコードを書いてみます
  • OpenMAX IL のインターフェースは C 言語ですが、C++17 から取り扱うことにします
  • 以降記載するコードは、ソースコード量を短縮させるため、エラーを雑に扱っています

環境構築

  • libomxil-bellagio-dev をインストールして Bellagio OpenMAX IL のライブラリと OpenMAX IL のヘッダを取得
  • g++ ビルド環境に build-essentialpkg-config をインストール
apt update
apt install build-essential pkg-config libomxil-bellagio-dev

core を初期化するだけ

コードは以下の通り。

#include <iostream>
#include <OMX_Core.h>

int main() {
  OMX_ERRORTYPE res = OMX_Init();
  if (res != OMX_ErrorNone) {
    std::cerr << "OMX_Init res=" << std::endl;
    abort();
  }
  std::cout << "OMX_Init: ok" << std::endl;

  OMX_ERRORTYPE res = OMX_Deinit();
  if (res != OMX_ErrorNone) {
    std::cerr << "OMX_Deinit res=" << std::endl;
    abort();
  }
  std::cout << "OMX_Deinit: ok" << std::endl;
  return 0;
}

gcc を使って上記コードをビルドします。ライブラリの依存の引数は pkg-config に任せておきます。

g++ -std=c++17 -o app1 $(pkg-config --cflags libomxil-bellagio) app1.cpp  $(pkg-config --libs libomxil-bellagio)
OMX_Init: ok
OMX_Deinit: ok

Component の一覧取得

Component の一覧と、Component ごとの role を取得してみます。
1 つの Component が複数の機能を持っていることがあり、role で区別されます。
よくあるのは、DRM (Digital Rights Management) に対応したものとそうでないものが用意されているケースで、OMX.Xxx.AVC.DecoderOMX.Xxx.AVC.Decoder.secure が取得されがちです。

以下にコードを示します。role 名を格納する型と Component 名を格納する型が異なる点を除いては、特に変わっている点はないです。

#include <array>
#include <iostream>
#include <vector>
#include <OMX_Component.h>
#include <OMX_Core.h>

#define CHECK_OMX(expr)                                                        \
  {                                                                            \
    OMX_ERRORTYPE __res;                                                       \
    if ((__res = (expr)) != OMX_ErrorNone) {                                   \
      std::cerr << "LINE" << __LINE__ << ": failed `" #expr "` errortype="     \
                << static_cast<int>(__res) << std::endl;                       \
      abort();                                                                 \
    }                                                                          \
  }

std::string
convertRoleToString(const std::array<OMX_U8, OMX_MAX_STRINGNAME_SIZE> &role) {
  return std::string(reinterpret_cast<const char *>(role.data()));
}

std::vector<std::array<OMX_U8, OMX_MAX_STRINGNAME_SIZE>>
getRolesOfComponent(const std::string &component_name) {
  constexpr OMX_U32 kMaxNumRoles = 4;
  // TODO: USE OMX_UUIDTYPE
  std::vector<std::array<OMX_U8, OMX_MAX_STRINGNAME_SIZE>> role_names_buf(
      kMaxNumRoles);
  std::vector<OMX_U8 *> role_names_buf_ptr(kMaxNumRoles);
  OMX_U32 num_roles = kMaxNumRoles;

  for (size_t i = 0; i < (size_t)num_roles; ++i)
    role_names_buf_ptr[i] = role_names_buf[i].data();

  CHECK_OMX(OMX_GetRolesOfComponent(const_cast<char *>(component_name.data()),
                                    &num_roles, role_names_buf_ptr.data()));

  role_names_buf.resize(num_roles);
  return role_names_buf;
}

std::vector<std::string> getAllComponentNames() {
  std::vector<std::string> component_names;
  for (OMX_U32 index = 0;; ++index) {
    char component_name[OMX_MAX_STRINGNAME_SIZE] = {};
    OMX_ERRORTYPE result = OMX_ComponentNameEnum(
        component_name, OMX_MAX_STRINGNAME_SIZE - 1, index);
    if (result == OMX_ErrorNoMore)
      break;
    CHECK_OMX(result);
    component_names.emplace_back(component_name);
  }
  return component_names;
}

int main() {
  CHECK_OMX(OMX_Init());

  for (const auto &component_name : getAllComponentNames()) {
    std::cout << "component name: " << component_name << "\n";
    for (const auto &role_name : getRolesOfComponent(component_name)) {
      std::cout << "- role: " << convertRoleToString(role_name) << "\n";
    }
    std::cout << std::endl;
  }

  CHECK_OMX(OMX_Deinit());
  return 0;
}

実行してみると、Component が 1 つも取得できていないと思います。
libomxil-bellagio-dev には Component は入っておらず、別のパッケージに分かれているので、別途インストールします。改めて実行すると、インストールしたものが表示されるはず。

$ g++ -std=c++17 -o app2 $(pkg-config --cflags libomxil-bellagio) app2.cpp  $(pkg-config --libs libomxil-bellagio)
$ ./app2
$ apt search libomxil-bellagio0-components
Sorting... Done
Full Text Search... Done
libomxil-bellagio0-components-alsa/bionic 0.1-2 amd64
  ALSA source/sink components for Bellagio OpenMAX IL

libomxil-bellagio0-components-base/bionic 0.9.3-4 amd64
  components for Bellagio OpenMAX IL
...

$ apt install libomxil-bellagio0-components-fbdevsink
...
$ ./app2 
component name: OMX.st.fbdev.fbdev_sink
- role: fbdev.fbdev_sink

component name: OMX.st.fbdev.fbdev_sink
- role: fbdev.fbdev_sink

Component の作成に進みたいところですが、記事が長くなってしまうのでここまで。

おわり

株式会社ACCESS Advent Calendar 2022 2日目の記事でした。
業務で OpenMAX IL を触れたので、最も手に取りやすい Bellagio を題材にして OpenMAX IL の記事を書きました。

OpenMAX IL に興味を持って触ってみようと思った方は、まずは先に Android の MediaCodec に触れてみることをオススメします。インターフェースはよく似ているし、インターネットにより多くの情報があります。

  1. 実際の実装はこの辺り https://android.googlesource.com/platform/frameworks/av/+/a8a1b31a1d53ce0e2e854ceb3a154c1991495dc4/media/libstagefright/ACodec.cpp

  2. たぶん

  3. だと思う

  4. 2010 年より前のコードのようです。Ubuntu 10.04 より新しい環境では動作保証されていない。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?