最近、色々忙しくなってきて、週末は時間が何かと足りなくなってます・・
では、早速本題!
FFmpeg はオープンソースで、動画や音声のエンコード、デコードなどを行うツール
です、非常に多くの人が改善してきた事で、非常に高い品質と、機能が実装されて
います。
また、このプロジェクトはマルチプラットホームなので、多くの環境で同じように
使う事が出来ます。
「FFmpeg」コマンドを使って動画をエンコードしたりする話は、既に沢山の方が書
かれています、今回の話は、「FFmpeg」が利用しているライブラリー郡を使って、
C++ のプログラムから、動画をデコードしてみようという内容です。
(1)準備
・まず、FFmpeg コマンドをインストールします。
※MSYS2 MinGW-w64 で、話を進めますが、OS-X の BREW などでも同じように出来ま
す。
pacman -S mingw-w64-x86_64-ffmpeg
この操作だけで、ffmpeg 他必要なライブラリーなど全てインストールされます。
(2)実装
・動画の中の、特定の1フレームをデコードするサンプル
・まず、必要そうなヘッダーをインクルード
#include <iostream>
#include <string>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavfilter/avfilter.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
};
※これら、API のプロトタイプでは、全てC言語のみが想定されている為、C++
から使う場合は「extern "C"」が必要です。
・初期化
avcodec_register_all();
av_register_all();
avfilter_register_all();
※FFmpeg 関係お約束の呼び出しですかねぇ・・
・ビデオファイルを開く
std::string file_name = argv[1];
AVFormatContext* pFormatCtx = NULL;
if(avformat_open_input(&pFormatCtx, file_name.c_str(), NULL, NULL) != 0) {
std::cerr << "ERROR: avformat_open_input(): '" << file_name << '\'' << std::endl;
return -1;
}
・ストリーム情報の取得と表示
if(avformat_find_stream_info(pFormatCtx, NULL) < 0) {
std::cerr << "ERROR: avformat_find_stream_info(): '" << file_name << '\'' << std::endl;
return -1;
}
// ストリーム情報の表示
av_dump_format(pFormatCtx, 0, file_name.c_str(), 0);
fflush(stderr);
・コーデックの検索とコーデックのオープン
AVCodecContext* pCodecCtx = pFormatCtx->streams[0]->codec;
AVCodec* pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
// コーデックを開く
if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
std::cerr << "ERROR: avcodec_open2(): '" << file_name << '\'' << std::endl;
return -1;
}
・ログ・レベルの設定
av_log_set_level(1);
※デコードの過程で、非常に細かく、内部の軽微な問題を報告してくれるので、それをカット!
・バッファー関係の確保と必要な初期化
// フレームの確保
AVFrame* pFrame = avcodec_alloc_frame();
// イメージの確保
AVFrame* pImage = avcodec_alloc_frame();
// イメージ用バッファの確保
unsigned char* buffer = (unsigned char *)av_malloc(avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height));
// バッファとフレームを関連付ける
avpicture_fill((AVPicture*)pImage, buffer, PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
・スケーリング用コンテキストの取得
struct SwsContext* pSWSCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height
, pCodecCtx->pix_fmt
, pCodecCtx->width, pCodecCtx->height
, PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
・メイン
int count = 0;
AVPacket packet;
// フレーム読み込み
while(av_read_frame(pFormatCtx, &packet) >= 0) {
// デコード
int state;
avcodec_decode_video2(pCodecCtx, pFrame, &state, &packet);
// 各フレーム、デコード完了
if(state) {
// 特定のフレームを切り出し
if(count == 400) {
int bsize = pCodecCtx->width * pCodecCtx->height * 3;
sws_scale(pSWSCtx, (const uint8_t **)pFrame->data
, pFrame->linesize, 0 , pCodecCtx->height
, pImage->data, pImage->linesize);
FILE *fp = fopen("rgb24.raw", "wb");
if(fp) {
std::cout << "FrameSize: " << (int)pCodecCtx->width << ", " << (int)pCodecCtx->height << std::endl;
fwrite(buffer, bsize, 1, fp);
fclose(fp);
}
}
++count;
}
// パケット・メモリ解放
av_free_packet(&packet);
}
std::cout << "Total frame: " << count << std::endl;
※この場合、400フレーム目を「RGB24」で「RAW」ファイルとして書き出します。
・メモリ解放
sws_freeContext(pSWSCtx);
av_free(buffer);
av_free(pImage);
av_free(pFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
・コンパイルと実行
g++ -c -O2 -std=c++14 -DHAVE_STDINT_H -DWIN32 -DNDEBUG -isystem /mingw64/include -Wall -Werror -Wno-deprecated-declarations -o release/decode.o decode.cpp
g++ -L/mingw64/lib release/decode.o -lpthread -lavdevice -lavformat -lavfilter -lavcodec -lswresample -lswscale -lavutil -lz -o ffdecode_test.exe
ffdecode_test test.mp4
※適当な動画を食わせれば、400フレーム目をデコードして、RGB24ビットで、ファイル「rgb24.raw」を書き出します。
decode.cpp