root/docs/hiratara/20080325-codezine-ffmpeg_api/FFmpeg_api.txt @ 12213

Revision 12213, 23.3 kB (checked in by hiratara, 5 years ago)

FFmpeg関連の作業用
2つの記事に分けた。
前編の記事を校正

  • Property svn:mime-type set to text/plain;charset=Shift_JIS
Line 
1タイトル: FFmpeg APIで様々な動画を処理する/前編
2サブタイトル: 動画からサムネイルの作成
3
4概要:
5オープンソースの動画録画・変換・配信ソフトウェアとしてFFmpegが有名となりつつありますが、そのAPIに関してはあまり解説されていません。本稿ではFFmpeg APIについて説明し、自分のプログラムから様々なコーデックを使って動画を扱う方法を説明します。
6
7本文:
8■はじめに
9
10こんにちわ、hirataraです。
11
12近年、YouTubeやニコニコ動画に代表される動画共有サービスの需要が、急速に高まっています。そのような状況で、オープンソースの動画読み書きソフトウェアのFFmpegが、にわかに注目を集めています。
13
14本稿では、そのFFmpegの共有ライブラリを直接呼び出して、C言語から動画を扱う方法を紹介します。FFmpegにはffmpegと言う便利なコマンドがついていますので、ほとんどの場合はこのコマンドを利用します。しかし、FFmpegを共有ライブラリ付きでコンパイルすると、自分の作ったプログラムから直接FFmpegのAPIを利用することが可能になります。
15
16FFmpegの共有ライブラリを利用すると、動画をファイルからデコードして再生をしたり、新たな動画を作成してファイルに特定フォーマットでエンコードをして保存したりすることができます。対応codecがたくさんあるため、動画プレイヤーやゲームを作成する場合には、大きな力を発揮することでしょう。
17
18■対象読者
19
20[l:-]
21-C言語がわかる方
22-動画をプリミティブに処理したい方
23-ffmpegコマンドについて深く知りたい方
24[/l]
25
26■必要な環境
27
28[l:-]
29-テキストエディタ
30-gccが動作する環境(Linux、Mac OS X、を想定)
31[/l]
32
33●FFmpegを共有ライブラリ付きでインストールする
34
35FFmpegを共有ライブラリ付きでインストールするには、ソースコードからコンパイルする必要があります。まずはsvnを利用して、最新のソースを取ってきて下さい。
36
37[s:bash;最新のソースを取ってくる]
38% svn co svn://svn.mplayerhq.hu/ffmpeg/trunk ffmpeg
39[/s]
40
41そして、 --enable-shared オプション付きで configure をします。後は、通常通り make と make install と実行して、インストールを行って下さい。
42
43[s:bash;最新のソースを取ってくる]
44% ./configure --enable-shared --enable-libmp3lame --enable-libfaad \
45--enable-libfaac --enable-liba52 --enable-gpl
46% make
47% sudo make install
48[/s]
49
50なお、ここで挙げた --enable-shared 以外のオプションは、利用したいcodecによって変わります。自分の環境にあわせて、適切なオプションを指定して下さい。詳しくは、拙著「FFmpegで作る動画共有サイト」の中で書かれています。
51
52[c:FFmpegのリビジョン]
53FFmpegは執筆時点(2008年5月現在)では、とても開発が活発なプロジェクトです。SVN上のソースは刻一刻と変化し続けているため、未来のバージョンでは本稿のプログラムは動かない可能性があります。もっと不運な場合ですと、FFmpegのビルドすらできないことがあります。
54
55もし、SVNのHEADのソースを利用してうまく行かない場合は、本稿の執筆の検証のために利用したリビジョンであるr12939をご利用下さい。ソースのチェックアウトを以下のコマンドで行います。
56
57[s:bash;r12939を取ってくる]
58% svn co -r 12939 svn://svn.mplayerhq.hu/ffmpeg/trunk ffmpeg
59[/s]
60[/c]
61
62
63●インストールしたFFmpegの使い方
64
65FFmpegをインストールすると、ffmpegと言うコマンドが利用できるようになります。例えば、以下のようなコマンドで、動画フォーマットの変更が出来ます。
66
67[s:bash;ffmpegコマンドの実行]
68% ffmpeg -i sample.avi sample.mov
69[/s]
70
71本稿ではffmpegコマンドは一切利用しないので、コマンドラインオプション等詳しい説明に関しては拙著「FFmpegで作る動画共有サイト」を参照して下さい。
72
73
74●サンプルのコンパイル・実行の仕方
75
76サンプルスクリプトには簡単なMakefileを用意してますので、以下のようにmakeして下さい。
77
78[s:shell;makeする]
79% make
80gcc -O4 -Wall -c -o make_thumb.o make_thumb.c
81gcc -o make_thumb  make_thumb.o -lavutil -lavformat -lavcodec
82[/s]
83
84makeするとmake_thumbコマンドができますので、以下のように実行します。
85
86[s:shell;実行]
87% ./make_thumb [読み込み動画ファイル] [出力画像]
88[/s]
89
90
91■サンプル: サムネイル画像を作る 前編
92
93それでは、早速FFmpegの共有ライブラリを利用してみましょう。例として、任意の動画ファイルから初めのフレームを画像として出力するプログラムを作ってみます。このサンプルプログラムがやっていることは単なるファイルフォーマットの変換であり、わざわざC言語のAPIを利用しなくともffmpegコマンドで実行できてしまうことです。しかし、実装していくとわかりますが、共有ライブラリを利用すると、動画の1コマ1コマに直接触ることができます。これによって、ffmpegコマンドだけではできなかった動画のフィルタリングや編集と言ったことまでが実装できるようになります。
94
95前編では、動画の最初の画像を1枚読み込む処理を作成します。必要な処理は、以下の3つです。
96
97[l:+]
98+FFmpegの初期化
99+サムネイルの受け取り場所を確保
100+動画から1フレームを得る
101[/l]
102
103では、早速コードを書いていきましょう。
104
105●FFmpegの初期化
106
107FFmpegのAPIを利用するにあたり、一番始めにしなくてはならないことはFFmpegの初期化です。FFmpegを初期化するには、以下のように記述します。
108
109[s:c;FFmpegを初期化する]
110        /* FFmpegの初期化 */
111        av_register_all();
112[/s]
113
114^^av_register_all()^^は、libavformat/avformat.hで宣言されています。この関数を呼ぶと、FFmpegで利用できる全てのファイルフォーマットとコーデックが初期化され、利用できる状態になります。
115
116また、今回は利用しませんが、通常のファイルフォーマットに加えて外部機器やウィンドウサーバーなどからの入出力を有効にしたい場合は、以下のように^^avdevice_register_all()^^も呼びます。
117
118[s:c;FFmpegを初期化する]
119    avdevice_register_all();
120[/s]
121
122
123●サムネイルの受け取り場所を確保
124
125実際に動画の読み込みを始める前に、動画ファイルから読み込む1フレーム分の格納場所を作っておきます。今回のプログラムでは、前編では確保した領域に最初のフレームを保存し、これを後編でjpeg画像としてエンコードして出力することになります。
126
127[s:c;動画から読み込むフレームの保存場所を作る]
128    /* 画像を保存するフレームの初期化 */
129    AVFrame *frame;
130    init_frame(&frame);
131[/s]
132
133^^AVFrame^^は、libavcodec/avcodec.hで定義されている構造体で、動画の1コマ分を表します。この構造体には、1コマ分の画像の他に、PTS(Presentation Time Stamp)と言うその画像はいつ表示されるべきかを示す値や、その他動画ならではの情報が含まれています。ただし、ここでは単に画像の保存場所として利用します。
134
135なお、PTSに関してはエンコードを行う後編の記事で詳しく説明します。
136
137フレームを初期化する^^init_frame()^^はFFmpegのAPIではなく自前で定義した関数です。^^init_frame()^^の部分コードは、以下のようになります。
138
139[s:c;フレームを初期化]
140/**
141 * 出力するフレームの情報
142 */
143#define PIC_WIDTH  640
144#define PIC_HEIGHT 480
145#define PIC_FORMAT PIX_FMT_YUV420P
146
147/**
148 * フレームを初期化する
149 * @param[out] frame  初期化したいAVFrameポインタへのポインタ
150 */
151int init_frame(AVFrame **frame){
152    /* フレーム領域を確保 */
153    *frame = avcodec_alloc_frame();
154    if(*frame == NULL) error("can't allocate frame.");
155
156    /* 保存用バッファを確保 */
157    int numBytes  = avpicture_get_size(PIC_FORMAT,
158                                       PIC_WIDTH, PIC_HEIGHT);
159    buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
160    if(buffer == NULL) return -1;
161
162    /* バッファをフレームへセット */
163    avpicture_fill((AVPicture *)( *frame ), buffer, PIC_FORMAT,
164                   PIC_WIDTH, PIC_HEIGHT);
165
166    return 0;
167}
168[/s]
169
170^^avcodec_alloc_frame()^^で領域を確保し、^^avpicture_get_size()^^で必要なバッファのサイズを調べて確保しています。画像フォーマットによって必要なバッファのサイズは異なるため、この関数を呼び出して計算する必要があります。
171
172最後に、^^avpicture_fill()^^を利用して確保した^^AVFrame^^に作業用バッファを割り当てています。^^avpicture_fill()^^は^^AVPicture^^を引数に取るため、キャストして渡しています。構造体^^AVFrame^^と構造体^^AVPicture^^は先頭部分の定義が一緒になっているため、キャストが可能です。^^AVPicture^^は純粋に画像データのみを含む構造体です。
173
174
175●動画から1フレームを得る(^^load_frame^^関数の実装)
176
177ここまでで、前処理が完了しましたので、いよいよ動画を読み込む処理を見て行きましょう。^^main()^^関数からは、先ほど確保したフレームを出力先として利用し、以下のように呼び出すこととします。
178
179[s:c;1フレーム読み込み処理の呼び出し部]
180/**
181 * 動画から最初のフレームを読み込む
182 * @param[out] output_frame 読み込んだフレームの出力先
183 * @param[in]  filename     動画ファイルのPATH
184 */
185void load_frame(AVFrame *output_frame, const char *filename);
186
187
188/* main関数 */
189int main(int argc, char *argv[]){
190    /* ... FFmpegの初期化(略) ...*/
191
192    /* ... サムネイルの受け取り場所を確保(略) ...*/
193
194    /* フレームのロード  */
195    load_frame(frame, argv[1]);
196
197    /* ... 後略(後編で作成) ...*/
198}
199[/s]
200
201^^load_frame()^^の処理は、おおよそ以下のような流れで実行していきます。
202
203[l:+]
204+動画ファイルのフォーマットをFFmpegに解析させる
205+動画ファイルの中から、動画ストリームを探し出す
206+コーデックを開き、読み込みの準備を行う
207+パケットを読み込む
208+パケットからフレームを復元する
209+目的のフレームが得られるまで、4と5を繰り返す
210+利用したメモリを解放し、終了する
211[/l]
212
213
214□動画ファイルのフォーマットをFFmpegに解析させる
215
216動画ファイルを読み込むには、当然対象となる動画ファイルのフォーマットやコーデックを正確に把握し、それに合致した手順で読み込まなくてはなりません。FFmpegでは、動画ファイルからこれらの情報を予想するための関数が提供されています。
217
218以下のコードを見て下さい。
219
220[s:c;動画ファイルを解析する]
221    /* ファイルのヘッダを読み、フォーマットを得る */
222    AVFormatContext *formatCtx;
223    ret = av_open_input_file(&formatCtx, filename, NULL, 0, NULL);
224    if(ret != 0) error("can't open input file.");
225
226    /* ファイルの中身からストリーム情報を得る */
227    ret = av_find_stream_info(formatCtx);
228    if(ret < 0) error("can't find stream info.");
229[/s]
230
231構造体^^AVFormatContext^^はlibavformat/avformat.hで定義されています。この構造体には、対象となるファイルの入出力フォーマットとI/Oに関わる情報が保存されています。具体的には、ファイル名や入出力ストリーム、メタ情報として動画のタイトルや著者、等となります。
232
233^^av_open_input_file()^^もlibavformat/avformat.hに宣言があります。この関数では、動画ファイルのヘッダ部を読み込むことで、動画フォーマットを判定します。開いたファイルハンドルや判明したフォーマット情報は、全て^^formatCtx^^へ保存されます。
234
235続く^^av_find_stream_info()^^もlibavformat/avformat.hで宣言される関数で、パケットを先読みして動画ファイルに含まれるストリームの情報を判別します。動画ファイルには動画だけではなく、音声等、種々のストリームが含まれています。この関数を呼ぶことで、含まれるストリームとその種類がわかるようになります。
236
237
238□動画ファイルの中から、動画ストリームを探し出す
239
240今回のサンプルではサムネイルを作成するため、実際に読み込みたいのは動画情報を含むストリームです。複数ある(かもしれない)ストリームの中から、サムネイル作成の対象とするフレームをあらかじめ見つけておきます。
241
242この部分のコードは、以下のようになります。
243
244
245[s:c;動画を含むストリームの検索]
246    /* ビデオストリームを探す */
247    int streamIndex = -1;
248    for(i = 0; i < formatCtx->nb_streams; i++){
249        if(formatCtx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO){
250            /* ビデオストリームを発見 */
251            streamIndex = i;
252            break;
253        }
254    }
255    if(streamIndex < 0) error("can't find video stream.");
256    AVCodecContext *codecCtx = formatCtx->streams[streamIndex]->codec;
257[/s]
258
259
260^^formatCtx->nb_streams^^にはこの動画ファイルが含んでいるストリームの本数が含まれており、^^formatCtx->streams^^には実際のストリーム(^^AVStream^^)が含まれています。これらを利用し、ストリームを0から順番に調べています。ビデオ情報を含んでいるかどうかは、^^streams[i]->codec^^に含まれる、ストリーム内のコーデック情報(^^AVCodecContext^^)を参照し、コーデックの種類で判定しています。コーデックの種類は^^codec->codec_type^^に含まれる^^enum CodecType^^と言う列挙型です。ストリームが動画を含む場合、コーデックの種類は^^CODEC_TYPE_VIDEO^^となります。
261
262目的のストリームが見つかったら、次の処理のためにそのコーデック情報を^^codecCtx^^へ保存しておきます。
263
264[c:コーデックの種類]
265^^enum CodecType^^はlibavcodec/avcodec.hで定義されており、他に以下の種類があります。
266
267[l:-]
268-^^CODEC_TYPE_UNKNOWN^^
269-^^CODEC_TYPE_VIDEO^^
270-^^CODEC_TYPE_AUDIO^^
271-^^CODEC_TYPE_DATA^^
272-^^CODEC_TYPE_SUBTITLE^^
273-^^CODEC_TYPE_ATTACHMENT^^
274[/l]
275
276他に、列挙型の最後の値として^^CODEC_TYPE_NB^^が定義されていますが、これはコーデック種類の個数を表すのに利用されています(^^_NB^^はnumberの略だと推測されます)。例えば、ffmpeg.cでは、^^CODEC_TYPE_NB^^を以下のように利用しています。
277
278[s:c;ffmpeg.cより引用]
279static AVCodecContext *avctx_opts[CODEC_TYPE_NB];
280
281...
282
283for(i=0; i<CODEC_TYPE_NB; i++){
284    avctx_opts[i]= avcodec_alloc_context2(i);
285}
286[/s]
287[/c]
288
289
290□コーデックを開き、読み込みの準備を行う
291
292読み込みたいストリームに対するコーデックの情報が手に入ったので、そのコーデックを開いて動画のデコードが行える状態にします。
293
294コードは、以下のようになります。
295
296[s:c;コーデックを開く]
297    /* codecを探す */
298    AVCodec *codec = avcodec_find_decoder(codecCtx->codec_id);
299    if(codec == NULL) error("can't find codec(decoder).");
300
301    /* codecを開く */
302    ret = avcodec_open(codecCtx, codec);
303    if(ret != 0) error("can't open codec(decoder).");
304[/s]
305
306まず先に、^^AVCodecContext^^について説明しておきましょう。この構造体はlibavcodec/avcodec.hに定義されており、ビットレートや時間の単位、画像のサイズ、その他コーデックに渡すフラグ等、コーデックに関する情報を保持しています。この中に^^enum CodecID^^型の^^codec_id^^と言うフィールドがあり、これが実際のコーデックの識別子となります。
307
308この識別子を^^avcodec_find_decoder^^に渡すと、デコードに必要なコーデックが返ってきます。ここで^^_decoder^^となってることから推測できるかと思いますが、エンコードに必要なコーデックを入手するための^^avcodec_find_encoder^^関数も存在します(後編で利用)。
309
310デコーダを週種で来たら、次はこのデコーダを^^avcodec_open^^関数に渡します。この関数を呼ぶと、^^codec^^に適切な初期化処理を行って^^codecCtx^^へセットし、利用可能な状態にしてくれます。
311
312なお、^^avcodec_find_decoder^^も^^avcodec_open^^もlibavcodec/avcodec.hで宣言されており、実際の定義はlibavcodec/utils.cにあります。
313
314
315□パケットを読み込む
316
317動画ファイルのデータを読み込むには、^^av_read_frame^^を利用します。この関数を呼び出すと、^^AVPacket^^構造体にファイルから1パケットが読み込まれます。動画の場合、パケットにはフレームが1つ含まれています。
318
319ただし、^^av_read_frame^^で入手できるパケットは、動画だけではなく音声等のストリームから得られた物も含んでおり、これらのパケットが時系列の順に読み込まれていきます。今回欲しいのは動画の情報だけですので、あらかじめ見つけておいた動画ストリーム(^^streamIndex^^)から得られたパケットだけを利用します。^^packet.stream_index^^に、出元のストリーム番号が入っているため、これを元に動画ストリームかどうかを判定しています。
320
321なお、^^av_read_frame^^で入手したパケットは、自分で解放する必要があるとドキュメントに書かれています。パケットを解放するには、^^av_free_packet^^を利用します。これを必ず呼び出す必要があるので、LAST: ブロックを作って必ずパケットの解放をするようにしています。
322
323後は、^^while^^で^^av_read_frame^^がパケットを返さなくなるまでループをし続ければ、ファイル全体を読み込むことができます。
324
325ここまでのコードは以下です。
326
327[s:c;パケット読み込みのループ]
328    /* ファイルからパケットを読み込む */
329    AVPacket packet;
330    while(av_read_frame(formatCtx, &packet) >= 0){
331        /* 動画ストリーム以外は飛ばす */
332        if(packet.stream_index != streamIndex) goto LAST;
333
334        /*... パケットの復号処理 ...*/
335
336    LAST:
337        /* パケットを解放 */
338        av_free_packet(&packet);
339    }
340[/s]
341
342ちなみに、^^av_read_frame^^はlibavformat/avformat.hに宣言されています。^^av_free_packet^^も同様にlibavformat/avformat.hにありますが、こちらはインライン関数として直接定義されています。^^AVPacket^^構造体の定義もこのファイル内にあります。
343
344□パケットからフレームを復元する
345
346^^AVPacket^^に含まれているフレームは符号化されているため、このままでは利用できません。これをコーデックで復号することで、参照可能なフレームを得ます。
347
348得たフレームは、最終的には^^output_frame^^に保存しますが、その前に一時領域で受け取ります。パケットの読み込みループに入る前に、以下のように一時領域を用意します。
349
350[s:c;フレームの準備]
351    /* データを受け取るフレームの作成 */
352    AVFrame *frame    = avcodec_alloc_frame();
353    if(frame == NULL) error("can't allocate a frame to store data.");
354[/s]
355
356[c:コーデック内のフレーム領域]
357最初に紹介した^^init_frame^^では、画像のバッファを自前で確保しましたが、ここで用意したフレームではコーデックが画像領域の面倒を見てくれるため、^^AVFrame^^構造体の領域だけ確保しています。
358
359例えば、"mpeg4"や"h264"等のデコーダでは、復号したフレームはlibavcodec/mpegvideo.hの^^MpegEncContext^^構造体の^^picture^^フィールドに格納されており、これらの領域はlibavcodec/mpegvideo.cが確保・解放の管理をしています。
360[/c]
361
362この一時領域へ、パケットを復号してできたフレームを格納していきます。パケットの読み出しループの中で、以下のようなコードで処理をします。
363
364[s:c;フレームの復号]
365    int isFinish = 0;
366    while(パケット読み出し){
367        /* ... 前略 ... */
368
369        /* パケットからフレームを復号する  */
370        avcodec_decode_video(codecCtx, frame, &isFinish,
371                             packet.data, packet.size);
372
373        /* 復号がまだの場合は次のパケットまで処理を飛ばす */
374        if(! isFinish) goto LAST;
375
376        /* ... 後略 ... */
377    }
378[/s]
379
380^^avcodec_decode_video^^はlibavcodec/avcodec.hで宣言されている関数で、パケットからフレームを復号します。引数がちょっと多いので解説します。
381
382[l:-]
383-^^codecCtx^^: コーデックの情報
384-^^frame^^: ここにフレームが出力される
385-^^&isFinish^^: ここに結果が出力される。0だと、フレームが出力されなかったことを意味する。
386-^^packet.data^^: 入力バッファ
387-^^packet.size^^: 入力バッファのサイズ
388[/l]
389
390^^avcodec_decode_video^^の結果、^^isFinish^^に0が代入されていれば、フレームが生成できなかったので^^LAST^^に飛び、パケットを解放してもう一巡してフレームが復号されるのを待ちます。
391
392[c:なぜフレームが復号されないことがあるのか]
393パケットには動画1フレームが含まれていいます。それにも関わらず、なぜ^^avcodec_decode_video^^を読んでもフレームが復号されないことがあるのでしょうか?
394
395それは、動画の圧縮技術に関係があります。MPEGでは圧縮のためにフレーム間の変化を予測をして符号化しますが、これを前フレームからの予測だけではなく、後フレームからの予測も行うことがあります。
396
397例えば、1-2-3-4と言う4フレームがあった場合、MPEGによる圧縮では、2と3のフレームを1と4を元にして圧縮することがあります。そうすると、符号化の順番の関係で、ファイルにはこのフレームが1-4-2-3と言う順で符号化されて記録されることがありえるのです。このとき、動画ファイルを前から読み込むと1の次は4のフレームが含まれたパケットが返ってきますが、次に返さなければいけないのは2のフレームです。そのため、^^avcodec_decode_video^^を呼んで4のフレームを復号してもその場では返すことができず、次の2のフレームが出てくるのを待つと言う動きをします。
398
399この例の2と3のように、前後のフレームを元に符号化されるフレームをBフレーム(Bi-directional Predicted Frame)と呼びます。また、1や4のような、Bフレームの元となるフレームをIフレーム(Intra-coded Frame)やPフレーム(Predicted Frame)と呼びます。Iフレームは他のフレームがなくても復号できるフレームで、Pフレームは手前のフレームだけで復号できるフレームです。
400[/c]
401
402
403○フレームを返却用の領域にコピーする
404
405^^load_frame^^関数では、^^output_frame^^にフレームを戻すことになっていました。ここまでで、^^avcodec_decode_video^^で復号したフレームはコーデックが管理する領域に保持されているので、これをこのまま返すことはできません。そこで、先ほど入手できたフレームを^^avcodec_decode_video^^へコピーする必要があります。
406
407今回は画像のサイズも変更するため、静止画編集のAPIであるlibswscaleを利用します。コードは以下のようになります。
408
409
410[s:c;画像のサイズ変換]
411    while(パケット読み出し){
412        /* ... フレームを復号する処理(略) ... */
413
414        /* フレームが得られたので、画像サイズを変換し、出力先フレームへ保存 */
415        /* 変換の設定 */
416        struct SwsContext *swsCtx = sws_getContext(
417            codecCtx->width, codecCtx->height, codecCtx->pix_fmt,
418            PIC_WIDTH      , PIC_HEIGHT      , PIC_FORMAT,
419            0,
420            NULL, NULL, NULL);
421
422        /* 画像変換する */
423        sws_scale(swsCtx,
424                  frame->data, frame->linesize, 0, codecCtx->height,
425                  output_frame->data, output_frame->linesize);
426
427        /* 変換設定の破棄 */
428        sws_freeContext(swsCtx);
429
430        /* ... 後略 ... */
431    }
432[/s]
433
434^^sws_getContext^^、^^sws_scale^^、^^sws_freeContext^^は全てlibswscale/swscale.hにて宣言されています。^^sws_getContext^^でどんな変換をしたいかを指定し、^^SwsContext^^構造体を作ってもらいます。それを^^sws_scale^^に渡すと、画像の変換を行ってくれます。ここでは、^^frame^^を変換して^^output_frame^^へ保存させています。最後に、^^sws_freeContext^^で確保した領域を解放して完了です。
435
436○1フレームを入手したらループを抜ける
437
438一般的な動画の読み込みでは、ループさせてパケットをどんどん読んでいきますが、今回の例では最初のフレームのサムネイルを作るだけなので、1フレーム入手できればそれ以上パケットを読む必要はありません。ループの最後に以下のように^^break^^を書き、さっさとループを抜けてしまいましょう。
439
440
441[s:c;フレームの復号]
442    while(パケット読み出し){
443        /* ... 復号+サイズ変更処理(略) ... */
444
445        /* 今回は最初のフレームだけが必要なので、フレームが得られていれば終了 */
446        if(isFinish) break;
447    }
448[/s]
449
450
451□^^load_frame^^関数内で利用したメモリを解放し、終了する
452
453これで目的の処理は全て完了しました。最後にコーデックとストリームを閉じ、確保した領域を解放しましょう
454
455[s:c;フレームの復号]
456    /* コーデックを閉じる */
457    avcodec_close(codecCtx);
458    /* ファイルを閉じる */
459    av_close_input_file(formatCtx);
460    /* 確保したメモリを解放する */
461    av_freep(&frame);
462[/s]
463
464^^avcodec_close^^はlibavcodec/avcodec.h、^^av_close_input_file^^はlibavformat/avformat.h、^^av_freep^^はlibavutil/mem.hにそれぞれ宣言されています。
465
466
467これで読み込み処理の説明は終わりです。次の書き込み処理に関しては、後編で解説します。
468
469
470■FFmpegの構造体のまとめ
471
472デコード処理の説明が一通り終わったところで、一度FFmpeg APIで登場する構造体をまとめておきましょう。
473
474○入出力操作を表す構造体
475
476動画を扱うと言う性質上、FFmpegには入出力に関わる構造体が多く登場します。その関係を見てみましょう。
477
478その前に、^^AVStream^^についてまだ説明していなかったので、簡単に説明します。この構造体はlibavfromat/avformat.hで定義されており、^^codec^^フィールドに^^AVCodecContext^^を保持しています。
479
480FFmpegの入出力を取り扱う各構造体の関係を表すと、以下のようなイメージになります。
481
482[s:text;各構造体の関係]
483AVFormatContext - AVInputFormat (又は AVOutputFormat)
484 |
485 +-AVStream
486 |  |
487 |  +-AVCodecContext - AVCodec
488 |
489 +-AVStream
490 |  |
491 |  +-AVCodecContext - AVCodec
492 |
493... 以下動画内にあるストリームの数だけ続く ...
494[/s]
495
496^^AVCodec^^はコーデックであり、音声データや動画データをどのように符号化・復号するのかを表す部分です。コーデックがあれば、音声や動画を適切に符号化することはできますが、音声と動画をうまく同じファイルに保存するためにはそのためのファイル形式が必要です。その部分を扱うのが^^AVInputFormat^^や^^AVOutputFormat^^です。これはいわゆるコンテナの部分で、複数の音声や動画等のストリームをどのようにファイルにまとめるのかを表したデータです。
497
498
499○入出力されるデータを表す構造体
500
501ここまでで、入出力されるデータを表す構造体としては^^AVPicture^^、^^AVFormat^^、^^AVPacket^^が登場しました。
502
503^^AVPacket^^はファイルに保存されている音声や動画の1つ分のデータを表しており、^^AVInputFormat^^や^^AVOutputFormat^^によって扱われます。FFmpegのデータ解析ループは、パケットを単位にして行われます。
504
505^^AVPicture^^と^^AVFrame^^はどちらも動画の"画"を表すデータで、主に動画の^^AVCodec^^によって扱われます。両者の違いは、^^AVPicture^^は単純に画像だけのデータであることに対して、^^AVFrame^^には"動"の側面が含まれることです。具体的には、何コマ目の画像であるとか表示される時間等の情報を含んでいます。
506
507これらの関係を簡単に説明すると、ファイルから^^AVPacket^^を一つずつ読み出し、それをコーデックで処理すると^^AVFrame^^(動画)や音声を得られるイメージとなります。
508
509
510■まとめ
511
512本稿では、以下の内容について説明しました。
513
514[l:-]
515-FFmpeg APIで登場する構造体、関数
516-動画を読み込むのに必要な手続き
517[/l]
518
519後編では、動画の書き込み処理について説明します。
520
521■参考文献
522
523[l:+]
524+『[FFmpegで作る動画共有サイト[http://www.amazon.co.jp/dp/483992466X/]]』毎日コミュニケーションズ
525+[An FFmpeg and SDL Tutorial[http://www.dranger.com/ffmpeg/]]
526+[技術開発【MPEG技術解説】[http://pioneer.jp/crdl/tech/mpeg/1.html]]
527[/l]
Note: See TracBrowser for help on using the browser.