c++ builder: convert video to png-snapshots with directshow - directshow

Thanks to your help I was able to search for the right words to use directshow a bit better.
I found a tutorial how to use the SampleGrabber-object here:
http://msdn.microsoft.com/en-us/library/windows/desktop/dd407288%28v=vs.85%29.aspx
I could implement it and modified it a bit so it isn't just saving the first frame, but every Frame to a PNG. For that I use corona.
However, I just guessed something around and don't quite know which buffers are containing my data and in which form.
So, I have basically 3 questions:
Am I using SavePNG right? the resulting Images are upside-down!
Can I replace the BaseFilter for the video with one that is connected to a camera?
Contains pBuffer my Imagedata so I can get rgb-byte-informations by simply type pBuffer[123]?
I'm using embarcadero's C++-Builder (XE2 16).
Here is the code I found at the website, a bit modified (error-handling removed for better view. after each hr=... there is a Failed-check):
void __fastcall TForm1::btn_kameraClick(TObject *Sender)
{
HRESULT hr = S_OK;
IGraphBuilder *pGraph = NULL;
IMediaControl *pControl = NULL;
IMediaEventEx *pEvent = NULL;
IBaseFilter *pGrabberF = NULL;
ISampleGrabber *pGrabber = NULL;
IBaseFilter *pSourceF = NULL;
IEnumPins *pEnum = NULL;
IPin *pPin = NULL;
IBaseFilter *pNullF = NULL;
BYTE *pBuffer = NULL;
hr = CoCreateInstance(CLSID_FilterGraph, NULL,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&pGraph));
hr = pGraph->QueryInterface(IID_PPV_ARGS(&pControl));
hr = pGraph->QueryInterface(IID_PPV_ARGS(&pEvent));
hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&pGrabberF));
hr = pGraph->AddFilter(pGrabberF, L"Sample Grabber");
hr = pGrabberF->QueryInterface(IID_PPV_ARGS(&pGrabber));
AM_MEDIA_TYPE mt;
ZeroMemory(&mt, sizeof(mt));
mt.majortype = MEDIATYPE_Video;
mt.subtype = MEDIASUBTYPE_RGB24;
hr = pGrabber->SetMediaType(&mt);
hr = pGraph->AddSourceFilter(L"C:/Users/Julian/Desktop/homogenität/1,1x_2,7y.mpg", L"Source", &pSourceF);
hr = pSourceF->EnumPins(&pEnum);
while (S_OK == pEnum->Next(1, &pPin, NULL))
{
hr = ConnectFilters(pGraph, pPin, pGrabberF);
SafeRelease(&pPin);
if (SUCCEEDED(hr))break;
}
hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&pNullF));
hr = pGraph->AddFilter(pNullF, L"Null Filter");
hr = ConnectFilters(pGraph, pGrabberF, pNullF);
hr = pGrabber->SetOneShot(TRUE);
hr = pGrabber->SetBufferSamples(TRUE);
long evCode=0;
long cbBuffer=0;
hr = pControl->Run();
hr = pEvent->WaitForCompletion(INFINITE, &evCode);
hr = pGrabber->GetCurrentBuffer(&cbBuffer, NULL);
pBuffer = (BYTE*)CoTaskMemAlloc(cbBuffer);
hr = pGrabber->GetConnectedMediaType(&mt);
CComQIPtr< IMediaSeeking, &IID_IMediaSeeking > pSeeking( pGraph );
// for(int i=0;i<10;i++){
bool hui=true;int i=0;
while(hui){
REFERENCE_TIME Start = i * UNITS;
hr = pSeeking->SetPositions( &Start, AM_SEEKING_AbsolutePositioning,NULL, AM_SEEKING_NoPositioning );
// Sleep(10);
hr = pEvent->WaitForCompletion(INFINITE,&evCode);
if(hr!=0)hui=false;
hr = pGrabber->GetCurrentBuffer(&cbBuffer, (long*)pBuffer);
if ((mt.formattype == FORMAT_VideoInfo) &&(mt.cbFormat >= sizeof(VIDEOINFOHEADER)) &&(mt.pbFormat != NULL))
{
VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)mt.pbFormat;
// hr = WriteBitmap(("hui"+(String)i+".bmp").c_str(), &pVih->bmiHeader, mt.cbFormat - SIZE_PREHEADER, pBuffer, cbBuffer);
hr = SavePNG(i,pBuffer, pVih->bmiHeader.biWidth, pVih->bmiHeader.biHeight);
}
else hr = VFW_E_INVALIDMEDIATYPE;
i++;
}
FreeMediaType(mt);
done:
CoTaskMemFree(pBuffer);
SafeRelease(&pPin);
SafeRelease(&pEnum);
SafeRelease(&pNullF);
SafeRelease(&pSourceF);
SafeRelease(&pGrabber);
SafeRelease(&pGrabberF);
SafeRelease(&pControl);
SafeRelease(&pEvent);
SafeRelease(&pGraph);
}
bool SavePNG(int i, Byte* m_pImageData,long m_Width,long m_Height)
{
// Make sure there is image data
if (!m_pImageData)
return false;
stringstream FilePath;
FilePath << "hui"<< i<<".png";
// Create a corona image
corona::Image* pImage = corona::CreateImage(m_Width, m_Height, corona::PF_R8G8B8, m_pImageData);
// Make sure the image was created
if (!pImage)
return false;
// Save the image to a PNG file
corona::SaveImage(FilePath.str().c_str(), corona::FF_PNG, pImage);
// Delete the corona image
delete pImage;
// Nothing went wrong
return true;
}
I hope I have done nothing horribly wrong... I really tried to research everything^^
Does somebody knows about my 3 questions above?
I you found something really wrong here, I would also appreciate for you to tell me, so I can fix and improve.
Regards,
Julian

Now when you have a video frame in 24-bit RGB format, all you need is to compress to PNG. You have choices here:
libpng
GDI+
WIC
Possibly, C++ bulder has native classes to cover PNG as well.
P.S. DirectShow API you are using is not DirectX, it is a part of Windows core SDK.

Related

Switching sprites of a object in different rooms

I am creating a character selection which changes the sprites of the main character.
To do this I have an arrow object which the user clicks to change the sprite of the main character.
global.Mario = true;
global.PrincessPeach = false;
global.Luigi = false;
global.Bowser = false;
if (mouse_check_button_pressed(mb_left)) {
if (global.Mario = true) {
Mario = false;
PrincessPeach= true;
}
if (global.PrincessPeach= true) {
PrincessPeach = true;
Mario = false;
}
if (global.Luigi = true) {
Luigi = false;
Bowser = true;
}
if (global.Bowser = true) {
Bowser = false;
Mario = true;
}
}
Then, on my main character I have a created an event executing code similar to the following:
if (global.Mario = true) {
sprite_index = Mario_NotJumping;
}
if (global.Luigi = true) {
sprite_index = Luigispr;
}
However, when I run my game to test it out, I get the following error:
FATAL ERROR in
action number 1
of Create Event
for object Mario_selection:
Push :: Execution Error - Variable Get -5.Mario(100000, -1)
at gml_Object_Mario_selection_Create_0 (line 1) - if (global.Mario = true) {
The object Mario_selection has the exact create event and same code as the main character. To display changes to the user.
If anyone could help me out, I am fairly new to GameMaker so I have a feeling I'm just misunderstanding global variables.
It looks like the var global.Mario, has not been declared. In the arrow objects create event put,
global.Mario = true;
global.PrincessPeach = false;
global.Luigi = false;
global.Bowser = false;
If i were you, i would save the chosen "image" of the user in 1 simple variable.
Add a few constants;
PLAYER_MARIO = 0;
PLAYER_PEACH = 1;
PLAYER_LUIGI = 2;
PLAYER_BOWSER = 3;
Then you can use those constants and save them in a global variable;
global.player_image = PLAYER_LUIGI;
Then you can use those in your objects like so;
switch (global.player_image) {
case PLAYER_MARIO:
sprite_index = Mario_NotJumping;
break;
case PLAYER_PEACH:
sprite_index = Peach_NotJumping;
break;
case PLAYER_LUIGI:
sprite_index = Luigi_NotJumping;
break;
case PLAYER_BOWSER:
sprite_index = Bowser_NotJumping;
break;
}
The specific error you were getting was because the global variable was not defined at the time of use.
A better method by the way, would be something like this - saving all sprite handles into specific variables, then using those.
Define some constants for readability;
P_LEFT = 0;
P_RIGHT = 1;
P_JUMP = 2;
Then whenever a player switches its character;
//When the user switches to peach;
global.player_sprite[P_LEFT] = spr_Peach_Left;
global.player_sprite[P_RIGHT] = spr_Peach_Right;
global.player_sprite[P_JUMPING] = spr_Peach_Jumping;
And overwrite;
//When the user switches to Luigi;
global.player_sprite[P_LEFT] = spr_Luigi_Left;
global.player_sprite[P_RIGHT] = spr_Luigi_Right;
global.player_sprite[P_JUMPING] = spr_Luigi_Jumping;
Then you can code all code like this;
if (jumping) {
sprite_index = global.player_sprite[P_JUMPING];
} else {
sprite_index = global.player_sprite[P_LEFT];
}
Do you understand what i mean? This way you won't need the switch statements everywhere, and just store the chosen sprites in 1 single array at the beginning of the game.
You can then code the game without having to check what player image the player chose.
Also, always define global variables at the beginning of the game. Global variables will always exist in the game, in every room, at any given moment.
You should use var mario=true; instead.

Changing resolution using media foundation under win 7

I am writing an app on top media foundation under win 7, I use IMFMediaSource to query the cameras interfaces to get frames and other properties. its weird but I cant find a way to change resolution. it seems that if I used IMFCaptureSource i could use SetCurrentDeviceMediaType to change resolution but its only supported in Windows 8. so we cant change resolution under win 7 using media foundation?? is there a way to use direct show with IMFMediaSource to change resolution??
if so, can anyone help with some code sample?
thanks!
ok, so I found out eventually. Iam using IMFSourceReader to get samples from the MFMediaSource, so after configurating the SourceReader you can iterate the native media types that the camera supports like this :
HRESULT nativeTypeErrorCode = S_OK;
DWORD count = 0;
UINT32 streamIndex = 0;
UINT32 requiredWidth = 1600;
UINT32 requiredheight = 900;
while (nativeTypeErrorCode == S_OK)
{
IMFMediaType * nativeType = NULL;
nativeTypeErrorCode = m_pReader->GetNativeMediaType(streamIndex, count, &nativeType);
if(nativeTypeErrorCode != S_OK) continue;
// get the media type
GUID nativeGuid = {0};
hr = nativeType->GetGUID(MF_MT_SUBTYPE, &nativeGuid);
if (FAILED(hr)) return hr;
UINT32 width, height;
hr = ::MFGetAttributeSize(nativeType, MF_MT_FRAME_SIZE, &width, &height);
if (FAILED(hr)) return hr;
if(nativeGuid == <my type guid> && width == requiredWidth && height == requiredheight)
{
// found native config, set it
hr = m_pReader->SetCurrentMediaType(streamIndex, NULL, nativeType);
if (FAILED(hr)) return hr;
break;
}
SafeRelease(&nativeType);
count++;
}
this means that Iam not creating a new media type with the resolution I require, I get the native media type with the configuration I need, and I set it on the SourceReader.
hope it will help the future media foundation traveler... :)
You can query directshow interface from IMediaSource which can change resolution.
for ex:for Camera control properties I do like this.
IAMCameraControl* m_pCameraControl = NULL;
HRESULT hr = S_OK;
hr = pMediaSource->QueryInterface(IID_PPV_ARGS(&m_pCameraControl));
if (m_pCameraControl == NULL)
{
return E_FAIL;
}
In the same way in your case I am not sure about the interface but I guess it will be in following way.
IAMStreamConfig * m_pStreamConfig = NULL;
HRESULT hr = S_OK;
hr = pMediaSource->QueryInterface(IID_PPV_ARGS(&m_pStreamConfig ));
if (m_pCameraControl == NULL)
{
return E_FAIL;
}

How to get data from directshow filter output pin?

I have direct show filter which takes an input and process it and give the result to outputpin.
I want to write this filter output data to a file...And i want to do it in its filter class.So i want to get the output pin buffer data.
Shortly how to reach final data of outputpin in its filter? How can i do it?
Not: The output pin is derived from CBaseOutputPin.This is an open source filter it "magically" :-) put wright data to its output pin which i can not figure out how yet...
Update:
Here is the siutuation:
Media Source ----> GFilter ----> FileWriter
I have source code of GFilter... I have no source code of FileWriter...What i want to make is make GFilter write its own data...I debug GFilter get some insight how its transform data but my attemp to write this data result with wrong data... So i deceide for now how to simply get data at its output pin...
Update[2]
In Filter outputpin somwhere the filter writer pass the file writer pin to IStreamPtr variable...Everthing seems to written to a variable m_pIStream which is type of [IStreamPtr]
GFilterOutput::CompleteConnect(IPin *pReceivePin)
{
// make sure that this is the file writer, supporting
// IStream, or we will not be able to write out the metadata
// at stop time
// m_pIStream is IStreamPtr type
m_pIStream = pReceivePin;
if (m_pIStream == NULL)
{
return E_NOINTERFACE;
}
return CBaseOutputPin::CompleteConnect(pReceivePin);
}
...
GFilterOutput::Replace(LONGLONG pos, const BYTE* pBuffer, long cBytes)
{
//OutputDebugStringA("DEBUG: Now at MuxOutput Replace");
// all media content is written when the graph is running,
// using IMemInputPin. On stop (during our stop, but after the
// file writer has stopped), we switch to IStream for the metadata.
// The in-memory index is updated after a successful call to this function, so
// any data not written on completion of Stop will not be in the index.
CAutoLock lock(&m_csWrite);
HRESULT hr = S_OK;
if (m_bUseIStream)
{
IStreamPtr pStream = GetConnected();
if (m_pIStream == NULL)
{
hr = E_NOINTERFACE;
} else {
LARGE_INTEGER liTo;
liTo.QuadPart = pos;
ULARGE_INTEGER uliUnused;
hr = m_pIStream->Seek(liTo, STREAM_SEEK_SET, &uliUnused);
if (SUCCEEDED(hr))
{
ULONG cActual;
hr = m_pIStream->Write(pBuffer, cBytes, &cActual);
if (SUCCEEDED(hr) && ((long)cActual != cBytes))
{
hr = E_FAIL;
}
}
}
} else {
// where the buffer boundaries lie is not important in this
// case, so break writes up into the buffers.
while (cBytes && (hr == S_OK))
{
IMediaSamplePtr pSample;
hr = GetDeliveryBuffer(&pSample, NULL, NULL, 0);
if (SUCCEEDED(hr))
{
long cThis = min(pSample->GetSize(), cBytes);
BYTE* pDest;
pSample->GetPointer(&pDest);
CopyMemory(pDest, pBuffer, cThis);
pSample->SetActualDataLength(cThis);
// time stamps indicate file position in bytes
LONGLONG tStart = pos;
LONGLONG tEnd = pos + cThis;
pSample->SetTime(&tStart, &tEnd);
hr = Deliver(pSample);
if (SUCCEEDED(hr))
{
pBuffer += cThis;
cBytes -= cThis;
pos += cThis;
}
}
}
}
return hr;
}
You have full source code, step it through with debugger until you reach the point where your filter calls IPin::Receive of the peer downstream filter, update/override code there and you have full control as for writing data into file etc.

Negotiating an allocator between Directshow filters fails

I'm developing a custom Directshow source filter to provide decompressed video data to a rendering filter. I've used the PushSource sample provided by the Directshow SDK as a basis for my filter. I'm attempting to connect this to a VideoMixingRenderer9 filter.
When creating the graph I'm calling ConnectDirect():
HRESULT hr = mp_graph_builder->ConnectDirect(OutPin, InPin, &mediaType);
but during this call, calling SetProperties on the downstream filters allocator (in DecideBufferSize()), fails with D3DERR_INVALIDCALL (0x8876086c):
ALLOCATOR_PROPERTIES actual;
memset(&actual,0,sizeof(actual));
hr = pAlloc->SetProperties(pRequest, &actual);
If I let it try to use my allocator (the one provided by CBaseOutputPin) when setting the allocator on the downstream filter, this fails with E_FAIL (in CBaseOutputPin::DecideAllocator)
hr = pPin->NotifyAllocator(*ppAlloc, FALSE);
Any help would be much appreciated!
Thanks.
EDIT:
This is the media type provided by GetMediaType
VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER*)pMediaType->AllocFormatBuffer(sizeof(VIDEOINFOHEADER));
if (pvi == 0)
return(E_OUTOFMEMORY);
ZeroMemory(pvi, pMediaType->cbFormat);
pvi->AvgTimePerFrame = m_rtFrameLength;
pMediaType->formattype = FORMAT_VideoInfo;
pMediaType->majortype = MEDIATYPE_Video;
pMediaType->subtype = MEDIASUBTYPE_RGB24;
pMediaType->bTemporalCompression = FALSE;
pMediaType->bFixedSizeSamples = TRUE;
pMediaType->formattype = FORMAT_VideoInfo;
pvi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pvi->bmiHeader.biWidth = (640 / 128 + 1) * 128;
pvi->bmiHeader.biHeight = -480; // negative so top down..
pvi->bmiHeader.biPlanes = 1;
pvi->bmiHeader.biBitCount = 24;
pvi->bmiHeader.biCompression = NULL; // ok if rgb else use MAKEFOURCC(...)
pvi->bmiHeader.biSizeImage = GetBitmapSize(&pvi->bmiHeader);
pvi->bmiHeader.biClrImportant = 0;
pvi->bmiHeader.biClrUsed = 0; //Use max colour depth
pvi->bmiHeader.biXPelsPerMeter = 0;
pvi->bmiHeader.biYPelsPerMeter = 0;
SetRectEmpty(&(pvi->rcSource));
SetRectEmpty(&(pvi->rcTarget));
pvi->rcSource.bottom = 480;
pvi->rcSource.right = 640;
pvi->rcTarget.bottom = 480;
pvi->rcTarget.right = 640;
pMediaType->SetType(&MEDIATYPE_Video);
pMediaType->SetFormatType(&FORMAT_VideoInfo);
pMediaType->SetTemporalCompression(FALSE);
const GUID SubTypeGUID = GetBitmapSubtype(&pvi->bmiHeader);
pMediaType->SetSubtype(&SubTypeGUID);
pMediaType->SetSampleSize(pvi->bmiHeader.biSizeImage);
and DecideBufferSize where pAlloc->SetProperties is called
HRESULT CPushPinBitmap::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pRequest) {
HRESULT hr;
CAutoLock cAutoLock(CBasePin::m_pLock);
CheckPointer(pAlloc, E_POINTER);
CheckPointer(pRequest, E_POINTER);
if (pRequest->cBuffers == 0) {
pRequest->cBuffers = 2;
}
pRequest->cbBuffer = 480 * ( (640 / 128 + 1) * 128 ) * 3;
ALLOCATOR_PROPERTIES actual;
memset(&actual,0,sizeof(actual));
hr = pAlloc->SetProperties(pRequest, &actual);
if (FAILED(hr)) {
return hr;
}
if (actual.cbBuffer < pRequest->cbBuffer) {
return E_FAIL;
}
return S_OK;
}
The constants are only temporary!
There is no way you can use your own allocator with VMR/EVR filters. They just insist on their own, which in turn is backed on DirectDraw/Direct3D surfaces.
To connect directly to VMR/EVR filters you need a different strategy. The allocator is always theirs. You need to support extended strides. See Handling Format Changes from the Video Renderer.

Replace multiple different images on one PDF template page with itext (itextsharp)

We have an ASP.NET application that users use to generate certain reports. So far we had one PDF template that had one image on it, and we would just replace that image with our programatically generated one (graph).
We have used code from this site for that:http://blog.rubypdf.com/2007/12/12/how-to-replace-images-in-a-pdf/
Problem now is that we have two different images on one PDF page, and the code from link above selects both images on one page and replaces them all at once with our generated image.
Does anyone have any idea how to replace multiple different images on one page with itext?
Thanks
Ugh. First, let me rewrite some of that source.
PdfReader pdf = new PdfReader("in.pdf");
PdfStamper stp = new PdfStamper(pdf, new FileOutputStream("c:\\out.pdf"));
PdfWriter writer = stp.getWriter();
Image img = Image.getInstance("image.png");
PdfDictionary pg = pdf.getPageN(1);
PdfDictionary res = pg.getAsDict.get(PdfName.RESOURCES);
PdfDictionary xobj = res.getAsDict(PdfName.XOBJECT);
if (xobj != null) {
for (Iterator<PdfName> it = xobj.getKeys().iterator(); it.hasNext(); ) {
PdfObject obj = xobj.get(it.next());
if (obj.isIndirect()) {
PdfDictionary tg = (PdfDictionary)PdfReader.getPdfObject(obj);
PdfName type = tg.getAsName(PdfName.SUBTYPE));
if (PdfName.IMAGE.equals(type)) {
PdfReader.killIndirect(obj);
Image maskImage = img.getImageMask();
if (maskImage != null)
writer.addDirectImageSimple(maskImage);
writer.addDirectImageSimple(img, (PRIndirectReference)obj);
break;
}
}
}
}
Whew. the getAs functions can save you quite a bit of knuckle-grease and make your code much clearer.
Now. You need to be able to differentiate between the various images. If you're willing to hard-code things, you could find out what the resource names are and go that route:
String imageResName[] = {"Img1", "Img2" ... };
Image img[] = {Image.getInstance("foo.png"), Image.getInstance("bar.png"), ... };
for (int i = 0; i < imageResName.length; ++i) {
PdfName curKey = new PdfName(imageResName[i]);
PdfIndirectReference ref = xobj.getAsIndirect(curKey);
PdfReader.killIndirect( ref );
Image maskImage = img[i].getImageMask();
if (maskImage != null) {
writer.addDirectImageSimple(maskImage);
}
writer.addDirectImageSimple(img[i], (PRIndirectReference)ref);
}
If you're not willing to go with hardcoded resource names (and no one would fault you, quite the opposite, particularly when the order they appear (and thus the number on the end) depends on their order in a hash map... [shudder]), you may be able to differentiate based on image width and height.
//keep the original for loop, stepping through resource names
if (PdfName.IMAGE.equals(type)) {
float width = tg.getAsNumber(PdfName.WIDTH).floatValue();
float height = tg.getAsNumber(PdfName.HEIGHT).floatValue();
Image img = getImageFromDimensions(width, height);
Image maskImage = img.getImageMask();
...
}
Just a note that sometimes the image will be nested in a form, so it is wise to make a function that will be called recursively.
Something like this:
public void StartHere()
{
PdfReader pdf = new PdfReader("in.pdf");
PdfStamper stp = new PdfStamper(pdf, new FileOutputStream("c:\\out.pdf"));
PdfWriter writer = stp.getWriter();
Image img = Image.getInstance("image.png");
PdfDictionary pg = pdf.getPageN(1);
replaceImage(pg, writer,img);
}
private void replaceImage(PdfDictionary pg, PdfWriter writer,Image img)
{
PdfDictionary res = pg.getAsDict.get(PdfName.RESOURCES);
PdfDictionary xobj = res.getAsDict(PdfName.XOBJECT);
if (xobj != null) {
for (Iterator<PdfName> it = xobj.getKeys().iterator(); it.hasNext(); ) {
PdfObject obj = xobj.get(it.next());
if (obj.isIndirect()) {
PdfDictionary tg = (PdfDictionary)PdfReader.getPdfObject(obj);
PdfName type = tg.getAsName(PdfName.SUBTYPE));
if (PdfName.IMAGE.equals(type))
{
PdfReader.killIndirect(obj);
Image maskImage = img.getImageMask();
if (maskImage != null)
writer.addDirectImageSimple(maskImage);
writer.addDirectImageSimple(img, (PRIndirectReference)obj);
break;
}
else if(PdfName.FORM.equals(type))
{
replaceImage(tg, writer,img);
}
}
}
}

Resources