マルチメディア トンネリングは Android フレームワーク 5.0 以降で実装できます。マルチメディア トンネリングは Android TV に必須ではありませんが、高解像度(4K)コンテンツを快適に利用できます。
Android 11 では、音声と動画コンテンツを Tuner から直接供給する、マルチメディア トンネリングを実装できます。Codec2
と AudioTrack
は、Tuner からの HW 同期 ID を使用できます。これは、プログラム クロック リファレンス(PCR)またはシステムタイム クロック(STC)チャネルに対応している場合があります。
背景
Android メディア フレームワークでは、次の 4 つの方法のいずれかで音声 / 動画コンテンツが処理されます。
- ピュア ソフトウェア(ローカル デコード): アプリ プロセッサ(AP)はローカルに、特別なアクセラレーションなしでオーディオをパルス復号変調(PCM)にデコードします。常に Ogg Vorbis に使用され、圧縮オフロードのサポートがない場合は MP3 と AAC に使用されます。
- 圧縮オーディオ オフロード: 圧縮オーディオ データをデジタル シグナル プロセッサ(DSP)に直接送信し、可能な限り AP をオフにします。画面をオフにして音楽ファイルを再生する場合に使用します。
- 圧縮オーディオ パススルー: Android TV デバイスでデコードせずに、圧縮オーディオ(具体的には AC3 と E-AC3)を HDMI 経由で外部のテレビまたはオーディオ レシーバーに直接送信します。動画部分は個別に処理されます。
- マルチメディア トンネリング: 圧縮オーディオ データと圧縮ビデオデータをまとめて送信します。エンコードされたストリームがビデオ デコーダーとオーディオ デコーダーで受信されると、フレームワークに戻りません。理想的には、ストリームは AP を中断しません。
- マルチメディア パススルー: フレームワークを介さずに、圧縮オーディオ データと圧縮ビデオデータを、Tuner からビデオ デコーダーとオーディオ デコーダーにまとめて送信します。

アプローチの比較
ピュア ソフトウェア | 圧縮オーディオ オフロード | 圧縮オーディオ パススルー | マルチメディア トンネリング | マルチメディア パススルー | |
---|---|---|---|---|---|
場所のデコード | 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
インスタンスが AudioTrack
の HW_AV_SYNC
インスタンスにリンクされている場合、HW_AV_SYNC
に由来する暗黙的クロックは、実際のオーディオまたはビデオフレームのプレゼンテーション タイム スタンプ(PTS)に基づき、各ビデオフレームとオーディオ サンプルが提示されるタイミングを制限します。
API 呼び出しフロー
Android 11 以降の場合、クライアントは Tuner からの HW 同期 ID を使用できます。
SurfaceView
インスタンスを作成します。SurfaceView sv = new SurfaceView(mContext);
- オーディオ セッション ID を取得します。この一意の ID は、オーディオ トラック(
AudioTrack
)の作成に使用されます。メディア コーデック(MediaCodec
)に渡され、メディア フレームワークでオーディオパスとビデオパスをリンクするために使用されます。AudioManager am = mContext.getSystemService(AUDIO_SERVICE); int audioSessionId = am.generateAudioSessionId() // or, for Android 11 or higher int avSyncId = tuner.getAvSyncHwId();
- 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);
- ビデオ
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);
- ビデオフレームをデコードします。
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 の順序を入れ替えることができます。


デバイス メーカー向け
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 コンポーネントを構成し(bTunneled
を OMX_TRUE
に設定)、AUDIO_HW_AV_SYNC
フラグを使用して作成された関連するオーディオ出力デバイスを、OMX コンポーネントに渡します(nAudioHwSync
内)。
コンポーネントがこの構成をサポートしている場合、このコーデックにサイドバンド ハンドルを割り当て、pSidebandWindow
メンバーを介して戻す必要があります。サイドバンド ハンドルは、Hardware Composer(HW Composer)が識別できる、トンネルレイヤの ID タグです。コンポーネントがこの構成をサポートしていない場合は、bTunneled
を OMX_FALSE
に設定する必要があります。
フレームワークは、OMX コンポーネントによって割り当てられた、トンネルレイヤ(サイドバンド ハンドル)を取得し、HW Composer に渡します。このレイヤの compositionType
は HWC_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_SYNC
と AUDIO_OUTPUT_FLAG_DIRECT
を設定したオーディオ出力ストリーム プロファイルが少なくとも 1 つ必要です。これらのフラグは、システム クロックをオーディオ クロックから設定するために使用します。
OMX
デバイス メーカーは、トンネル動画再生用に OMX コンポーネントを別途用意する必要があります。メーカーは、セキュアな再生など、他の種類の音声と動画の再生用に OMX コンポーネントを追加できます。
このコンポーネントの出力ポートには、0 バッファ(nBufferCountMin
、nBufferCountActual
)を指定する必要があります。
トンネル コンポーネントは、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)に基づき正しいタイミングで表示します。
