次の方法で共有


ビデオ エンコード

ビデオ エンコードの設定を保存して再利用する

Adi Shavit

コード サンプルのダウンロード

カメラや記憶装置などの機器の価格が下落しコンピューティング パフォーマンスが向上するにつれて、リモート監視システムから組み込みのロボット プラットフォームに至るまで、ビデオを自動的かつ自立的に録画するアプリケーションがこれまでになく普及してきています。しかし、このようなシステムは、配置やインストールを単純化し自動化する必要があるにもかかわらず、リモートで構成できないことはよくあります。

最近、起動時に同じ設定を自動的に読み込んで使用するよう、事前にリモート システムのビデオ録画設定を構成する方法が必要になりました。Web を検索したところ、同様の質問が 10 年以上前からさまざまなフォーラムに寄せられてきたことがわかりましたが、部分的な解決策や不完全な解決策しか見つかりませんでした。

この記事では、ビデオ処理アプリケーションから一定の圧縮設定と一緒にビデオを保存できるようにすることで、アプリケーションやコンピューターを起動するたびに手動でコーデックの設定を指定する必要のない、単純ながらも汎用の方法を紹介します。また、コーデックの内部設定バッファーにアクセスすることで、簡単に保存、再読み込み、および再利用できるようにする方法も説明します。この方法は、コンピューターにインストールされているあらゆるビデオ コーデックに使用でき、コーデック固有の API を使用する必要はありません。

ビデオの基礎

圧縮していないビデオ データのサイズは莫大です。毎秒 15 フレームという低いフレーム レートで録画する、ごく平均的な VGA カメラについて考えてみます。3 チャネル、640×480 ピクセルのフレームは 1 フレームあたりのサイズが 900 KB になるため、毎秒 13 MBps (メガバイト/秒) を超えるレートでデータが生成されます。これは約 105 Mbps (メガビット/秒) に相当します。携帯電話のカメラなど、やや高度なビデオ機器では、高精細 (HD) 解像度、高いフレーム レート、または 8 ビットを超える色深度を使用しているものもあります。HD ビデオを 90 分録画するとは、圧縮していない形式ではおおよそ 1 TB ものサイズに達します。さいわい、ビデオ データは冗長性が高いため、ビデオ ファイルは、ビデオ圧縮アルゴリズムによってをかなり効率よく保存できます。使用する圧縮アルゴリズムの種類は、具体的なアプリケーションとその要件や制約によって異なります。リアルタイムのレート、パフォーマンスに関する考慮事項、画質と生成後のファイル サイズのトレードオフなど、圧縮方法によって異なる要素もいくつかあります。

Windows では、ビデオ圧縮サービスをコーデックによって提供しています。コーデック (codec) は 圧縮と圧縮解除 (COmpressor-DECompressor) を略した造語です。今回は、圧縮済みの .avi ビデオ ファイルを保存する Windows 用ビデオ (VfW) API に注目します。Audio Video Interleaved (.avi) はマルチメディア コンテナー ファイル形式の 1 つで、オーディオとビデオの両方のストリームを複数含めることができ、選択したストリームのオーディオとビデオを同期再生できます。ここからは、圧縮のコーデック設定を手動で選択して永続的な方法で保存することで、同じコーデックをインストールしている任意のコンピューター上のアプリケーションで、この設定を後から再読み込みして自動的に再利用する方法を説明します。

VfW を使ってビデオを保存する

VfW の導入は 1992 年とかなり古いものですが、現在も広く使用されている不朽の API です。ffmpeg のスイート (ffmpeg.org、英語) など、新しいコーデックやコーデックの実装では、今でも Microsoft Windows の VfW インターフェイスを提供しています。

ビデオ録画プログラムの一般的なフローは、通常、次のような手順になります。

  1. 新しい AVI ファイルを作成し、このファイル内に新しく圧縮していないビデオ ストリームを作成します。次に、圧縮に必要な設定を選択し、圧縮していないストリームから新しく圧縮済みのストリームを作成します。
  2. 必要な数だけ新しいフレームを追加します。
  3. ここまでの手順が完了したら、逆の順番でリソースを解放します。

VfW を使用すると、このフローは次のようになります。

  1. 次の関数を使用して、AVI ビデオ ファイルを開いて準備します。
    1. AVIFileInit: AVIFile ライブラリを初期化します。他の AVIFile 関数を使用する前に呼び出す必要があります。
    2. AVIFileOpen: AVI ファイルを開きます。
    3. AVIFileCreateStream: AVI ファイル内に新しいビデオ ストリームを作成します。AVI ファイルには、(さまざまな種類の) 複数のストリームを含めることができます。
    4. AVISaveOptions: 標準の圧縮オプション ダイアログ ボックスを表示します (図 1 参照)。ユーザーが圧縮オプションの選択を終えると、選択されたオプションが AVICOMPRESSOPTIONS 構造体に返されます。
    5. AVIMakeCompressedStream: AVIFileCreateStream から返された圧縮していないストリームと AVISaveOptions から返された圧縮フィルターから、圧縮ストリームを作成します。
    6. AVIStreamSetFormat: ストリームのイメージ形式を設定します。
  2. 次の関数を使用して、ビデオ ストリームにフレームを追加します。
    1. AVIStreamWrite: ビデオ ストリームにフレームを 1 つ追加します。繰り返し呼び出して、必要な数だけフレームを追加します。
  3. 次の関数を使用して、クリーンアップして閉じます。
    1. AVIStreamRelease: 圧縮ストリーム (AVIMakeCompressedStream で作成) を閉じます。
    2. AVIStreamRelease: 圧縮していないストリーム (AVIFileCreateStream で作成) を閉じます。
    3. AVISaveOptionsFree: AVISaveOptions 関数で割り当てたリソースを解放します (この処理を忘れるとメモリ リークが発生することがよくあります)。
    4. AVIFileRelease: AVI ファイル (AVIFileOpen で作成) を閉じます。
    5. AVIFileExit: AVIFile ライブラリを終了します。

The Video Compression Dialog Box Opened by the AVISaveOptions Function図 1 AVISaveOptions 関数で表示される圧縮オプション ダイアログ ボックス

VfW を使用したビデオの録画方法の詳細については、ここで扱う範囲を超えています。多数のサンプルやチュートリアルがオンラインで公開されています。ここでは、手順 1.4. の圧縮プログラムの選択とコーデックの設定に重点を置いて説明します。この記事付属のソース コードは、OpenCV (opencv.willowgarage.com、英語) オープンソース プロジェクトの C++ 実装に基づいており、code.msdn.microsoft.com/mag201112Video (英語) からダウンロードできます。ただし、ここで紹介する手法が適しているのは (また、簡単に統合できるのは)、特定の実装やプログラミング言語専用ではない、VfW API を使用する任意の録画アプリケーションです。

一般的なソリューション

既に説明したように、圧縮設定を指定する一般的な方法は、AVISaveOptions 関数を呼び出して、圧縮オプション ダイアログ ボックスを表示する方法です。ユーザーは、目的のコーデックを手動で選択できます。選択したコーデックによっては、コーデックの [Configure] (構成) オプションが表示され、コーデック固有の設定をカスタマイズできます。当然ながら、アクティブなユーザーがいないどころか GUI もないシステムでは、このダイアログ ボックスは使用できません。

手動選択の一般的な代替策としては、プログラムでコーデックの FOURCC (4 文字コード、詳細については fourcc.org 参照) 識別子を使用して特定のコーデックを選択し、すべてのコーデックに共通する追加の一般的な設定 (データ レート、品質、およびキー フレーム レート) を指定します。この手法の大きな欠点は、コーデック固有の設定を指定できない点です。さらに、コーデックの内部設定が存在していれば、必ずその設定がコーデックで使用されます。内部設定は、既定の設定の場合もあれば、さらに悪いことに他のプログラムで一時的に指定した設定の場合もあります。また、システムで使用できるすべての FOURCC コーデックで VfW API がサポートされているとは限らないため、圧縮ストリームの作成に失敗することがあります。

一部のコーデックには、内部設定を外部から変更できる専用の構成ツールが用意されています。このような構成ツールは、GUI 形式の場合もあれば、コマンド ライン ツール形式の場合もあります。しかし、ほとんどのコーデックにはこのような構成ツールが備わっていません。また、各ツールは個々のコーデック用にカスタマイズされているため、ツールを使用するには専用の構文や使用法を学ぶ必要があります。

今回必要とするのは、コーデックとその内部設定を自動選択する場合でも、手動選択モードのように柔軟に選択できることでした。

両方の長所を活かす

1 つ目の手法では、AVISaveOptions を呼び出して、すべての値を設定した AVICOMPRESSOPTIONS オブジェクトを返します。2 つ目の手法では、基本的に、AVISaveOptions を使用せずにコード内で AVICOMPRESSOPTIONS オブジェクトの値の一部を指定します。

実のところ、AVISaveOptions の呼び出すと、内部のコーデックの状態全体を保存できるため、AVISaveOptions を再度呼び出さなくてもコーデックの状態を復元できます。ビデオ圧縮プログラム固有の内部のデータと形式に関する秘訣は、lpParms と lpFormat という AVICOMPRESSOPTIONS の型が不明なポインターにあります。AVISaveOptions から返されるとき、この 2 つのポインターは AVIMakeCompressedStream 関数で圧縮ストリームを作成するために必要なすべての情報を指しています (おそらくこの情報は AVISaveOptionsFree 関数で解放するリソースです)。

したがって、今回の手法では、lpParms と lpFormat が指す型が不明なデータを保存して、再利用できるようにすることを基本方針に据えました。さいわい、この処理は簡単です。AVICOMPRESSOPTIONS 構造体には 2 つの便利なメンバー (cbParms と cbFormat) があり、それぞれ lpParms と lpFormat が指している型が不明なバイナリ バッファーのサイズを表しています。バッファーのサイズは、選択したコーデックに応じて変化します。

まとめると、AVISaveOptions の呼び出し後に保存が必要なバイナリの "BLOB" は 3 つあります。つまり、AVICOMPRESSOPTIONS オブジェクト自体と、このオブジェクトの lpParms メンバーと lpFormat メンバーが指している 2 つのバイナリ バッファーです。2 つのバッファーのバイト単位の長さは、それぞれ cbParms と cbFormat を使用して取得します。コーデックの設定を復元するには、まったく逆の手順を実行します。まず、ファイルから AVICOMPRESSOPTIONS オブジェクトを読み取ります。次に、ファイルから読み取った対応するバイナリ バッファーを指すように、オブジェクトの lpParms メンバーと lpFormat メンバーを設定します。復元したバイナリ バッファーを、アプリケーション自体で割り当てて管理します。

バイナリ データを保存する方法は多数あります。図 2 に示すように、サンプル コードではデータをバイナリ形式で保存しています。また、表示できない文字を使用してはならない場合 (.txt ファイルや .xml ファイルの場合)、経験では Base 64 エンコーディングを使用してバッファーを保存できました。

図 2 コーデックの設定を保存して復元する例

bool CvVideoWriter_VFW::writeCodecParams( char const* configFileName ) const
{
  using namespace std;   
  try
  { // Open config file
    ofstream cfgFile(configFileName, ios::out | ios::binary);          
    cfgFile.exceptions ( fstream::failbit | fstream::badbit );
    // Write AVICOMPRESSOPTIONS struct data
    cfgFile.write(reinterpret_cast<char const*>(&copts_), sizeof(copts_)); 
    if (copts_.cbParms != 0)// Write codec param buffer
      cfgFile.write(reinterpret_cast<char const*>(copts_.lpParms), copts_.cbParms);
    if (copts_.cbFormat != 0)// Write codec format buffer
      cfgFile.write(reinterpret_cast<char const*>(copts_.lpFormat),
        copts_.cbFormat);
  }
  catch (fstream::failure const&)
  { return false; } // Write failed
  return true;
}
bool CvVideoWriter_VFW::readCodecParams( char const* configFileName )
{ 
  using namespace std;
  try
  { // Open config file
    ifstream cfgFile(configFileName, ios::in | ios::binary);
    cfgFile.exceptions (
      fstream::failbit | fstream::badbit | fstream::eofbit );
    // Read AVICOMPRESSOPTIONS struct data
    cfgFile.read(reinterpret_cast<char*>(&copts_), sizeof(copts_));        
    if (copts_.cbParms != 0)
    { copts_Parms_.resize(copts_.cbParms,0);                // Allocate buffer
      cfgFile.read(&copts_Parms_[0], copts_Parms_.size());  // Read param buffer
      copts_.lpParms = &copts_Parms_[0];                    // Set lpParms to buffer
    }
    else
    { copts_Parms_.clear();
      copts_.lpParms = NULL;
    }
    if (copts_.cbFormat != 0)
    { copts_Format_.resize(copts_.cbFormat,0);              // Allocate buffer
      cfgFile.read(&copts_Format_[0], copts_Format_.size());// Read format buffer
      copts_.lpFormat = &copts_Format_[0];                  // Set lpFormat to buffer
    }
    else
    { copts_Format_.clear();
      copts_.lpFormat = NULL;
    }
  }
  catch (fstream::failure const&)
  { // A read failed, clean up
    ZeroMemory(&copts_,sizeof(AVICOMPRESSOPTIONS));
    copts_Parms_.clear();
    copts_Format_.clear();   
    return false; 
  }
  return true;
}

サンプル コードに関する注意事項

この記事のサンプル コードでは、ビデオ カメラや Web カメラのビデオをキャプチャして、AVI ファイルに保存します。コードでは OpenCV の VideoCapture クラスを使用してカメラにアクセスし、ビデオをキャプチャしています。ビデオを保存するため、OpenCV の CvVideoWriter_VFW クラスを使用しています。今回は、コーデックの設定の保存をサポートするようにこのクラスを変更しました。その際、元のコードをできる限り変更しないよう注意しました。サンプル コードでは、CvVideoWriter_VFW::open メソッドの 2 つ目の引数が std::string になっています。この文字列の長さが (FOURCC のように) 4 文字の場合は、文字列を FOURCC と見なし、この FOURCC に対応するコーデックを選択します。つまり、通常は mmioFOURCC マクロを使用して、4 つの 1 バイト文字を FOURCC 整数コードに変換します (mmioFOURCC('D','I','V','X') など)。詳細については、tinyurl.com/mmioFOURCC (英語) を参照してください。長さが 4 文字ではなければ、文字列をコーデックの設定ファイル名と見なします。ファイルが存在するときは、ファイルから設定を読み取って、ビデオの圧縮に使用します。存在しないときは、圧縮オプション ダイアログ ボックスを表示し、ユーザーがコーデックとその設定を手動で指定できるようにします。選択した設定は、選択した名前のファイルに保存して、アプリケーションの次回実行時に再利用します。注: サンプル コードをコンパイルして実行するには、OpenCV の実装が必要です。

複数のメリット

ここで紹介したソリューションには、複数のメリットがあります。アプリケーションの圧縮設定を変更する場合は、引数を 1 つ変更するだけで済みます。再コンパイルする必要も、実行中のアプリケーションを停止する必要もありません。この手法はコーデックに依存せず、すべての VfW コーデックで機能します。コーデックによっては、フレームのサイズ変更やインターレース解除など、詳細なカスタマイズされたイメージ処理も実行できます。このようなオプションはすべて、コーデックの設定ファイル内で自動的にキャプチャされます。

この手法は、従来の Microsoft Windows システム (Windows 2000 以降) でも、以前のコンパイラ (Visual Studio 6.0 など) でも機能します。また、Visual Basic、C# など、VfW インターフェイスを公開するさまざまな言語で使用できます。さらに、各コンピューターにおける内部のコーデックの現状に関係なく、複数のコンピューターに配置できます。

主な短所は、このソリューションが確実に機能するのがまったく同じバージョンのコーデックを使用している場合に限られることです。特定のコンピューターにコーデックがインストールされていない場合や、設定ファイルの作成に異なるバージョンを使用した場合、予想の付かない結果になることがあります。もちろん、このソリューションは VfW API に基づいているので、VfW 以外のコーデックは使用できません。

今回のソリューションの拡張例としては、同じ方法でオーディオ圧縮プログラムの設定を保存することが挙げられます。オーディオ圧縮プログラムの設定は、ビデオ圧縮プログラムの設定と同じように処理できます。また、AVISaveOptions では複数のストリームが含まれる AVI をネイティブにサポートしているので、このような場合にも今回と同じソリューションを利用でき、ストリームごとのコーデックの設定を 1 つのファイルに書き込むことができます。

Windows には、DirectShow という一般的なビデオおよびオーディオ処理 API がもう 1 つあり、こちらの方が新しい API です。DirectShow に関しても同様のソリューションを実装できますが、これについては今後の記事で取り上げる予定です。

Adi Shavit は、コンピューター画像に関するコンサルタントです。15 年以上にわたってイメージおよびビデオ処理システムに携わっており、リアルタイムのビデオ分析を専門としています。連絡先は adishavit@gmail.com (英語のみ) です。

この記事のレビューに協力してくれた技術スタッフの Dana ShavitMatthew Wilson に心より感謝いたします。