こんにちは、Habr!
この記事では、FFmpegプロジェクトのライブラリーを使用した単純なプレーヤーの開発に焦点を当てます。
Habréでこのテーマに関する記事を見つけられなかったため、このギャップを埋めることにしました。
ビデオのデコードは、FFmpegライブラリ、ディスプレイ、SDLを使用して実行されます。
はじめに
FFmpegを使用すると、エンコードとデコード、多重化と逆多重化など、多数のビデオ処理タスクを実行できます。 これにより、マルチメディアアプリケーションの開発が大幅に促進されます。
ほとんどのオープンソースプロジェクトと同様に、主な問題の1つはドキュメントです。 それは非常に小さく、常に関連するとは限りません。 これは、絶えず変化するAPIを備えたペースの速いプロジェクトです。 したがって、ドキュメントの主なソースは、ライブラリ自体のソースコードです。 古い記事から、[1]と[2]を読むことをお勧めします。 それらは、一般的なライブラリを扱うという考えを与えます。
FFmpegは、さまざまなメディア形式で作業するためのユーティリティとライブラリのセットです。 ユーティリティについて話すことはおそらく意味がありません-誰もがそれらについて聞いたことがありますが、ライブラリについて詳しく知ることができます。
- libavutil-乱数ジェネレータ、データ構造、数学的手順、基本的なマルチメディアユーティリティなどを含む一連の補助関数が含まれています。
- libavcodec-オーディオ/ビデオコーデック用のエンコーダーとデコーダーが含まれています(このフレーズを10回連続で発音します)。
- libavformat-マルチメディアコンテナのマルチプレクサおよびデマルチプレクサが含まれています。
- libavdevice-一般的なマルチメディアフレームワーク(Video4Linux、Video4Linux2、VfW、ALSA)からキャプチャおよびレンダリングするための入出力デバイスが含まれています。
- libavfilter-変換用のフィルターセットが含まれています。
- libswscale-画像のスケーリング、色空間およびピクセル形式の変換を実行するために最適化された関数が含まれています。
- libswresample-オーディオのオーバーサンプリングとサンプル形式の変換を実行するために最適化された関数が含まれています。
画面にビデオを表示するには、SDLを使用します。 これは、非常にシンプルなAPIを備えた便利でクロスプラットフォームなフレームワークです。
経験豊富な読者は、そのようなプレーヤーがFFmpegディストリビューションに既に存在し、そのコードがffplay.cファイルで利用可能であり、SDLも使用していることに気付くかもしれません! しかし、そのコードは初心者のFFmpeg開発者にとって理解が非常に難しく、多くの追加機能が含まれています。
同様のプレーヤーも[1]で説明されていますが、FFmpegに含まれていないか、廃止された関数を使用しています。
現在のAPIを使用した、最小限の理解しやすいプレーヤーの例を紹介します。 簡単にするために、音声なしでビデオのみを表示します。
それでは始めましょう。
コード
まず、必要なヘッダーファイルを含めます。
#include <stdio.h> #include <SDL.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h>
この小さな例では、すべてのコードがメインになります。
まず、
av_register_all()で ffmpegライブラリを初期化します。 初期化中に、ライブラリで使用可能なすべてのファイル形式とコーデックが登録されます。 その後、これらのコーデックを使用して、この形式のファイルを開くときに自動的に使用されます。
int main(int argc, char* argv[]) { if (argc < 2) { printf("Usage: %s filename\n", argv[0]); return 0; }
次に、SDLを初期化します。 引数として、
SDL_Init関数は、初期化する必要があるサブシステムのセットを受け取ります(複数のサブシステムを初期化するために論理ORが使用されます)。 この例では、ビデオサブシステムのみが必要です。
int err;
次に、入力ファイルを開きます。 ファイル名は、コマンドラインの最初の引数として渡されます。
avformat_open_input関数は、ファイルヘッダーを読み取り、見つかった形式に関する情報を
AVFormatContext構造体に
格納します。 残りの引数はNULLに設定できます。この場合、libavformatは自動パラメーター検出を使用します。
なぜなら
avformat_open_inputはファイルヘッダーのみを読み取る
ため 、次のステップはファイル内のストリームに関する情報を取得することです。 これは、
avformat_find_stream_info関数によって行われます。
その後、
format_context-> streamsには既存のすべてのファイルストリームが含まれます。 それらの数は
format_context-> nb_streamsと同じ
です 。
av_dump_format関数を使用して、ファイルおよびすべてのストリームに関する詳細情報を表示できます。
次に、
format_context-> streamsでビデオストリームの番号を取得し
ます 。 この数により、コーデックコンテキスト(
AVCodecContext )を取得でき、ファイルの読み取り時にパッケージのタイプを決定するために使用されます。
ストリーム内のコーデックに関する情報は、「コーデックコンテキスト」(
AVCodecContext )と呼ばれます。 この情報を使用して、必要なコーデック(
AVCodec )を見つけて開くことができます。
AVCodecContext* codec_context = format_context->streams[video_stream]->codec; AVCodec* codec = avcodec_find_decoder(codec_context->codec_id); err = avcodec_open2(codec_context, codec, NULL); if (err < 0) { fprintf(stderr, "ffmpeg: Unable to open codec\n"); return -1; }
SDLを使用してビデオを出力するためのウィンドウを準備します(ビデオのサイズはわかっています)。 一般に、任意のサイズのウィンドウを作成し、libswscaleを使用してビデオをスケーリングできます。 ただし、簡単にするために、ウィンドウをビデオのサイズにしましょう。
ウィンドウ自体に加えて、ビデオを表示するオーバーレイ(オーバーレイ)も追加する必要があります。 SDLは、画面に画像を描画するための多数のメソッドと、ビデオを表示するために特別に設計されたメソッドをサポートしています。これはYUVオーバーレイと呼ばれます。
YUVはRGBのような色空間です。 Y-輝度成分を表し、UとVは色成分です。 この形式はRGBよりも複雑です。なぜなら、 色情報の一部は破棄され、2 Yサンプルごとに1つのUおよびVサンプルのみが存在できます。 YUVオーバーレイは、YUVデータの配列を取得して表示します。 4種類の形式をサポートしていますが、最も高速なのはYV12です。 別のYUV形式があります-YUV420P。 UとVの配列が逆になっていることを除いて、YV12と同じです。 FFmpegは画像をYUV420Pに変換でき、ほとんどのビデオストリームは既にこの形式に含まれているか、単に変換されます。
したがって、SDLのYV12オーバーレイを使用し、FFmpegのビデオをYUV420P形式に変換し、表示時にUおよびV配列の順序を変更します。
SDL_Surface* screen = SDL_SetVideoMode(codec_context->width, codec_context->height, 0, 0); if (screen == NULL) { fprintf(stderr, "Couldn't set video mode\n"); return -1; } SDL_Overlay* bmp = SDL_CreateYUVOverlay(codec_context->width, codec_context->height, SDL_YV12_OVERLAY, screen);
ピクセル形式の変換とFFmpegのスケーリングは、libswscaleを使用して行われます。
変換は2段階で実行されます。 最初のステップは、変換コンテキスト(
struct SwsContext )を作成することです。 以前は、
sws_getContextという
フレンドリ名の
関数がこれ
に使用されていました。 ただし、現在は非推奨であり、コンテキストの作成は
sws_getCachedContextを使用して行うことをお勧めします。 それを使用します。
struct SwsContext* img_convert_context; img_convert_context = sws_getCachedContext(NULL, codec_context->width, codec_context->height, codec_context->pix_fmt, codec_context->width, codec_context->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); if (img_convert_context == NULL) { fprintf(stderr, "Cannot initialize the conversion context\n"); return -1; }
さて、ここで最も興味深い部分、つまりビデオディスプレイについて説明します。
ファイルからのデータはバッチ(
AVPacket )で読み取られ、フレーム(
AVFrame )が表示に使用されます。
ビデオストリームに関連するパッケージにのみ関心があります(ビデオストリームの番号を
video_stream変数に保存したことを思い出して
ください )。
avcodec_decode_video2関数は、以前に受信したコーデック(
codec_context )を使用してパケットをフレームにデコードします。 この関数は、フレーム全体がデコードされる場合、
frame_finishedに正の値を設定します(つまり、1つのフレームが複数のパケットを占有でき、
frame_finishedは最後のパケットのデコード時にのみ設定されます)。
AVFrame* frame = avcodec_alloc_frame(); AVPacket packet; while (av_read_frame(format_context, &packet) >= 0) { if (packet.stream_index == video_stream) {
次に、ウィンドウに表示する画像を準備する必要があります。 まず、データを書き込むため、オーバーレイをブロックします。 ファイル内のビデオは任意の形式にすることができ、YV12用にディスプレイを構成しました。 Libswscaleが助けになります。 前に、
img_convert_context変換
コンテキストをセットアップしました。 それを適用する時間です。 主なlibswscaleメソッドは、もちろん
sws_scaleです。 彼は必要な変換を実行します。 配列を割り当てるときは、インデックスの不整合に注意してください。 これはタイプミスではありません。 前述のように、YUV420Pは、色成分の順序が異なるという点でのみYV12と異なります。 libswscaleをYUV420Pに変換するように設定し、SDLはYV12を期待しています。 ここでは、すべてが正しくなるようにUとVの置換を行います。
SDL_LockYUVOverlay(bmp); AVPicture pict; pict.data[0] = bmp->pixels[0]; pict.data[1] = bmp->pixels[2];
オーバーレイの画像をウィンドウに表示します。
SDL_Rect rect; rect.x = 0; rect.y = 0; rect.w = codec_context->width; rect.h = codec_context->height; SDL_DisplayYUVOverlay(bmp, &rect);
パッケージを処理した後、パッケージが占有しているメモリを解放する必要があります。 これは、
av_free_packet関数によって行われます。
} }
OSがアプリケーションのハングを考慮せず、ウィンドウを閉じるときにアプリケーションを完了するために、サイクルの最後に1つのSDLイベントを処理します。
さて、今ではすべての使用済みリソースをクリーニングするための標準手順です。
sws_freeContext(img_convert_context);
議会に渡します。 gccを使用する最も簡単なオプションは次のようになります。
gcc player.c -o player -lavutil -lavformat -lavcodec -lswscale -lz -lbz2 `sdl-config --cflags --libs`
始めます。 そして、私たちは何を見ますか? ビデオは途方もない速度で再生されています! 正確には、ファイルからのフレームの読み取りとデコードの速度で再生が行われます。 本当に。 フレームレートを制御するコードを1行も記述しませんでした。 そして、このトピックはすでに別の記事のためのものです。 このコードで改善できる多くのことがあります。 たとえば、サウンドの再生を追加したり、他のストリームでファイルを読み込んで表示したりします。 Habrasocietyに興味がある場合は、次の記事でこれについて説明します。
ソースコード全体。ご清聴ありがとうございました!続き:
ffmpegビデオプレーヤーの完成参照資料
- ffmpegとSDLチュートリアル (en)
- libavformatおよびlibavcodecの使用 (en)