動画ファイルの作成と再生

DIBのアニメーション表示では、メモリ上に動画の各フレームデータを作成し、フレームを順次表示する事でアニメショーン表示をしてみました。今回は、フレームデータをディスク上のファイルに作って、そのファイルを読み込みながら表示してみましょう。「動画ファイル」を作ってみるわけです。

動画ファイルの作成

動画ファイルは、単純にフレームのビットマップデータを最初から並べたものとします。フレームは、256×256の256色DIB、総フレーム数は240フレームでフレームレートは20fpsとしました。これだと、動画ファイルの大きさは256×256×240=15Mバイト、ビットレートは256×256×20×8=10Mビット/秒になりますね。

カラーテーブルは256段階のグレースケールとして、まずメモリ上にフレーム0〜239の240枚のフレームを作成し、それをそのままファイルに書き出せば良いでしょう。15Mバイトのメモリを確保するので、実装メモリの少ないマシンだとかなりつらいものがありますが....。メモリ(やファイル内)では、フレーム0、フレーム1...フレーム239とフレームが順番に並ぶようにします。

  /* フレームデータ用バッファ確保 */
  lpBuf=(LPBYTE)GlobalAlloc(GPTR,256*256*240);

動画ファイルを作る処理では、まず動画ファイル全体と同じだけのメモリを確保しそこへ適当なデータを書き込んで行きます。今回の例では、宇宙空間を流星と何なのか良くわからない光が横切って行く、というイメージで作ってみました。
240フレームの画像データをメモリ上に作ったら、後はファイルを作成して書き込むだけです。

  /* ファイル作成 */
  fh=CreateFile("test.vid",GENERIC_READ|GENERIC_WRITE,0,NULL,
    CREATE_NEW,FILE_ATTRIBUTE_NORMAL,NULL);

  /* ファイルに作成した動画データを書き込む */
  WriteFile(fh,lpBuf,256*256*240,&dwDummy,NULL);

  CloseHandle(fh); /* ファイルを閉じる */
  GlobalFree(lpBuf); /* バッファ開放 */

 これで、test.vidという15Mバイトの動画ファイルが作成されました。

動画ファイルの再生

動画ファイルの再生は、動画ファイルから1フレームずつ読み込んで表示して行く、という方式にしました。具体的には、表示すべきフレームのビットマップデータを格納するバッファを用意し、そこにファイルからデータを読み込んで256×256ピクセル/8ビットのDIBヘッダをつけて表示します。

動画ファイルは、フレームのビットマップが順番に並んでいる構造でしたので、フレームnのビットマップデータの先頭はファイルの最初から256×256×nバイトの位置にある事になります。フレームnを表示する時には、ここから256×256=64Kバイト分をバッファに読み込んで画像(静止画)として表示すれば良いわけです。

プログラムではまず最初にメモリを確保して、そのメモリをDIBヘッダとフレーム用のバッファにしましょう。さらに、動画ファイルも開いておきます。

  lpBuf=(LPBYTE)GlobalAlloc /* 必要なメモリをまとめて確保 */
    (GPTR,sizeof(BITMAPINFO)+255*sizeof(RGBQUAD)+256*256);

  lpDIB=(LPBITMAPINFO)lpBuf;
  lpRGB=(RGBQUAD*)(lpBuf+sizeof(BITMAPINFOHEADER));
  lpFrame=lpBuf+sizeof(BITMAPINFO)+255*sizeof(RGBQUAD);

  lpDIB->bmiHeader.biSize=sizeof(BITMAPINFOHEADER); /* BITMAPINFO構造体 */
  lpDIB->bmiHeader.biWidth=256;
  lpDIB->bmiHeader.biHeight=256;
  lpDIB->bmiHeader.biPlanes=1;
  lpDIB->bmiHeader.biBitCount=8;
  lpDIB->bmiHeader.biCompression=BI_RGB;
  lpDIB->bmiColors[0]=lpRGB[0]; /* カラーテーブルの先頭 */

  for (i=0;i<256;i++) { /* カラーテーブル作成 */

      lpRGB[i].rgbRed=i;
      lpRGB[i].rgbGreen=i;
      lpRGB[i].rgbBlue=i;

  }

  /* 動画ファイルを開く */
  fh=CreateFile("test.vid",GENERIC_READ,0,NULL,
    OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

これで、DIBヘッダとカラーテーブル、フレームのビットマップを読み込むバッファが用意できたので、後はメインループの関数でファイルからフレームデータを読み込んで表示するだけです。毎秒20フレーム表示するので、読み込むフレーム番号dwIndexdwStartを再生開始時刻とすれば、現在の時刻を取得する関数GetTickCountを用いて以下の式で求められます。

  /* フレーム番号を1/20秒毎に1増やす */
  dwIndex=(int)((GetTickCount()-dwStart)/50.0);

読み込むフレーム番号がわかったら、ビットレートをもとに画像データのある位置を計算して動画ファイルをシークしフレームデータをバッファに読み込みましょう。読み込みを開始する位置は、ファイルの先頭から256×256×dwIndexですね。

  SetFilePointer(fh,256*256*dwIndex,&dwDummy,FILE_BEGIN);
  ReadFile(fh,lpFrame,256*256,&dwDummy,NULL);

 これで表示すべきフレームデータがlpFrameに読み込まれたので、ウインドウを更新しWM_PAINTでフレームを表示します。

  /* フレームを表示 */
  StretchDIBits(hdc,8,8,256,256,0,0,256,256,
    lpFrame,lpDIB,DIB_RGB_COLORS,SRCCOPY);

プログラム

今回は、動画ファイル作成と再生用に2本プログラムを用意しました。
まず、両方のソースをダウンロードしたらそれぞれのソースをビルドして実行ファイルを2つ作ってください(仮に、動画作成用をvida.exe、再生用をplayera.exeとします)。次に、vida.exe、playera.exe同じディレクトリにコピーします。そして、動画作成プログラムvida.exeを実行して再生用の動画ファイルtest.vidを作りましょう。エラーが出たら、すでにtest.vidという名前のファイルがないか、またディスクの空き容量はあるか、確認してください。
なお、動画ファイルの作成はメモリの少ないマシンではかなり時間がかかりますが、途中で強制終了させないようにしてください。作成に失敗したら一度test.vidを削除してからやり直しましょう。

動画ファイルが出来たら、後は動画ファイルと同じディレクトリにある再生プログラムplayera.exeを実行して「再生」ボタンをクリックすれば、再生できます。プログラムではフレーム数だけでなくコマ落ちも表示するようにしたので、コマ落ちせずに再生できるようであれば再生中に他のプログラムを起動するなど負荷をかけてみましょう。
私の環境では、新しいマシン(HDD8.4GB、メモリ128MB)ではコマ落ちなしで再生できますが、古いマシン(HDD800MB、メモリ24MB)ではコマ落ちが発生しました。最も、コマ落ちしても見苦しいという程ではなく、少し動きがぎこちない程度ですが。むしろ問題なのは動画ファイルの作成。新しいマシンならすぐ出来るのに、古いマシンで動画ファイルを作ると凄まじい状況(ディスクアクセスの嵐)になります(^^;。

プログラムソース(動画ファイル作成)表示
プログラムソース(動画ファイル再生)表示

戻る