バックグラウンド:
ゲームエンタテインメントなどの生放送サービスの成長に伴い、モバイル端末で生放送を視聴する需要はますます高まっています。ただし、モバイル端末のネイティブ プレーヤーは、さまざまなライブ ストリームをうまくサポートしていません。Android のネイティブ MediaPlayer は flv および hls ライブ ストリームをサポートしておらず、iOS は標準の HLS ストリームのみをサポートしています。この記事では、ffplay フレームワークに基づくクロスプラットフォーム プレーヤーの実装を紹介し、ハード デコーディングの実装を考慮に入れます。
プレーヤーの原則:
直観的に言えば、一般的に、メディア ファイルを再生するには、ファイル読み取りモジュール (ソース)、逆多重化モジュール (デマルチプレクサー)、ビデオ デコード モジュール (デコーダー)、色空間変換モジュール (カラー スペース コンバーター)、オーディオの 5 つの基本モジュールが必要です。およびビデオ レンダリング モジュール (Render)。データの流れを下の図に示します。ffmpeg フレームワークには、ファイルを読み取り、オーディオとビデオを逆多重化するためのモジュールが含まれています。
-
ファイル読み取りモジュール (Source) の役割は、下位レベルのデマルチプレクサー モジュール (Demuxer) にパケットの形式でデータ ストリームを継続的に提供することです. 下位レベルのデマルチプレクサーでは、ローカル ファイルとネットワーク データは同じです. ffmpeg フレームワークでは、ファイル読み取りモジュールを 3 つのレイヤーに分けることができます。
- プロトコル層: パイプ、tcp、udp、http などの特定のローカル ファイルまたはネットワーク プロトコル。
- 抽象化レイヤー: 基になる特定のローカル ファイルまたはネットワーク プロトコルを一様に表す URLContext 構造
- インターフェース層は AVIOContext 構造を使用して URLProtocol 構造を広義のファイルに展開し、内部バッファ機構を備えており、最上位層のみ AVIOContext を使用してモジュールの外部にサービスを提供し、メディア ファイルを読み取る機能を実現します。
-
逆多重化モジュール (Demuxer): この機能は、ファイルの種類とメディアの種類を識別し、オーディオ、ビデオ、字幕の元のデータ ストリームを分離し、タイム スタンプ情報を下位レベルのビデオ デコード モジュール (Decoder) に渡します。単純に 2 つの層に分けることができます.最下層は AVIContext、TCPContext、UDPContext などの特定のメディアの逆多重化構造と関連する基本的なプログラムであり、上の層は AVInputFormat 構造と関連するプログラムです。上位層と下位層の間の AVInputFormat に対応する AVFormatContext 構造体の priv_data フィールドは、AVIContext または TCPContext または UDPContext などの特定のファイル形式に関連付けられています。AVInputFormat と特定のオーディオおよびビデオ エンコーディング アルゴリズム フォーマットは、AVFormatContext 構造体のストリーム フィールドによってメディア フォーマットに関連付けられます. ストリームは Demuxer の出力ピンに相当します. 多重分離モジュールは、オーディオとビデオの生データを分離し、それらを下位に渡しますストリームを介したレベルのオーディオおよびビデオ デコーダー。
-
ビデオ デコード モジュール (デコーダ) の機能は、データ パケットをデコードし、同期クロック情報を渡すことです。
-
カラー スペース変換モジュール (カラー スペース コンバーター) の機能は、ビデオ デコーダーによってデコードされたデータを、現在のディスプレイ システムでサポートされているカラー フォーマットに変換することです。
-
オーディオおよびビデオ レンダリング モジュール (Render) の機能は、適切なタイミングで対応するメディアをレンダリングすることです. ビデオ メディアの場合は画像を直接表示することであり、オーディオの場合はサウンドを再生することです.
クロスプラットフォームの実装
プレーヤーの 5 つのモジュールのうち、ファイル読み取りモジュール (Source)、逆多重化モジュール (Demuxer)、および色空間変換モジュール (Color Space Converter) はすべて ffmpeg のフレームワークで実装でき、f fmpeg 自体はクロスしています。 -プラットホーム。したがって、クロスプラットフォーム プレーヤーを実装するには、プラットフォームに依存しないオーディオおよびビデオのデコードおよびレンダリング インターフェイスのレイヤーを抽象化する必要があります。Android、iOS、Window、およびその他のプラットフォームは、標準の ffmpeg ベースのプレーヤーを構築するために、それぞれのプラットフォームのレンダリングとハードウェア デコード (サポートされている場合) を実装するだけで済みます。
次の図は、ffplay に基づく基本的な再生フローチャートです。
図の赤い部分は抽象インターフェイスが必要で、構造は次のとおりです。
FF_Pipenode.run_sync ビデオ デコーディング スレッドには、デフォルトで libavcodec のソフト デコーディング実装があり、他のプラットフォームは独自のハード デコーディング実装を追加できます。SDL_VideoOut は、オーバーレイが Android の NativeWindow または OpenGL の Texture であるビデオ レンダリング抽象化レイヤーです。SDL_AudioOutはサウンドカードドライバを直接操作できるオーディオ再生抽象化レイヤーで、SDL2.0ではALSAやOSSインターフェースをサポートしており、もちろんAndroidやiOS SDKのオーディオAPIでも実装可能です。
ちなみに、Android や iOS プラットフォームの普及に伴い、ffmpeg 版も徐々に Android や iOS 向けのハードウェア デコーダに対応しており、例えば fmpeg は以前から libstagefright をサポートしており、最新の ffmpeg2.8 では iOS のハードウェア デコードもサポートしています。ライブラリ VideoToolBox。以下では、モバイル プラットフォームでのビデオ ハードウェア デコーディングとオーディオおよびビデオ レンダリング モジュールの実装に焦点を当てます。
アンドロイド
1.ハードデコードモジュール:
Android のハード デコード モジュールには現在、次の 2 つの実装があります。
libstagefright_h264:
libstagefright は Android2.3 以降のバージョンのマルチメディア ライブラリです. ffmpeg はバージョン 0.9 で libstagefright_h264 を独自のデコード ライブラリに含めています. libstagefright.cpp に含まれるヘッダー ファイルのパスから判断すると、Android2.3 のソース コードに基づいています. . そのため、libstagefright をコンパイルするには、Android2.3 関連のソース コードとダイナミック リンク ライブラリが必要です。
ffmpeg の libstagefright は現在のところ h264 形式のデコードのみを実装しています. Android のモデルとバージョンの深刻な断片化により, 特定の Android バージョンに基づいてコンパイルされたこの libstagefright も深刻な互換性の問題を抱えています. 私は Android4.4 ではないという問題に遭遇しました.モデルでデコードできます。
メディアコーデック:
MediaCodec は、Google が Android4.1 (API16) 以降に提供する新しいハードウェア コーデック API であり、その動作原理を図に示します。
デコードを例にとると、まず Codec から inputBuffer を取得し、inputbuffer にデコードするデータを入力してから、inputbuffer を Codec に渡すと、解放されたばかりの画像と音声の情報を Codec の outputBuffer から取得できます。コーデック。次のコード例は、問題をよりよく示している可能性があります。
MediaCodec codec = MediaCodec.createByCodecName(name);
codec.configure(format, …);
MediaFormat outputFormat = codec.getOutputFormat(); // option B
codec.start();
for (;;) {
int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
if (inputBufferId >= 0) {
ByteBuffer inputBuffer = codec.getInputBuffer(…);
// fill inputBuffer with valid data
…
codec.queueInputBuffer(inputBufferId, …);
}
int outputBufferId = codec.dequeueOutputBuffer(…);
if (outputBufferId >= 0) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
// bufferFormat is identical to outputFormat
// outputBuffer is ready to be processed or rendered.
…
codec.releaseOutputBuffer(outputBufferId, …);
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// Subsequent data will conform to new format.
// Can ignore if using getOutputFormat(outputBufferId)
outputFormat = codec.getOutputFormat(); // option B
}
}
codec.stop();
codec.release();
残念なことに、MediaCodec は Java 層の API しか提供していないことと、当社のプレーヤーは ffplay アーキテクチャに基づいており、コアのデコード モジュールを Java 層に移動することは不可能です。上に移動できないため、MediaCodec を Native 層にプルし、(*JNIEnv)->CallxxxMethod を使用して、Native 層で MediaCodec に関連する一連の API を作成することしかできません。これで、ビデオのハードウェア デコードを実装できます。
queue_picture の実装を次の図に示します。
2. ビデオ レンダリング モジュール:
レンダリングする前に、最初にレンダリング キャンバスを指定する必要があります。Android では、ImageView、SurfaceView、TextureView、または GLSurfaceView にすることができます。
ネイティブ レイヤーでの画像のレンダリング方法について、次の 4 つのレンダリング方法を紹介している記事を読みました。
- Java サーフェス JNI
- OpenGL ES 2 テクスチャ
- NDK ANativeWindow API
- プライベート C++ API
ソフト デコードに ffmpeg の libavcodec を使用する場合、NDK ANativeWindow API を使用することが最も効率的でシンプルなソリューションになります。主な実装コードは次のとおりです。
ANativeWindow* window = ANativeWindow_fromSurface(env, javaSurface);
ANativeWindow_Buffer buffer;
if (ANativeWindow_lock(window, &buffer, NULL) == 0) {
memcpy(buffer.bits, pixels, w * h * 2);
ANativeWindow_unlockAndPost(window);
}
ANativeWindow_release(window);
サンプル コードの javaSurface は、Java レイヤーの SurfaceHolder から取得され、ピクセルは RGB イメージ データを指します。
MediaCodec をデコードに使用すると、ビデオ レンダリングが非常に簡単になります。MediaCodec の構成時にイメージ レンダリング用の Surface を指定するだけです (MediaCodec.configure)。次に、releaseOutputBuffer (index, true ) を呼び出します。MediaCodec は指定された Surface にイメージをレンダリングします。初めの。
3.オーディオプレーヤーモジュール
Android は、AudioTrack と OpenSL ES という 2 セットのオーディオ インターフェースをサポートしています。ここでは、AudioTrack を例として、オーディオ プロセスの一部を紹介します。
AudioTrack には Java レイヤー API しかないため、MediaCodec のようなネイティブ レイヤーで一連の AudioTrack インターフェイスをやり直す必要があります。
ここでは、デコードと再生は 2 つの独立したスレッドです. audioCallback は、オーディオ フレーム キューからデコードされたオーディオ データを取得する責任があります. デコードされたオーディオのサンプリング レートが AudioTrack でサポートされていない場合は、リサンプリングに libswresample を使用する必要があります.
iOS
1.ハードデコードモジュール
iOS8から、h264ハードウェアコーデックをサポートするVideoToolbox.frameworkというAPIで、ハードデコードとハードコーディングのAPIが公開されましたが、それを使用するにはiOS 8以降が必要です。この一連のハード デコード API は、任意の OC または C++ コードで使用できるいくつかの純粋な C 関数です。最初に VideoToolbox.framework をプロジェクトに追加し、次のヘッダー ファイルをインクルードします。
#含む
デコードには、主に次の 4 つの関数が必要です。
- VTDecompositionSessionCreate はデコード セッションを作成します
- VTDecompressionSessionDecodeFrame はフレームをデコードします
- フレームをデコードした後の VTDecompressionOutputCallback コールバック
- VTDecompressionSessionInvalidate はデコード セッションを破棄します
デコード プロセスを図に示します。
2. ビデオ レンダリング モジュール
ビデオ レンダリングは、OpenGL ES2 テクスチャ マップの形式です。
3.オーディオプレーヤーモジュール
iOS の AudioToolbox.frameworks を使用して再生します。データ フローは Android プラットフォームと同じですが、違いは、Android プラットフォームは PCM データを AudioTrack に送り、iOS は PCM データを AudioQueue に送ります。
要約する
実際、ffpmeg に付属するプレーヤー インスタンス ffplay はクロスプラットフォーム プレーヤーであり、依存するマルチメディア ライブラリ SDL のおかげで、マルチプラットフォームのオーディオおよびビデオ レンダリングを実現します。ただし、SDL ライブラリは大きすぎて、全体としてモバイル端末への移植には適していません。この記事で紹介するクロスプラットフォーム実装スキームも SDL2.0 の内部実装に基づいていますが、レンダリング インターフェイスは再設計されています。