オーディオとビデオを支える技術(6)- FFMPEGを使用して、ビデオを画像として保存する。
前回、FFMPEGを使用する環境を構築しました。
今回もその続きから始めていきます。
前回の記事
nakadasanda.hatenablog.jp
まず簡単な例として、FFMPEGを使用して、ビデオファイルを開き、デコードして、ビデオを画像として保存します。
*デコードとは:
元の容量よりも少ない容量で表現された圧縮データを、圧縮前のデータに復元する処理をデコードと呼びます。
今回の目標
プログラム開始
1.まず最初にエンコーダとデコーダを初期化する必要があります。次の関数を使います。
av_registter_all(); //FFMPEGを初期化します。
この関数を使用して、エンコーダとデコーダの初期化を完了です。
最初に呼び出しておきましょう。
ffmpeg4.0からなぜか、warningが出ますが、現在調査中です。
知っている方がいたら教えてください。
2.次にAVFormatContextを初期化します。FFMPEGのすべての操作は、このAVFormatContextを介して実行されます。
AVFormat* pFormatCtx =avformat_alloc_context();
3.ビデオを開きます。 ここでは、日本語のファイルを使わないでください。
char* file_path = "D:\\Amthem.mp4"; avformat_open_input(&pFormatCtx,file_path,NULL,NULL); cout << "file open" << endl;
4.ファイルが開かれた後ファイル内のビデオストリームを検索します。
ストリームとは、ビデオや、オーディオなどを管理する構造の一つです。
/// video タイプのストリームを見つけるまで、動画に含まれるストリーム情報をループします。 /// それを記録しvideoStreamに保存します。 ///ここでは、video ストリームだけを扱います。 for(i=0;i < pFormatCtx->nb_streams;i++) { if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { videoStream = i; } } ///見つかりませんでした。 if(videoStream == -1) { cout << "Don't find stream " << endl; return -1; }
5.次にデコーダーを開いて、ビデオストリームに従ってデコードします。
//デコーダーを見つける。 pCodecCtx = pFormatCtx->streams[videoStream]->codec; pCodec = avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec == NULL) { cout << "not find codec " << endl; } cout << "codec find " << endl; //コーデックを開く if(avcodec_open2(pCodecCtx,pCodec,NULL)<0) { cout << "not open codec " << endl; } cout << "codec open " << endl;
見つかったビデオストリームに基づいて、デコーダーを直接取得できることがわかります。
実際に使用しているエンコーダーは、わかりません。(実際には、pCodecCtxの中にcodec_nameという価があるので、参照したらわかります。)
ビデオコーデックについて、毎回気にする必要がないためffmpegを使用しています。
6.それでは、ビデオを読み込みます。
int y_size = pCodecCtx->width * pCodecCtx->height; packt = (AVPacket *) malloc(sizeof(AVPacket)); //パケットを割り当てる av_new_packet(packt,y_size); 。 int index = 0; while (1) { if(av_read_frame(pFormatCtx,packt) < 0) { break; //読み込み終わったらwhile()終了 }
7.ビデオのデータは、圧縮されているので、デコードする必要があります。
pFrameは、AVFrame構造体でこの構造体は、デコードされた、オーディオまたは、ビデオのデータを保存します。 got_pictureには、decodeできない場合には、0が入り、それ以外には、使用されたバイト数が入ります。
if(packt -> stream_index == videoStream) { ret = avcodec_decode_video2(pCodecCtx,pFrame,&got_picture,packt); if(ret < 0) { cout << "decode error " << endl; return -1; } }
8.基本的にデコード後に取得されたすべての画像は、YUV420形式です。ここでは、画像ファイルとして保存するので、取得したYUV420データをRGB形式に変換する必要があります。
if(got_picture){ sws_scale(img_convert_ctx, (uint8_t const *const *)pFrame->data, pFrame->linesize,0,pCodecCtx->height,pFrameRGB->data, pFrameRGB->linesize); SaveFrame(pFrameRGB,pCodecCtx->width,pCodecCtx->height,index++); if(index > 1000) return 0; //1000回したら終了。 }
sws_scaleは、連続する画像をカットし、結果として、得られた画像をdst画像に配置します。
9.RGBデータを取得した後、それを直接画像に保存します。
void SaveFrame(AVFrame *pFrame, int width, int height,int index) { FILE *pFile; char Filename[32]; int y; //Openfile sprintf(Filename,"frame%d.ppm",index); pFile = fopen(szFilename,"wb"); if(pFile==NULL) return; //Write header fprintf(pFile,"P6\n%d %d\n255\n", width, height); //Write pixel data for(y=0;y<height;y++) { fwrite(pFrame->data[0]+y*pFrame->linesize[0],1,width*3,pFile); } //close file fclose(pFile); }
fopenするところに画像が保存されるので、自分で正しい場所に、Filenameを書き換えるといいと思います。
実行すると画像が、1000枚保存されます。
手元に偶然あったデレステのMVをかけてみました。
1分の動画をかけてみると21GBのファイルサイズになり画像は、
60秒*60fps=3600枚できます。
ソースコードは、以下にあります。 github.com
シリーズ一覧 nakadasanda.hatenablog.jp