再生スレッドでの動画再生

前回の動画再生では、メインループ内で常に現在の時間に応じたフレームを描画していましたが、今回は動画ファイルの再生用に別スレッドを作ってみます。

再生スレッド

 今回は、ウインドウ上に動画を再生する「再生」ボタンと動画ファイルを作成する「作成」ボタンを配置し、再生ボタンをクリックすると再生スレッドを起動するようにしました。

  case 0: /* 再生ボタン */

      /* 再生ボタン無効化 */
      EnableWindow(hwPlay,FALSE);

      /* 再生スレッド起動 */
      hThread=CreateThread(NULL,0,play,0,0,&d);

      break;

 再生スレッドの処理は、基本的に前回の再生と同じです。ただ、今回は再生時間を高精度のtimeGetTime()で取得し、「毎回現在のフレームを描く」のではなく「描きかえる必要がある時に描きかえる」(現在表示しているフレームと表示すべきフレームが同じなら次のフレームの時間までSleepする)ようにしました。
 表示するフレームは以前と同じで、256×256ピクセルの8ビットグレースケールDIBです。動画ファイルにはピクセル列のみを記録し1フレームは256×256=65536バイト、全体は600フレーム(64KB×600=37.5MB)としました。これを毎秒25フレームで表示するので、全体の再生時間は600÷25=24秒になります。

  DWORD WINAPI play(LPVOID lpDummy) { /* 再生スレッド */

      DWORD dwIndex,dwPIndex,dwFFrame,dwStart,dwDummy;
      HANDLE hFile;

      dwStart=timeGetTime();
      dwIndex=0;
      dwPIndex=0;
      dwFFrame=0;
      dwLength=600;

      /* ファイルオープン */
      hFile=CreateFile("test.vid",GENERIC_READ,0,NULL,
        OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

      if (hFile==INVALID_HANDLE_VALUE) { /* オープンエラー */

          MessageBox(NULL,"ファイルを開けませんでした。","失敗",MB_OK);

          CloseHandle(hFile);

          /* メインウインドウに終了メッセージをポスト */
          PostMessage(hwMain,WM_END_PLAY,0,0);

          /* スレッド終了 */
          ExitThread(0);

          return 0;

      }

      do { /* 再生ループ */

          /* 次の描画タイミングまで待つ */
          while((DWORD)((timeGetTime()-dwStart)/40.0)==dwIndex)
              Sleep(1);

          /* 描画するフレーム計算 */
          dwIndex=(int)((timeGetTime()-dwStart)/40.0);

          if (dwIndex>dwLength-1)
              dwIndex=dwLength-1;

          if (dwIndex>dwPIndex+1) /* コマ落ち判定 */
              dwFFrame+=dwIndex-dwPIndex-1;

          dwPIndex=dwIndex; /* 今回再生したフレームを記録 */

          /* フレームをファイルから読みこむ */
          SetFilePointer(hFile,256*256*dwIndex,&dwDummy,FILE_BEGIN);
          ReadFile(hFile,lpFrame,256*256,&dwDummy,NULL);

          wsprintf(lpszStr,"フレーム %03d/ コマ落ち %03d",dwIndex,dwFFrame);

          InvalidateRect(hwMain,NULL,FALSE);
          UpdateWindow (hwMain);             /* フレーム表示 */

      } while (dwIndex<dwLength-1);

      /* ファイルクローズ */
      CloseHandle(hFile);

      /* メインウインドウに終了メッセージをポスト */
      PostMessage(hwMain,WM_END_PLAY,0,0);

      /* スレッド終了 */
      ExitThread(0);
      return 0;

  }

 スレッドの終了時には、メインウインドウに独自に定義したWM_END_PLAYメッセージを送ってメインウインドウのウインドウプロシージャーで再生の終了処理を行います。

  case WM_END_PLAY: /* 再生終了 */

      /* スレッド終了を待ってハンドルクローズ */
      WaitForSingleObject(hThread,INFINITE);
      CloseHandle(hThread);

      /* 再生ボタン有効 */
      EnableWindow(hwPlay,TRUE);

      break;

動画ファイルの作成

「作成」ボタンをクリックすると以下の関数で600フレーム分のピクセル列を書き出して動画ファイルを作成します。

  void video(void) { /* 動画ファイル作成 */

      LPBYTE lpBuf;
      HANDLE hFile;

      DWORD i,j,l,dwDummy;
      int d;

      /* 1フレーム分のバッファ確保 */
      lpBuf=GlobalAlloc(GPTR,256*256);

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

      if (hFile==INVALID_HANDLE_VALUE) {

          MessageBox(NULL,"ファイルを作成できませんでした。","失敗",MB_OK);

          CloseHandle(hFile);
          GlobalFree(lpBuf);

          return;

      }

      for (i=0;i<16;i++) { /* 最初の画面暗転 */

          FillMemory(lpBuf,256*256,255-i*16);

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

      }

      d=1;
      l=1;

      for (i=16;i<600;i++) { /* 背景をスクロール */

          MoveMemory(lpBuf+256,lpBuf,256*255);

          FillMemory(lpBuf,256,l);

          for (j=0;j<16;j++)
              lpBuf[rand() % 256]=rand() % 256;

          WriteFile(hFile,lpBuf,256*256,&dwDummy,NULL);

          if (l==255)
              d=-1;
          else if (l==0)
              d=1;

          l+=d;

      }

      CloseHandle(hFile);
      GlobalFree(lpBuf);

  }

プログラム

 実行したら、まず「作成」ボタンをクリックして動画ファイルを作ってください。この時、test.vidというファイルがあると失敗するので、前回作ったファイルは削除しておく必要があります。動画ファイルの作成にかなり時間がかかったり、作成した後もしばらくはシステムの反応が悪化する事があるので注意してください。次に「再生」ボタンで再生します。私の環境では、ほぼコマ落ちなしで再生できますね。CPUの使用率もあまり高くならないですし。

 なお、今回のプログラムではtimeGetTime()を使用しているので、プロジェクトの設定でリンクにwinmm.libを追加する必要があります。

プログラムソース表示

画像処理研究室