AVI動画とJPEG静止画の同時保存
AVIファイルとJPEGイメージを同時に取得する方法を例示しています。
Software | IC Imaging Control 3.4, Visual Studio™ 2019 |
---|---|
サンプル(C++) | image_and_avi_capture_vc.zip |
AVIファイルとJPEGファイルの同時保存という要求を実現するためには、JPEGファイルを保存するための特別なフレームフィルタを使用します。このフレームフィルタはサンプルのセットアップファイルに含まれています。サンプルには以下の機能を例示しています。
- ICImagingControl 標準のデバイス選択ダイアログを使って、ビデオキャプチャデバイスを開きます。
- 現在使用されているビデオキャプチャデバイスの情報を保存し、次回プログラム起動時にその情報を開きます。
- ICImagingControlの表示サイズをリサイズします。
- コンボボックスに全ての有効なコーデックをリストします。
- コーデックプロパティが有効であれば、そのプロパティダイアログを表示します。
- AVIキャプチャの一時停止、再開を行います。
- AVIファイル保存中に、静止画ファイルを保存します。
事前準備
ライブストリームを表示し、ビデオキャプチャデバイスを取り扱うために、アプリケーションには Grabberオブジェクトが必要です。
DShowLib::Grabber m_cGrabber;
AVIキャプチャを有効にするために、アプリケーションにはMedia Stream Sinkが必要です。
DShowLib::tMediaStreamSinkPtr m_pAVISink;
ライブ表示のリサイズ
ライブビデオはm_cStaticVideoWindowと呼ばれるCStaticオブジェクトに表示されます。
リサイズもm_cStaticVideoWindowにより行われます。
そのため最初にm_cStaticVideoWindow.GetClientRect(&rect)をコールしてm_cStaticVideoWindowのサイズを求める必要があります。
ライブ表示のリサイズをGrabberに指示するため、setDefaultWindowPositionはfalseにセットする必要があります。
その後、要求された横幅、縦幅の情報がGrabberのsetWindowSizeメソッドに引き渡されます。
CRect rect;
m_cStaticVideoWindow.GetClientRect( &rect);
m_cGrabber.setDefaultWindowPosition(false);
m_cGrabber.setWindowSize(rect.Width(), rect.Height());
デバイスのハンドリング
アプリケーション開始時に前回使用されたビデオキャプチャデバイスを開き、復元しようと試みます。
Grabberクラスには以前に保存されたXMLファイルをロードするloadDeviceStateFromFileメソッドが用意されています。ファイルに保存されたビデオキャプチャデバイスが見つかりデバイス設定が正常に読み込まれると、trueが返されます。
if( m_cGrabber.loadDeviceStateFromFile( "device.xml"))
{
// デバイス名をキャプションバーに表示する
SetWindowText( "Image and AVI Capture " + CString(m_cGrabber.getDev().c_str()));
}
アプリケーションにはビデオキャプチャデバイスの選択、デバイスプロパティの設定、ライブストリームの開始と終了のボタンが用意されています。デバイス選択ボタンがクリックされると、まずは実行中(かもしれない)デバイスの実行を停止します。その後.showDevicePage をコールする事によりICImagingControl標準のデバイス選択ダイアログを表示します。ダイアログが閉じられると、アプリケーションは有効なビデオキャプチャデバイスが選択されているかをチェックします。有効なビデオキャプチャデバイスが選択されていた場合、その現在の設定はsaveDeviceStateToFileメソッドによりセーブされます。この作成されたファイルは次回起動時に自動的に読み込まれ、同じデバイスが同じ設定で開かれるようになります。
void CImageandAVICaptureDlg::OnBnClickedButtondevice()
{
// ライブビデオが実行中の場合、停止する
if(m_cGrabber.isDevValid() && m_cGrabber.isLive())
{
m_cGrabber.stopLive();
}
m_cGrabber.showDevicePage(this->m_hWnd);
// 有効なデバイスが選択された場合、それを "device.xml"にセーブする
// それにより、次回起動時に自動的に同じデバイスを開く様にできる
if( m_cGrabber.isDevValid())
{
m_cGrabber.saveDeviceStateToFile("device.xml");
}
// デバイス名をキャプションバーに表示する
SetWindowText( "Image and AVI Capture " + CString(m_cGrabber.getDev().c_str()));
SetButtonStates();
}
ビデオキャプチャデバイスのプロパティは ICImagingControl 標準のデバイスプロパティダイアログを使用する事で簡単に設定する事ができます。showVCDPropertyPageをコールしてこのダイアログを表示します。このダイアログが閉じられた時にもデバイス設定をファイルに保存します。
void CImageandAVICaptureDlg::OnBnClickedButtonimagesettings()
{
if( m_cGrabber.isDevValid())
{
m_cGrabber.showVCDPropertyPage(this->m_hWnd);
m_cGrabber.saveDeviceStateToFile("device.xml");
}
}
ライブstart/stopボタンはGrabberメソッドのisLiveをコールします。このメソッドがtrue を返す場合、ライブストリームは開始または停止されます。
ボタンがクリックされたとき、そのMedia Stream Sink m_pAVISinkはePAUSEにセットされます。ライブストリームが開始されるとライブイメージを表示しますが、sinkモードがeRUNにセットされるまで静止画像はキャプチャできません。これを回避するため、アプリケーションには追加のチェックボックスが用意されています。
void CImageandAVICaptureDlg::OnBnClickedButtonlivevideo()
{
if( m_cGrabber.isDevValid())
{
if (m_cGrabber.isLive())
{
m_cGrabber.stopLive();
}
else
{
m_cGrabber.startLive();
}
SetButtonStates();
m_cCheckAVIPause.SetCheck(1);
m_pAVISink->setSinkMode( GrabberSinkType::ePAUSE );
}
}
フレームフィルタのロード
このダイアログクラスはprivateメンバーm_pSaveImageFilterを含みます。
smart_com<DShowLib::IFrameFilter> m_pSaveImageFilter;
このメンバーは"Save Image"フレームフィルターにアクセスするのに使用されるオブジェクトインスタンスを保持します。FilterLoader::createFilter("Save Image");をコールできるように、ダイアログOnInitDialogメソッドにあるフレームフィルターをロードします。フレームフィルタが正常にロードされると、デバイスパスポジションにあるGrabberオブジェクトに引き渡されます。
// "Save Image" frame filterのロードを試行します。
m_pSaveImageFilter = FilterLoader::createFilter("Save Image");
// フィルタが正常にロードされたかチェックします。
if( m_pSaveImageFilter == NULL)
{
MessageBox("Failed to load the \"Save Image\" frame filter!","Error",MB_OK|MB_ICONEXCLAMATION);
}
else
{
// frame filterが正常にロードされました。
// ここでGrabber objectに追加します。
if( !m_cGrabber.setDeviceFrameFilters( m_pSaveImageFilter.get()) )
{
MessageBox("Failed to add the \"Save Image\" frame filter to the grabber!","Error",MB_OK|MB_ICONEXCLAMATION);
}
}
ライブビデオが実行されているとき、"Save Image"フレームフィルタが静止画をスナップし、JPEGファイルに保存します。アプリケーションではライブビデオストリームとは別のスレッドで実行されるので、beginParamTransferとendParamTransfer をコールする必要があります。これらは設定が可能なフレームフィルタのパラメータをコールするためのものです。
"Save Image"フレームフィルタには、setParameter のコールにより設定される"ImageName"パラメータが必要です。ファイル名がこのパラメータに引き渡されると、"Save Image"フレームフィルタは次に入って来るフレームをスナップし、それを保存します。
void CImageandAVICaptureDlg::OnBnClickedButtonsnapimage()
{
if( m_pSaveImageFilter != NULL && m_cGrabber.isDevValid() && m_cGrabber.isLive())
{
char szImageFileName[MAX_PATH+1];
m_iImageCounter++;
sprintf(szImageFileName,"Image%03d.jpg",m_iImageCounter);
m_pSaveImageFilter->beginParamTransfer();
// フィルタに”Image Name”パラメータを引き渡すと
// フィルタは次に入ってくるイメージをスナップし、ファイルに保存します。
m_pSaveImageFilter->setParameter("ImageName",std::string(szImageFileName));
m_pSaveImageFilter->endParamTransfer();
}
}
Codecの選択とMedia Stream Sinkの設定
AVIファイルをキャプチャするため、コーデックのハンドリングを追加する必要があります。ダイアログクラスはm_CodecListPtrメンバーに含まれており、現在有効なコーデックのリストが示されます。
DShowLib::tCodecListPtr m_CodecListPtr;
ダイアログクラスであるFillCodecComboBoxメソッドは有効なコーデックとそのリストをコンボボックスm_ComboCodecsで呼び出せます。このメソッドでは最初に有効なコーデックが選択され、Media Stream Sinkを生成します。プロパティページを持つコーデックが選択された場合、m_cButtonCodecPropertiesボタンが有効になります。これにより、コーデックのプロパティダイアログを呼び出せます。FillCodecComboBoxメソッドの最後に非圧縮AVIキャプチャがm_ComboCodecsコンボボックスに追加されています。
void CImageandAVICaptureDlg::FillCodecComboBox()
{
m_CodecListPtr = Codec::getAvailableCodecs();
unsigned int i = 0;
for( i = 0; i < m_CodecListPtr->size();i++ )
{
m_ComboCodecs.AddString( m_CodecListPtr->at(i)->getName().c_str());
if( i == 0 ) // 最初のコーデックを選択
{
m_ComboCodecs.SetCurSel(i);
m_pAVISink = MediaStreamSink::create( MediaStreamContainer::create( MSC_AviContainer ), m_CodecListPtr->at(i));
// 現在選択中のコーデックがダイアログを持っている場合、コーデックプロパティボタンが有効になります。
if(m_CodecListPtr->at(i)->hasDialog())
m_cButtonCodecProperties.EnableWindow(true);
else
m_cButtonCodecProperties.EnableWindow(true);
}
}
// 非圧縮フォーマットを追加する
m_ComboCodecs.AddString("Y800");
m_ComboCodecs.AddString("RGB24");
m_ComboCodecs.AddString("RGB32");
}
m_ComboCodecsコンボボックスにコーデックが選択されると、Media Stream Sink m_pAVISinkは新たにこのコーデックで作成する必要があります。まず最初に現在実行中(かもしれない)ビデオストリームを停止します。その後、アプリケーションのダイアログクラスであるm_pAVISinkのメンバーはnullにセットして、現在のMedia Stream Sinkを削除します。次にm_ComboCodecsコンボボックスのテキストを評価します。
「手動」で追加された非圧縮ビデオフォーマットが選択された場合、そのメディアタイプをMediaStreamContainer::create に引き渡す必要があります。
それ以外の場合、m_CodecListPtrリストのiIndexの要素をMediaStreamContainer::createに引き渡します。
iIndexは現在選択されているコーデックの m_ComboCodecsコンボボックスのインデックスです。
そのため、コンボボックスm_ComboCodecsはそのエントリーをソートする必要がありません。
void CImageandAVICaptureDlg::OnCbnSelchangeCombocodecs()
{
int iIndex = m_ComboCodecs.GetCurSel();
bool bRestartLive = false;
if( iIndex > -1 )
{
CString szCodec;
m_ComboCodecs.GetLBText(iIndex,szCodec);
if( m_cGrabber.isLive())
{
m_cGrabber.stopLive();
bRestartLive = true;
}
m_pAVISink = NULL;
GetDlgItem(IDC_BUTTONCODECPROPERTIES)->EnableWindow(false);
if( szCodec == "Y800")
m_pAVISink = MediaStreamSink::create( MediaStreamContainer::create( MSC_AviContainer ), MEDIASUBTYPE_Y800 );
else
if( szCodec == "RGB24")
m_pAVISink = MediaStreamSink::create( MediaStreamContainer::create( MSC_AviContainer ), MEDIASUBTYPE_RGB24 );
else
if( szCodec == "RGB32")
m_pAVISink = MediaStreamSink::create( MediaStreamContainer::create( MSC_AviContainer ), MEDIASUBTYPE_RGB32 );
else
{
m_pAVISink = MediaStreamSink::create( MediaStreamContainer::create( MSC_AviContainer ), m_CodecListPtr->at(iIndex));
if(m_pAVISink->getCodec()->hasDialog())
{
GetDlgItem(IDC_BUTTONCODECPROPERTIES)->EnableWindow(true);
}
}
CString FileName;
m_cEditAVIName.GetWindowText(FileName);
m_pAVISink->setFilename((LPCSTR)FileName);
m_pAVISink->setSinkMode( GrabberSinkType::ePAUSE );
m_cCheckAVIPause.SetCheck(1);
m_cGrabber.setSinkType( m_pAVISink );
if( bRestartLive)
{
m_cGrabber.startLive();
}
}
}
m_cGrabber.setSinkType( m_pAVISink );をコールして新しく作成されたMedia Stream Sink m_pAVISinkをGrabberオブジェクトに引き渡す必要があります。
これが行われない場合もライブ表示と静止画保存は可能ですが、AVIファイルは作成できません。OnCbnSelchangeCombocodecsはMedia Stream Sinkにファイル名を引き渡し、AVIキャプチャをPauseした状態でライブストリームを再起動します。