Стоит задача анализа содержимого видео файла. Я больше инженер, чем программист, но пришлось разбираться с DirectShow, поэтому сильно не
пинайте… Использую Builder6.
Необходимо последовательно (иногда пропуская) сменять кадры, затем извлекать их содержимое для анализа. Соответственно на экране
исходный кадр отображаться не должен, т.к. там будет измененная картинка.
Для перехода к кадру использую связку
pMediaSeeking->SetPositions(&Nf,AM_SEEKING_AbsolutePositioning,NULL,AM_SEEKING_NoPositioning);
pBasicVideo->GetCurrentImage(&lBufSize,(long*)pDIB);
но GetCurrentImage жутко тормозит и на получение одного кадра уходит пол секунды времени!
Подскажите пожалуйста, как можно повысить быстродействие?
Выкладываю весь блок кода, возможно ошибка в самом подходе к решению задачи?
#include <vcl.h>
#include <dshow.h>
#include "Unit1.h"
#include "c_DirectShow.h"// объявления функций ниже#pragma comment (lib, "strmiids.lib") // IVideoFrameStep
//------------------------------------------------------------------------------//
HRESULT hr;
IGraphBuilder *pGraphBuilder;
IBasicVideo *pBasicVideo;
IMediaSeeking *pMediaSeeking;
//IVideoWindow *pVideoWindow;
BYTE *pDIB; // попиксельный массив видео кадра с заголовком.extern Graphics::TBitmap *mFRAME; // моя результирующая картинка
//==============================================================================//
//------------------------------------------------------------------------------//
// Инициализация и построение постоянных графов. //
//------------------------------------------------------------------------------//int OpenDirShow()
{
pGraphBuilder = NULL; // Интерфейс менеджера графа-фильтров
pBasicVideo = NULL;
pMediaSeeking = NULL;
//--------------------------------------------------------------------------
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); // Инициализируем библиотеку COMif(FAILED(hr)) return 1;
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, // Создание менеджера графа-фильтров
IID_IGraphBuilder, (void **)&pGraphBuilder);
if(FAILED(hr)) return 2;
hr = pGraphBuilder->QueryInterface(IID_IBasicVideo,(void**)&pBasicVideo);
if(FAILED(hr)) return 3;
hr = pGraphBuilder->QueryInterface(IID_IMediaSeeking,(void**)&pMediaSeeking);
if(FAILED(hr)) return 4;
//--------------------------------------------------------------------------return 0;
}
//------------------------------------------------------------------------------//
// освобождение ресурсов. //
//------------------------------------------------------------------------------//void CloseDirShow()
{
if(pMediaSeeking) pMediaSeeking->Release();
if(pBasicVideo) pBasicVideo->Release();
if(pGraphBuilder) pGraphBuilder->Release();
CoUninitialize();
}
//------------------------------------------------------------------------------//
// Открываем видео файл (Для Unicode) //
// Для не Unicode hr = pGraph->RenderFile((LPCWSTR)pcFileName, NULL); //
//------------------------------------------------------------------------------//
// Получает: //
// - имя файла //
// Возвращает: //
// - количество кадров //
// - размер кадра //
//------------------------------------------------------------------------------//int OpenAVI(char *pcFileName, __int64 *Lframe, long *Xframe, long *Yframe)
{
const GUID TIME_FORMAT_FRAME = { 0x7b785570, 0x8c82, 0x11cf, { 0xbc, 0xc, 0x0, 0xaa, 0x0, 0xac, 0x74, 0xf6}};
__int64 Nframe;
WCHAR wFileName[MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, pcFileName, -1, wFileName, MAX_PATH); // строку преобразую в Юникод
hr = pGraphBuilder->RenderFile((LPCWSTR)wFileName, NULL); // открываю файлif(FAILED(hr)) return 1;
pMediaSeeking->SetTimeFormat(&TIME_FORMAT_FRAME); // формат "кадры"
pMediaSeeking->GetDuration(Lframe); // количество кадров
pBasicVideo->GetVideoSize(Xframe,Yframe); // размер кадра
// hr = pGraphBuilder->QueryInterface(IID_IVideoWindow, (void**)&pVideoWindow); // параметры отображения (где и как)?
// if(FAILED(hr)) return 2;
ChgFrame(1, *Lframe, *Xframe, *Yframe); // отобразили кадрreturn 0;
}
//------------------------------------------------------------------------------//
// При закрытии видео файла освобождаем память занятую под кадр //
//------------------------------------------------------------------------------//void CloseAVI()
{
// if(pVideoWindow) pVideoWindow->Release();
delete [] pDIB;
}
//------------------------------------------------------------------------------//
// Произвольный доступ к заданному кадру. Рисует этот кадр на Form1->Image1 //
// Получает: //
// - признак инициализации (1 - при открытии файла) или работы (0) //
// - номер кадра для показа //
// - размер кадра //
//------------------------------------------------------------------------------//void ChgFrame(bool init, __int64 Nf, long Xf, long Yf)
{
TDateTime TTT0,TTT1;
AnsiString As[4];
static long lBufSize; // размер данных кадра (шапка + данные)static HDC wrkDC;
static BITMAPINFOHEADER *bmih;
static BITMAPINFO bmi;
static HBITMAP hbmp;
static char *pData;
TTT0=Time();
// переход к указному кадру
pMediaSeeking->SetPositions(&Nf,AM_SEEKING_AbsolutePositioning,NULL,AM_SEEKING_NoPositioning);
// pVideoFrameStep->Step(Nf, NULL);if (init==1) // при инициализации
{
wrkDC = GetDC(0); // ...\dx9sdk\Samples\C++\DirectShow\Editing\TransViewer
//wrkDC = CreateCompatibleDC(NULL);
pBasicVideo->GetCurrentImage(&lBufSize,NULL); // определяем размер памяти под кадр
pDIB=new BYTE[lBufSize]; // резерв памяти под кадр
mFRAME->PixelFormat = pf24bit;
mFRAME->Width =Xf;
mFRAME->Height=Yf;
}
//----------------------------------------------------------------------------
TTT1=Time();
DateTimeToString(As[0], "ss.zzzz", TTT1-TTT0);
pBasicVideo->GetCurrentImage(&lBufSize,(long*)pDIB); // получаю данные кадра
TTT0=Time();
DateTimeToString(As[1], "ss.zzzz", TTT0-TTT1);
pData = (char*)(int)pDIB+sizeof(BITMAPINFOHEADER); // адрес первого пикселя
TTT1=Time();
DateTimeToString(As[2], "ss.zzzz", TTT1-TTT0);
bmih = (BITMAPINFOHEADER*)pDIB; // махинации для дальнейшей конвертации изображения
ZeroMemory(&bmi, sizeof(BITMAPINFO));
CopyMemory(&(bmi.bmiHeader), bmih, sizeof(BITMAPINFOHEADER));
hbmp=CreateDIBitmap(wrkDC,bmih,CBM_INIT,pDIB,(PBITMAPINFO)&bmi,DIB_RGB_COLORS);
mFRAME->Handle=hbmp;
TTT0=Time();
DateTimeToString(As[3], "ss.zzzz", TTT0-TTT1);
Form1->Caption="Frame "+IntToStr(Nf)+" "+As[0]+" "+As[1]+" "+As[2]+" "+As[3];
Form1->Image1->Canvas->Draw(0,0, mFRAME); // отрисовка картинки.
Form1->Image1->Refresh();
}
Re: [DirectShow] Произвольный доступ к кадру AVI
От:
Аноним
Дата:
24.05.12 16:56
Оценка:
Здравствуйте, VAstronom, Вы писали:
Нужно использовать SampleGrabber фильтр. В нем изображение еще не ушло в видео память и потому более быстро доступно через интерфейс IMediaSample. Либо можно написать свой фильтр который будет делать то что надо, благо примеров в СДК полно.
VA>Стоит задача анализа содержимого видео файла. Я больше инженер, чем программист, но пришлось разбираться с DirectShow, поэтому сильно не VA>пинайте… Использую Builder6.
VA>Необходимо последовательно (иногда пропуская) сменять кадры, затем извлекать их содержимое для анализа. Соответственно на экране VA>исходный кадр отображаться не должен, т.к. там будет измененная картинка. VA>Для перехода к кадру использую связку VA> pMediaSeeking->SetPositions(&Nf,AM_SEEKING_AbsolutePositioning,NULL,AM_SEEKING_NoPositioning); VA> pBasicVideo->GetCurrentImage(&lBufSize,(long*)pDIB); VA>но GetCurrentImage жутко тормозит и на получение одного кадра уходит пол секунды времени!
VA>Подскажите пожалуйста, как можно повысить быстродействие?
VA>Выкладываю весь блок кода, возможно ошибка в самом подходе к решению задачи?
VA>
А>Нужно использовать SampleGrabber фильтр. В нем изображение еще не ушло в видео память и потому более быстро доступно через интерфейс IMediaSample. Либо можно написать свой фильтр который будет делать то что надо, благо примеров в СДК полно.
Ну не выходит, каменный цветок…
Если снять в коде комментарий с «НульРендер», это все вообще не работает.
Как советует MSDN, объявил SampleGrabber
задал
hr = pGrabber->SetOneShot(TRUE);
hr = pGrabber->SetBufferSamples(TRUE);
однако
pGrabber->GetCurrentBuffer( &lBufSize,NULL);
данных не получает.
hr = pControl->Run();
hr = pEvent->WaitForCompletion(INFINITE, &evCode);
менять кадры толком не хочет.
Фактически, своими силами так ничего и не добился.
В ГрафЕдит формируется такая схема
Avi_file -> AVI Splitter -> SampleGpabber->LAV Video Decoder -> VideoRender
Причем AVI Splitter и LAV Video Decoder подключаются без меня.
Может быть, код не работает т.к. я сам подключение пинов не делаю и SampleGpabber «в автомате» получается перед декомпрессией потока? Соответственно буфер пуст…
Допустим, подключение AVI Splitter в исходниках встречается, но как задать этот «LAV Video Decoder»? И нужно ли их вообще задавать в ручную?
Очень раздражает, что примеры из Dxsdk вроде как есть, а в С++ Builder их толком не перетянешь. Компилятор ругается на чего-то в черт знает каком заголовочном файле, не говоря уж о том, что он не понимает функции в самом примере. Может я не те либы ему подсунул? Брал отсюда: http://clootie.narod.ru/cbuilder/index.html#DX_CBuilder_Libs_90 (CBuilder_DX92_libs.zip Clootie_DX92_dlls.zip)
В системе стоит ХР + DX9.0c. Остаток чего то взял из c:\Dxsdk\lib\
Народ, у кого какие идеи? Уже голова кипит от этого бардака. Вроде бы простая цепочка. Я бы сказал «классика», а столько головной боли!
Re[3]: [DirectShow] Произвольный доступ к кадру AVI
Нужно построить цепочку
source file -> AVI Splitter -> Decoder -> Sample Grabber -> Null Renderer
OneShot не надо true, включаем в граббере буферизацию сэмплов, переводим граф в состояние паузы, делаем seek на нужную позицию, берем сохраненный в граббере кадр.
Вручную все добавлять необязательно. Можно добавить File Source Async, сказать ему открыть нужный файл, затем добавить сэмпл граббер и сказать ему в каком формате принимать данные (например, RGB24 или RGB32 или YV12), затем через RenderStream соединяем эти два фильтра, промежуточные добавятся сами.
Re[5]: [DirectShow] Произвольный доступ к кадру AVI
DM>Нужно построить цепочку DM>source file -> AVI Splitter -> Decoder -> Sample Grabber -> Null Renderer
DM>OneShot не надо true, включаем в граббере буферизацию сэмплов, переводим граф в состояние паузы, делаем seek на нужную позицию, берем сохраненный в граббере кадр.
DM>Вручную все добавлять необязательно. Можно добавить File Source Async, сказать ему открыть нужный файл, затем добавить сэмпл граббер и сказать ему в каком формате принимать данные (например, RGB24 или RGB32 или YV12), затем через RenderStream соединяем эти два фильтра, промежуточные добавятся сами.
-------------------------------
Спасибо за совет, попытался его реализовать, но пока безуспешно.
Во первых, пишет: RenderStream is not a member of IGraphBuilder. Что то здесь я не понял.
Ну ладно, вручную соединил выход pSourceFile и вход pGrabberF.
Странно то, что если код из функции ConnectFilters(pSourceFile, pGrabberF, &pPinOut);
вставить в функцию, он не работает (в коде закомментировано). При закрытии программы пишет ошибку обращения к памяти, хотя сама функция почти со всем тем же работает…
Теперь у меня стопорится на
hr = pGrabber->GetCurrentBuffer(&lBufSize,(long*)pDIB); Выдает пустой (белый) кадр, хотя lBufSize при инициализации примерно похоже на кадр (типа 1456886).
hr = pBasicVideo->GetCurrentImage(&lBufSize,NULL); которое раньше работало уже при попытке запроса возвращает ошибку.
Народ, извините меня, но очень хочется собрать рабочий шаблон подобной задачи (кадровый доступ). Уже столько времени угроблено, что жалко бросать нерешенную проблему. Может я чего то не понимаю, подскажите пожалуйста, какой и куда код надо вставить, что бы это заработало. Может есть другой способ перескакивать между кадрами, и зря я уцепился в эти pMediaSeeking и pBasicVideo?
------------------------------- DM> делаем seek
это как? pMediaSeeking->SetPositions(...)?
DM> включаем в граббере буферизацию сэмплов
это как? OneShot(false)?
DM>берем сохраненный в граббере кадр.
так: pGrabber->GetCurrentBuffer(...)?
Re[4]: [DirectShow] Произвольный доступ к кадру AVI
VA>Спасибо за совет, попытался его реализовать, но пока безуспешно.
Видимо, все же успешно... Кадр через ScanLine выдернул из byte *pDIB.
Меня это устраивает т.к. дальше идет анализ данных, но для большинства
это не универсально. При этом код для отображения из моего примера:
Достать кадр — это инструмент. Возможно, при решении целевой задачи
с этим кодом (доступ к кадру) возникнут новые грабли,
которых пока не видно. Время покажет...