ffmpegプレーヤーは詳細解の枠組み構築過程を実現する。


fflayはffmpegのソースコードの中で、ソースプレイヤーの実例を持っています。ローカルビデオファイルの再生とオンラインストリーミングメディアの再生をサポートしています。機能は非常に強いです。
FFplay:FFplay is a very simple and portble media player using the FFmpeg libries and the SDL library.It is mostly used as a testbed for the various FFmpeg API.
flayのコードは、ffmpegの関数ライブラリを十分に呼び出していますので、ffmpegの使用を学びたいです。あるいは、ffmpegに基づいて自分のプレイヤーを開発したいです。
ffmpeg自体の開発文書が少ないため、fflayプレイヤーのソースコードの実現は比較的複雑で、基本的なffmpegコンポーネントの呼び出し以外に、ビデオフレームのレンダリング、オーディオフレームの再生、音声ビデオ同期ポリシーおよびスレッドスケジュールなどの問題が含まれています。
したがって、ここでは、ffmpeg公式サイトで紹介されているフイプレイヤの簡略化されたバージョンの開発ルーチンをベースに、この基礎の上で、段階的にゆっくりと深化し、最終的にはビデオプレーヤの完全な論理を実現することを検討します。
fflayプレーヤの簡易バージョンの開発ルーチンはffmpeg公式サイト[documentation]ページの右下にあります。An FFmpeg and SDL Tutorialをクリックして、対応するソースコードを見つけることができます。

1、プロジェクトのコンパイル環境構築
ここではUbuntu 16.04 LTSをベースに説明していますが、ffmpegは複数の主流プラットフォームをサポートしており、apiインターフェースは各プラットフォームで一致していますので、他のプラットフォームも本明細書の内容を参照して、コードをwindowsなどの他のプラットフォームに移植して、調整しやすいです。
ソースコードのコンパイルは、ffmpeg環境の他に、SDL-1.xバージョンのサポートが必要です。ビデオフレームのレンダリングとオーディオフレームの再生を提供します。
1.1 sdlライブラリのコンパイル
SDL(Simple DirectMedia Layer)は、プラットフォームにまたがるマルチメディアとゲーム開発パッケージであり、2 D、オーディオ、イベントドライバ、マルチスレッド、タイマーなどのサービスを提供しています。C言語を使って書かれています。複数の制御画像、音声、出力の関数を提供しています。Mac OS Xなど)のアプリケーションです。
SDL:Simple DirectMedia Layer is a cross-plotform development libraary designed to provide low level access to auudio、keyboard、mouse、jystick、and gruuuudware viaaaaaapenGL and Direretttttttttttttttttwawawarererereremimimimimimimimimimimimimimimimimimimimimimimimimimimimimimittttttttttttttttttttttttttttttttttttttttttttand many Humble Bunle games.
下記のリンクでSDL-1.2.15のソースコードをダウンロードできます。注意してください。ルーチンに依存するSDLバージョンとfflayの違いがあります。
https://www.libsdl.org/download-1.2.php
ダウンロード完了後にSdlソースディレクトリに圧力を解除して、下記の構成方法でMakefileファイルを生成することができます。

./configure --prefix=/usr/local/3rdparty/sdl
Makefileファイルを作成したら、makeコマンドを入力してコンパイルを開始します。コンパイルが完了したら、make installコマンドを実行してインストールします。

make 
make install
インストールが完了すると、configureで指定されたディレクトリの下にsdlのディレクトリが見つかります。sdlはライブラリファイルとしてサポートされていますので、sdl/binディレクトリの下に対応する実行可能ファイルがありません。
1.2 sdl環境変数配置
sdlコンパイルが完了したら、システムに対応する設置位置を探せるようにする必要があります。設定ファイルを開き、その下にsdlの環境変数を追加します。

#SDL ENVIRONMENT
export C_INCLUDE_PATH=/usr/local/3rdparty/sdl/include/SDL:$C_INCLUDE_PATH
export LD_LIBRARY_PATH=/usr/local/3rdparty/sdl/lib:$LD_LIBRARY_PATH
export PKG_CONFIG_PATH=/usr/local/3rdparty/sdl/lib/pkgconfig:$PKG_CONFIG_PATH
1.3プロジェクトのソースコードのコンパイル
プロジェクトのソースコードは下記のMakefileシナリオでコンパイルできます。

tutorial01: tutorial01.c
	gcc -o tutorial01 -g3 tutorial01.c -I${FFMPEG_INCLUDE} -I${SDL_INCLUDE} \
	-L${FFMPEG_LIB} -lavutil -lavformat -lavcodec -lswscale -lswresample -lz -lm \
	`sdl-config --cflags --libs`

clean:
	rm -rf tutorial01
	rm -rf *.ppm
make命令を実行してコンパイルを開始し、コンパイルが完了したら、ソースディレクトリで[tutorial 01]という実行可能ファイルを生成することができます。
1.4検証
fflayの使い方と似ています。[tutorial01 url]コマンドを実行すると、ソースディレクトリで生成されたサフィックスの名前が見えます。ppmの画像が見られます。

./tutorial01 rtmp://58.200.131.2:1935/livetv/hunantv
ppm画像はlinuxプラットフォームの下で直接開くことができます。ppm画像の生成が見られます。プロジェクトが正常に動作することが確認できます。Ctrl+Cを入力してプログラムを終了します。

ppm形式の画像は普段あまり使われていませんので、深く研究する必要はありません。ここではコンパイル結果の検証に使います。
PPM:A PPM file is a 24-bitカラーマップformated using a text format.It stores each pixel with a number from 0 to 65536,which specifes the pixel.PPM files also store the mashe the imeight and width,parthtchthe potable graymap format(PGM)and the potable bitmap format(PBBM)are mage file formas designed to be easure exchange between plot forms.
2ソースコードの分析
上記のルーチンは何枚かの画像を生成する以外に、何もできないようです。機能が整っているビデオプレーヤーからはまだ遠い距離があります。
それでも、ルーチンは、ffmpegビデオ開発に使用されるほとんどのキーアプリとデータ構造を含んでいる。後の内容はこの基礎の上で絶えない完備されています。完全なビデオプレーヤーを実現するまで。
2.1フロー
以下のルーチンのフローチャートを示します。流れは非常に簡単で、すべてのコードはメインスレッドで実行されます。流れはapiとデータ構造の意味に関連しています。

2.2ソースコードに関わるapiとコンポーネント
紙面の制限のため、ここではまず各コンポーネントとappiの意味を簡単に紹介します。後続の文章では各コンポーネントとapiの使い方を深く紹介します。
コンポーネント:
  • AVFormatContectは、ファイル容器のパッケージ情報とコードフローパラメータの構造体
  • を保存します。
  • AVCodecContectデコーダのコンテキストオブジェクト、デコーダに依存する関連環境、ステータス、リソース、およびパラメータセットのインターフェースポインタ
  • AVCodecは、コーデック情報の構造体を保持し、符号化と復号化のための共通インターフェース
  • を提供する。
  • AVPacketは圧縮符号化データに関する情報を保存する構造体を担当しています。各フレームの画像は1〜複数のパケットからなる
  • です。
  • AVFrameは、状態情報、コーデック情報、マクロブロックタイプテーブル、QPテーブル、動きベクトルテーブルなどのデータ
  • を保存する。
  • Sws Contectは、変換器パラメータの構造体
  • を記述する。
    app:
  • av_レジスターallは、すべてのffmpegにサポートされているマルチメディアフォーマットを登録し、コーデック
  • を登録する。
  • avformat_open_inputはビデオファイルを開けて、ファイルのヘッダの内容を読んで、ファイルの容器のカプセル化情報とコードフローのパラメータを取得して、pFormatCtxの中で
  • 保存します。
  • avformat_find_ストリームinfoはファイルに保存されているコードストリーム情報を取得し、pFormatx->streamフィールド
  • に充填する。
  • avcodec_find_decoderは、ビデオストリームに対応するデコーダコンテキストから対応するデコーダを検索し、対応するデコーダ
  • を返す。
  • avcodec_alloc_context 3は、ビデオストリームから抽出されたフレーム
  • を保存するコーデックコンテキストオブジェクトを複製する。
  • avcodec_open 2デコーダを開く
  • av_frame_allocは、復号後のビデオ情報構造体に空間を割り当て、初期化動作
  • を完了する。
  • av_read_.frameはファイルから各画像符号化パケットを順次読み出し、AVPacketデータ構造に格納する。
  • avcodec_decode_video 2は、完全なフレームデータを復号し、一つのpacketが完全なビデオフレームを復号できない場合、ffmpegバックグラウンドで維持されるキャッシュ・キューは、完全なビデオフレームを復号することができるまで、複数のpacketを待ち続ける。
    3 ffmpegは何をしてくれますか?
    ビデオ開発は、複数のビデオフォーマットのデコード、複数のファイルフォーマットや伝送プロトコルのデコードなど、一挙にすべてを把握するのは難しい。
    ffmpegはパッケージのapiとコンポーネントを通して、異なるビデオパッケージのフォーマットと符号化フォーマットの違いをブロックしてくれます。統一したappiインターフェースを開発者に提供します。開発者は各符号化方式とパッケージ方式の具体的な技術詳細を知る必要がなく、ffmpegから提供されたapiを呼び出すだけで、パッケージの解凍と復号の操作が完了できます。
    動画フレームのレンダリングやオーディオフレームの再生に関しては、ffmpegは無力ですので、sdlライブラリのような他のコンポーネントを使って完成します。後の章を紹介します。
    4ソースリスト
    
    // tutorial01.c
    // Code based on a tutorial by Martin Bohme ([email protected])
    // Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1
    // With updates from https://github.com/chelyaev/ffmpeg-tutorial
    // Updates tested on:
    // LAVC 54.59.100, LAVF 54.29.104, LSWS 2.1.101 
    // on GCC 4.7.2 in Debian February 2015
    //
    // Updates tested on:
    // Mac OS X 10.11.6
    // Apple LLVM version 8.0.0 (clang-800.0.38)
    //
    // A small sample program that shows how to use libavformat and libavcodec to read video from a file.
    //
    // Use
    //
    // $ gcc -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lswscale -lz -lm
    //
    // to build (assuming libavutil/libavformat/libavcodec/libswscale are correctly installed your system).
    //
    // Run using
    //
    // $ tutorial01 myvideofile.mpg
    //
    // to write the first five frames from "myvideofile.mpg" to disk in PPM format.
    
    // comment by [email protected]
    
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libswscale/swscale.h>
    #include <libavutil/imgutils.h>
    
    #include <stdio.h>
    
    // compatibility with newer API
    #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
    #define av_frame_alloc avcodec_alloc_frame
    #define av_frame_free avcodec_free_frame
    #endif
    
    //  PPM  
    void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
    	FILE *pFile;//      
    	char szFilename[32];//       
    	
    	// Open file,    
    	sprintf(szFilename, "frame%d.ppm", iFrame);//        
    	pFile = fopen(szFilename, "wb");//      
    	if (pFile == NULL) {//            
    		return;
    	}
     
    	// Write header indicated how wide & tall the image is,           
    	fprintf(pFile, "P6
    %d %d
    255
    ", width, height); // Write pixel data,write the file one line a time, RGB24 int y; for (y = 0; y < height; y++) { fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile); } // Close file, fclose(pFile); } int main(int argc, char *argv[]) { /*-------------- -------------*/ // Initalizing these to NULL prevents segfaults! AVFormatContext *pFormatCtx = NULL;// AVCodecContext *pCodecCtxOrig = NULL;// , 、 、 AVCodecContext *pCodecCtx = NULL;// , PPM AVCodec *pCodec = NULL;// , , AVPacket packet;// , packet AVFrame *pFrame = NULL;// , 、 、 ,QP , AVFrame *pFrameRGB = NULL;// 24-bit RGB PPM struct SwsContext *sws_ctx = NULL;// int numBytes;//RGB24 uint8_t *buffer = NULL;// int i,videoStream;// , int frameFinished;// /*------------- ------------*/ if (argc<2) {// printf("Please provide a movie file
    "); return -1; } // Register all available formats and codecs, ffmpeg av_register_all(); /*----------------------- * Open video file, , , pFormatCtx * read the file header and stores information about the file format in the AVFormatContext structure * The last three arguments are used to specify the file format, buffer size, and format options * but by setting this to NULL or 0, libavformat will auto-detect these -----------------------*/ if (avformat_open_input(&pFormatCtx, argv[1], NULL, NULL) != 0) { return -1; // Couldn't open file. } /*----------------------- * , pFormatCtx->stream * check out & Retrieve the stream information in the file * then populate pFormatCtx->stream with the proper information * pFormatCtx->streams is just an array of pointers, of size pFormatCtx->nb_streams -----------------------*/ if (avformat_find_stream_info(pFormatCtx, NULL) < 0) { return -1; // Couldn't find stream information. } // Dump information about file onto standard error, pFormatCtx av_dump_format(pFormatCtx, 0, argv[1], 0); // Find the first video stream. videoStream=-1;// -1 for (i=0;i<pFormatCtx->nb_streams;i++) {// ( 、 、 ) if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {// videoStream = i;// , -1 break;// } } if (videoStream==-1) {// return -1; // Didn't find a video stream. } // Get a pointer to the codec context for the video stream, pFormatCtx->streams pCodecCtxOrig = pFormatCtx->streams[videoStream]->codec; /*----------------------- * Find the decoder for the video stream, , ( ) * The stream's information about the codec is in what we call the "codec context. * This contains all the information about the codec that the stream is using -----------------------*/ pCodec = avcodec_find_decoder(pCodecCtxOrig->codec_id); if (pCodec == NULL) {// fprintf(stderr, "Unsupported codec!
    "); return -1; // Codec not found. } // Copy context, , pCodecCtx = avcodec_alloc_context3(pCodec);// AVCodecContext pCodecCtx if (avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {// fprintf(stderr, "Couldn't copy codec context"); return -1; // Error copying codec context. } // Open codec, if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { return -1; // Could not open codec. } // Allocate video frame, ( ) pFrame = av_frame_alloc(); // Allocate an AVFrame structure, PPM pFrameRGB = av_frame_alloc(); if (pFrameRGB == NULL) {// return -1; } // Determine required buffer size and allocate buffer, numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height, 1); buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));// RGB24 // Assign appropriate parts of buffer to image planes in pFrameRGB Note that pFrameRGB is an AVFrame, but AVFrame is a superset of AVPicture // AVFrame , out_buffer pFrameYUV->data av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, buffer, AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height, 1); // Initialize SWS context for software scaling, AV_PIX_FMT_RGB24 sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL); /*-------------- -------------*/ i = 0;// Read frames(2 packet) and save first five frames to disk, /*----------------------- * read in a packet and store it in the AVPacket struct * ffmpeg allocates the internal data for us,which is pointed to by packet.data * this is freed by the av_free_packet() -----------------------*/ while (av_read_frame(pFormatCtx, &packet) >= 0) {// , AVPacket // Is this a packet from the video stream, if (packet.stream_index == videoStream) { /*----------------------- * Decode video frame, , frameFinished true * packet frame, packet * avcodec_decode_video2() frameFinished * Technically a packet can contain partial frames or other bits of data * ffmpeg's parser ensures that the packets we get contain either complete or multiple frames * convert the packet to a frame for us and set frameFinisned for us when we have the next frame -----------------------*/ avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet); // Did we get a video frame, if (frameFinished) { // Convert the image from its native format to RGB,// RGB24 sws_scale(sws_ctx, (uint8_t const * const *) pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize); if (++i <= 5) {// Save the frame to disk, 5 SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i); } } } // Free the packet that was allocated by av_read_frame, AVPacket av_packet_unref(&packet); } /*-------------- -------------*/ // Free the RGB image buffer av_free(buffer); av_frame_free(&pFrameRGB); // Free the YUV frame. av_frame_free(&pFrame); // Close the codecs. avcodec_close(pCodecCtx); avcodec_close(pCodecCtxOrig); // Close the video file. avformat_close_input(&pFormatCtx); return 0; }
    ここで、fmpegプレーヤーの詳細な理解を実現するためのフレームワークについての記事を紹介します。これまでの記事を検索したり、下記の関連記事を見たりしてください。これからも応援してください。