Google は、黒人コミュニティに対する人種平等の促進に取り組んでいます。取り組みを見る

マルチメディア トンネリング

マルチメディア トンネリングは Android フレームワーク 5.0 以降で実装できます。マルチメディア トンネリングは Android TV に必須ではありませんが、高解像度(4K)コンテンツを快適に利用できます。

Android 11 では、音声と動画コンテンツを Tuner から直接供給する、マルチメディア トンネリングを実装できます。Codec2AudioTrack は、Tuner からの HW 同期 ID を使用できます。これは、プログラム クロック リファレンス(PCR)またはシステムタイム クロック(STC)チャネルに対応している場合があります。

背景

Android メディア フレームワークでは、次の 4 つの方法のいずれかで音声 / 動画コンテンツが処理されます。

  • ピュア ソフトウェア(ローカル デコード): アプリ プロセッサ(AP)はローカルに、特別なアクセラレーションなしでオーディオをパルス復号変調(PCM)にデコードします。常に Ogg Vorbis に使用され、圧縮オフロードのサポートがない場合は MP3 と AAC に使用されます。
  • 圧縮オーディオ オフロード: 圧縮オーディオ データをデジタル シグナル プロセッサ(DSP)に直接送信し、可能な限り AP をオフにします。画面をオフにして音楽ファイルを再生する場合に使用します。
  • 圧縮オーディオ パススルー: Android TV デバイスでデコードせずに、圧縮オーディオ(具体的には AC3 と E-AC3)を HDMI 経由で外部のテレビまたはオーディオ レシーバーに直接送信します。動画部分は個別に処理されます。
  • マルチメディア トンネリング: 圧縮オーディオ データと圧縮ビデオデータをまとめて送信します。エンコードされたストリームがビデオ デコーダーとオーディオ デコーダーで受信されると、フレームワークに戻りません。理想的には、ストリームは AP を中断しません。
  • マルチメディア パススルー: フレームワークを介さずに、圧縮オーディオ データと圧縮ビデオデータを、Tuner からビデオ デコーダーとオーディオ デコーダーにまとめて送信します。
マルチメディア トンネリング フロー図
図 1. マルチメディア トンネリング フロー

アプローチの比較

  ピュア ソフトウェア 圧縮オーディオ オフロード 圧縮オーディオ パススルー マルチメディア トンネリング マルチメディア パススルー
場所のデコード AP DSP テレビまたはオーディオ / ビデオ レシーバー(AVR) テレビまたは AVR テレビまたは AVR
音声の処理 はい はい はい はい いいえ
動画の処理 はい いいえ いいえ はい いいえ

アプリ デベロッパー向け

SurfaceView インスタンスを作成し、オーディオ セッション ID を取得してから、AudioTrack インスタンスと MediaCodec インスタンスを作成して、再生とビデオフレームのデコードに必要なタイミングと構成を指定します。

Android 11 以降の場合、オーディオ セッション ID の代わりに、アプリは Tuner から HW 同期 ID を取得し、それを A/V 同期用に AudioTrack インスタンスと MediaCodec インスタンスに提供できます。

A/V 同期

マルチメディア トンネリング モードでは、オーディオとビデオがマスター クロックで同期されます。

  • Android 11 以降の場合、Tuner の PCR または STC が、A/V 同期のマスター クロックになることがあります。
  • Android 10 以前の場合、オーディオ クロックは A/V 再生に使用されるマスター クロックです。

トンネルビデオ MediaCodec インスタンスと AudioTrack インスタンスが AudioTrackHW_AV_SYNC インスタンスにリンクされている場合、HW_AV_SYNC に由来する暗黙的クロックは、実際のオーディオまたはビデオフレームのプレゼンテーション タイム スタンプ(PTS)に基づき、各ビデオフレームとオーディオ サンプルが提示されるタイミングを制限します。

API 呼び出しフロー

Android 11 以降の場合、クライアントは Tuner からの HW 同期 ID を使用できます。

  1. SurfaceView インスタンスを作成します。
    SurfaceView sv = new SurfaceView(mContext);
  2. オーディオ セッション ID を取得します。この一意の ID は、オーディオ トラック(AudioTrack)の作成に使用されます。メディア コーデック(MediaCodec)に渡され、メディア フレームワークでオーディオパスとビデオパスをリンクするために使用されます。
    AudioManager am = mContext.getSystemService(AUDIO_SERVICE);
    int audioSessionId = am.generateAudioSessionId()
    // or, for Android 11 or higher
    int avSyncId = tuner.getAvSyncHwId();
  3. HW A/V 同期 AudioAttributes を使用して AudioTrack を作成します。

    オーディオ ポリシー マネージャーは、FLAG_HW_AV_SYNC をサポートするデバイス出力を Hardware Abstraction Layer(HAL)に要求し、中間ミキサーなしでこの出力に直接接続されたオーディオ トラックを作成します。

    AudioAttributes.Builder aab = new AudioAttributes.Builder();
    aab.setUsage(AudioAttributes.USAGE_MEDIA);
    aab.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE);
    aab.setFlag(AudioAttributes.FLAG_HW_AV_SYNC);
    
    // or, for Android 11 or higher
    new tunerConfig = TunerConfiguration(0, avSyncId);
    aab.setTunerConfiguration(tunerConfig);
    
    AudioAttributes aa = aab.build();
    AudioTrack at = new AudioTrack(aa);
    
  4. ビデオ MediaCodec インスタンスを作成し、トンネル動画再生用に構成します。
    // retrieve codec with tunneled video playback feature
    MediaFormat mf = MediaFormat.createVideoFormat(“video/hevc”, 3840, 2160);
    mf.setFeatureEnabled(CodecCapabilities.FEATURE_TunneledPlayback, true);
    MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
    String codecName = mcl.findDecoderForFormat(mf);
    if (codecName == null) {
      return FAILURE;
    }
    // create codec and configure it
    mf.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, audioSessionId);
    
    // or, for Android 11 or higher
    mf.setInteger(MediaFormat.KEY_HARDWARE_AV_SYNC_ID, avSyncId);
    
    MediaCodec mc = MediaCodec.createCodecByName(codecName);
    mc.configure(mf, sv.getSurfaceHolder().getSurface(), null, 0);
    
  5. ビデオフレームをデコードします。
    mc.start();
     for (;;) {
       int ibi = mc.dequeueInputBuffer(timeoutUs);
       if (ibi >= 0) {
         ByteBuffer ib = mc.getInputBuffer(ibi);
         // fill input buffer (ib) with valid data
         ...
         mc.queueInputBuffer(ibi, ...);
       }
       // no need to dequeue explicitly output buffers. The codec
       // does this directly to the sideband layer.
     }
     mc.stop();
     mc.release();
     mc = null;

注: このプロセスでは、次の 2 つの図に示すように、ステップ 3 と 4 の順序を入れ替えることができます。

コーデックの構成前に作成したオーディオ トラックの図
図 2. コーデックの構成前に作成したオーディオ トラック
コーデックの構成後に作成したオーディオ トラックの図
図 3. コーデックの構成後に作成したオーディオ トラック

デバイス メーカー向け

OEM は、トンネル動画再生をサポートする独立した動画デコーダ OpenMAX IL(OMX)コンポーネントを作成する必要があります。この OMX コンポーネントは、トンネル再生が可能であることをアドバタイズする必要があります(media_codecs.xml 内)。

<Feature name=”tunneled-playback” required=”true” />

また、コンポーネントは、ConfigureVideoTunnelModeParams 構造を使用する OMX 拡張パラメータ OMX.google.android.index.configureVideoTunnelMode をサポートする必要があります。

struct ConfigureVideoTunnelModeParams {
    OMX_U32 nSize;              // IN
    OMX_VERSIONTYPE nVersion;   // IN
    OMX_U32 nPortIndex;         // IN
    OMX_BOOL bTunneled;         // IN/OUT
    OMX_U32 nAudioHwSync;       // IN
    OMX_PTR pSidebandWindow;    // OUT
};

トンネル MediaCodec の作成リクエストが行われると、フレームワークはトンネルモードで OMX コンポーネントを構成し(bTunneledOMX_TRUE に設定)、AUDIO_HW_AV_SYNC フラグを使用して作成された関連するオーディオ出力デバイスを、OMX コンポーネントに渡します(nAudioHwSync 内)。

コンポーネントがこの構成をサポートしている場合、このコーデックにサイドバンド ハンドルを割り当て、pSidebandWindow メンバーを介して戻す必要があります。サイドバンド ハンドルは、Hardware Composer(HW Composer)が識別できる、トンネルレイヤの ID タグです。コンポーネントがこの構成をサポートしていない場合は、bTunneledOMX_FALSE に設定する必要があります。

フレームワークは、OMX コンポーネントによって割り当てられた、トンネルレイヤ(サイドバンド ハンドル)を取得し、HW Composer に渡します。このレイヤの compositionTypeHWC_SIDEBAND に設定されます(hardware/libhardware/include/hardware/hwcomposer.h をご覧ください)。

HW Composer は、適切なタイミングで(たとえば、関連するオーディオ出力デバイスと同期して)ストリームから新しいイメージ バッファを受け取り、他のレイヤの現在のコンテンツと合成して、結果のイメージを表示します。これは、通常の準備 / 設定サイクルとは無関係に行われます。準備 / 設定の呼び出しは、他のレイヤが変更された場合、またはサイドバンド レイヤのプロパティ(位置やサイズなど)が変更された場合にのみ行われます。

構成

frameworks/av/services/audioflinger/AudioFlinger.cpp

HAL は HW_AV_SYNC ID を 64 ビット整数の文字列 10 進表現として返します(frameworks/av/services/audioflinger/AudioFlinger.cpp をご覧ください)。

audio_hw_device_t *dev = mPrimaryHardwareDev->hwDevice();
char *reply = dev->get_parameters(dev, AUDIO_PARAMETER_HW_AV_SYNC);
AudioParameter param = AudioParameter(String8(reply));
int hwAVSyncId;
param.getInt(String8(AUDIO_PARAMETER_HW_AV_SYNC), hwAVSyncId);

frameworks/av/services/audioflinger/Threads.cpp

オーディオ フレームワークは、このセッション ID が対応する HAL 出力ストリームを検索し、set_parameters を使用して HAL に hwAVSyncId をクエリする必要があります。

mOutput->stream->common.set_parameters(&mOutput->stream->common,
AUDIO_PARAMETER_STREAM_HW_AV_SYNC=hwAVSyncId);

OMX デコーダーの構成

MediaCodec.java

オーディオ フレームワークは、このセッション ID が対応する HAL 出力ストリームを検索し、get_parameters を使用して HAL に AUDIO_PARAMETER_STREAM_HW_AV_SYNC フラグをクエリすることで audio-hw-sync ID を取得します。

// Retrieve HW AV sync audio output device from Audio Service
// in MediaCodec.configure()
if (entry.getKey().equals(MediaFormat.KEY_AUDIO_SESSION_ID)) {
    int sessionId = 0;
    try {
        sessionId = (Integer)entry.getValue();
    }
    catch (Exception e) {
        throw new IllegalArgumentException("Wrong Session ID Parameter!");
    }
    keys[i] = "audio-hw-sync";
    values[i] = AudioSystem.getAudioHwSyncForSession(sessionId);
}

// ...

この HW 同期 ID は、カスタム パラメータ OMX.google.android.index.configureVideoTunnelMode を使用して OMX トンネル ビデオ デコーダーに渡されます。

ACodec.cpp

オーディオ ハードウェア同期 ID を取得すると、ACodec がそれを使用してトンネル ビデオ デコーダーを構成し、トンネル ビデオ デコーダーが同期するオーディオ トラックを認識できるようにします。

// Assume you're going to use tunneled video rendering.
// Configure OMX component in tunneled mode and grab sideband handle (sidebandHandle) from OMX
// component.

native_handle_t* sidebandHandle;

// Configure OMX component in tunneled mode
status_t err = mOMX->configureVideoTunnelMode(mNode, kPortIndexOutput,
        OMX_TRUE, audioHwSync, &sidebandHandle);

OMXNodeInstance.cpp

OMX コンポーネントは、上記の configureVideoTunnelMode メソッドで構成します。

// paraphrased

OMX_INDEXTYPE index;
OMX_STRING name = const_cast<OMX_STRING>(
        "OMX.google.android.index.configureVideoTunnelMode");

OMX_ERRORTYPE err = OMX_GetExtensionIndex(mHandle, name, &index);

ConfigureVideoTunnelModeParams tunnelParams;
InitOMXParams(&tunnelParams);
tunnelParams.nPortIndex = portIndex;
tunnelParams.bTunneled = tunneled;
tunnelParams.nAudioHwSync = audioHwSync;
err = OMX_SetParameter(mHandle, index, &tunnelParams);

err = OMX_GetParameter(mHandle, index, &tunnelParams);

sidebandHandle = (native_handle_t*)tunnelParams.pSidebandWindow;

ACodec.cpp

トンネルモードで OMX コンポーネントを構成すると、サイドバンド ハンドルがレンダリング サーフェスに関連付けられます。

  err = native_window_set_sideband_stream(nativeWindow.get(),
  sidebandHandle); if (err != OK) { ALOGE("native_window_set_sideband_stream(%p) failed! (err %d).",
  sidebandHandle, err); return err; }

次に、最大解像度のヒントがあれば、コンポーネントに送信されます。

// Configure max adaptive playback resolution - as for any other video decoder
int32_t maxWidth = 0, maxHeight = 0;
if (msg->findInt32("max-width", &maxWidth) &&
    msg->findInt32("max-height", &maxHeight)) {
    err = mOMX->prepareForAdaptivePlayback(
              mNode, kPortIndexOutput, OMX_TRUE, maxWidth, maxHeight);
}

一時停止のサポート

Android 5.0 以前では、一時停止はサポートされていません。A/V の枯渇でのみ、トンネル再生を一時停止できますが、動画の内部バッファが大きい場合(OMX コンポーネントに 1 秒間分のデータがある場合など)は、一時停止が応答しないように見えます。

Android 5.1 以降では、AudioFlinger はダイレクト(トンネル)オーディオ出力の一時停止と再開をサポートしています。HAL で一時停止 / 再開を実装した場合、トラックの一時停止 / 再開は HAL に転送されます。

一時停止、フラッシュ、再開の呼び出しシーケンスは、再生スレッドで HAL 呼び出しを実行することによって優先されます(オフロードの場合と同様)。

Codec2 のサポート

Android 11 以降では、Codec2 はトンネル再生をサポートしています。

CCodec.cpp

トンネル再生をサポートするために、Codec2 は OMX と同様に機能します。Tuner からの HW 同期 ID をサポートするために、Codec2 は下記のソースから同期 ID を検索します。

sp<ANativeWindow> nativeWindow = static_cast<ANativeWindow *>(surface.get());
int32_t audioHwSync = 0;
if (!msg->findInt32("hw-av-sync-id", &audioHwSync)) {
       if (!msg->findInt32("audio-hw-sync", &audioHwSync)) {
       }
}
err = configureTunneledVideoPlayback(comp, audioHwSync, nativeWindow);

実装に関する提案

オーディオ HAL

Android 11 では、A/V 同期に PCR または STC からの HW 同期 ID を使用できるため、動画のみのストリームがサポートされています。

Android 10 以前では、トンネル動画再生をサポートするデバイスには、audio_policy.conf ファイルにフラグ FLAG_HW_AV_SYNCAUDIO_OUTPUT_FLAG_DIRECT を設定したオーディオ出力ストリーム プロファイルが少なくとも 1 つ必要です。これらのフラグは、システム クロックをオーディオ クロックから設定するために使用します。

OMX

デバイス メーカーは、トンネル動画再生用に OMX コンポーネントを別途用意する必要があります。メーカーは、セキュアな再生など、他の種類の音声と動画の再生用に OMX コンポーネントを追加できます。

このコンポーネントの出力ポートには、0 バッファ(nBufferCountMinnBufferCountActual)を指定する必要があります。

トンネル コンポーネントは、OMX.google.android.index.prepareForAdaptivePlayback setParameter 拡張機能も実装する必要があります。

トンネル コンポーネントは、media_codecs.xml ファイルでその機能を指定し、トンネル再生機能を宣言する必要があります。また、フレームサイズ、アライメント、ビットレートの制限についても明確にする必要があります。


    <MediaCodec name="OMX.OEM_NAME.VIDEO.DECODER.AVC.tunneled"
    type="video/avc" >
        <Feature name="adaptive-playback" />
        <Feature name="tunneled-playback" required=”true” />
        <Limit name="size" min="32x32" max="3840x2160" />
        <Limit name="alignment" value="2x2" />
        <Limit name="bitrate" range="1-20000000" />
            ...
    </MediaCodec>

トンネル デコードとトンネル以外のデコードをサポートするために同じ OMX コンポーネントを使用する場合は、トンネル再生機能を必須ではないものとしておく必要があります。トンネル デコーダーとトンネル以外のデコーダーはどちらも同じ機能制限が適用されます。

    <MediaCodec name="OMX.OEM_NAME.VIDEO.DECODER.AVC" type="video/avc" >
        <Feature name="adaptive-playback" />
        <Feature name="tunneled-playback" />
        <Limit name="size" min="32x32" max="3840x2160" />
        <Limit name="alignment" value="2x2" />
        <Limit name="bitrate" range="1-20000000" />
            ...
    </MediaCodec>

HW Composer

ディスプレイ上にトンネルレイヤ(HWC_SIDEBAND compositionType のレイヤ)がある場合、そのレイヤの sidebandStream は OMX 動画コンポーネントによって割り当てられたサイドバンド ハンドルです。

HW Composer は、デコードされた動画フレームを(トンネル OMX コンポーネントから)関連するオーディオ トラックに(audio-hw-sync ID で)同期します。新しい動画フレームが現在のフレームになると、HW Composer は、最後の準備 / 設定呼び出しで受け取ったすべてのレイヤの現在のコンテンツを合成し、結果のイメージを表示します。準備 / 設定の呼び出しは、他のレイヤが変更された場合、またはサイドバンド レイヤのプロパティ(位置やサイズなど)が変更された場合にのみ行われます。

図 4 に、HW(またはカーネル / ドライバ)シンクロナイザーと連携する HW Composer を示します。動画フレーム(7b)を最新のコンポジション(7a)と組み合わせて、音声(7c)に基づき正しいタイミングで表示します。

音声に基づいて動画フレームを組み合わせる HW Composer の図
図 4. HW(またはカーネル / ドライバ)シンクロナイザーと連携する HW Composer