-
Notifications
You must be signed in to change notification settings - Fork 2
/
EDT_directx_dll.cpp
executable file
·558 lines (476 loc) · 18.9 KB
/
EDT_directx_dll.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
//----------------------------------------------------------------------------
// This program reads from a camera attached to an EDT video input board
// and implements a Microsoft DirectShow source filter that produces what
// it receives as a video source that can be linked to by DirectShow applications.
// This is a DLL that must be registered using the regsvr32.exe program.
// It is based on the simple_take application supplied by EDT, and uses
// the same library files that the "take" example application does.
//
// XXX Add in time stamps that keep track of how long had passed since the first
// frame was gotten and puts that into the record. This enables us to write
// AVI files with the data in them and include time stamps.
// XXX Its settings and ranges assume that the pixels are actually 8-bit values.
//#define DEBUG_ON
#include "edtinc.h"
#include <streams.h>
//--------------------------------------------------------------------------
// Version string for this program
static const char *g_version_string = "01.00";
//--------------------------------------------------------------------------
// Helper function to pop up dialog boxes so we can debug things.
static const void dialog(const char *s1, const char *s2)
{
TCHAR szMsg[MAX_PATH + MAX_PATH + 100];
wsprintf(szMsg, TEXT(s2));
OutputDebugString(szMsg);
MessageBox(NULL, szMsg, TEXT(s1), MB_ICONINFORMATION | MB_OK);
}
//----------------------------------------------------------------------------
// This is the class definition and implementation for the DirectShow source
// filter. It is based on Chapter 12 of the "Programming Microsoft Directshow for
// Digital Video and Television" book by Mark Pesce. I did not break the seal
// on the CD sample code, so this code should be distributable in source code
// form. I wrote this based on the text description. The GUID and CLSID stuff
// comes from around page 240-246 (generating new GUIDs is done as described on
// page 202-203, using the uuidgen.exe program with the -s argument). All of
// the real work happens in the CEDTPushPin
// class, which is defined first. The Source that is constructed and used is
// defined second. The methods for each class follow both class definitions.
static const GUID CLSID_EDTSource = { /* 3afc9237-cfb9-447e-9d15-9cc7762a3093 */
0x3afc9237,
0xcfb9,
0x447e,
{0x9d, 0x15, 0x9c, 0xc7, 0x76, 0x2a, 0x30, 0x93}
};
static const GUID IID_IEDTSource = { /* eac20d82-e2f2-4b11-9187-909d5650eb01 */
0xeac20d82,
0xe2f2,
0x4b11,
{0x91, 0x87, 0x90, 0x9d, 0x56, 0x50, 0xeb, 0x01}
};
class CEDTPushPin : public CSourceStream {
public:
CEDTPushPin(HRESULT *phr, CSource *pFilter);
~CEDTPushPin();
// Don't support quality control
STDMETHODIMP Notify(IBaseFilter *, Quality) { return E_FAIL; };
protected:
REFERENCE_TIME m_rtStartTime; //< When the first sample was grabbed.
BOOL m_bDiscontinuity; //< If true, set discontinuity flag
// These are CSourceStream methods that we have to override.
HRESULT GetMediaType(CMediaType *pMediaType);
HRESULT CheckMediaType(const CMediaType *pMediaType);
HRESULT DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pRequest);
// This method is called repeatedly by the streaming video thread
// as long as the filter is running or paused.
HRESULT FillBuffer(IMediaSample *pSample);
PdvDev *m_pdv_p; //< Pointer to the device that we're using
int m_unit; //< Which unit is being used
int m_channel; //< Which channel is being used
int m_nRows; //< Number of rows
int m_nCols; //< Number of columns
int m_num_timeouts; //< Total number of timeouts found so far
int m_numbufs; //< Number of buffers to use
};
class CEDTSource : public CSource {
public:
static CUnknown *WINAPI CreateInstance(IUnknown *pUnk, HRESULT *phr);
protected:
// The constructor needs to be private to force the application to
// use CreateInstance to construct one.
CEDTSource(IUnknown *pUnk, HRESULT *phr);
};
//----------------------------------------------------------------------------
// CEDTPushPin Methods
CEDTPushPin::CEDTPushPin(HRESULT *phr, CSource *pFilter)
: CSourceStream(NAME("CEDTPushPin"), phr, pFilter, L"Out")
, m_rtStartTime(0)
, m_pdv_p(NULL)
, m_unit(0)
, m_channel(0)
, m_nRows(100) //< Default size in case we don't get a connection
, m_nCols(100) //< Default size in case we don't get a connection
, m_num_timeouts(0)
, m_numbufs(4)
{
#ifdef DEBUG_ON
dialog("CEDTPushPin::CEDTPushPin","Entering constructor");
#endif
#ifdef DEBUG_ON
dialog("CEDTPushPin::CEDTPushPin", "opening device");
#endif
// Open the device and read its parameters.
char devname[128];
strcpy(devname, EDT_INTERFACE);
if ((m_pdv_p = pdv_open_channel(devname, m_unit, m_channel)) == NULL)
{
#ifdef DEBUG_ON
dialog("CEDTPushPin::CEDTPushPin()","Can't open device");
#endif
return;
}
m_nCols = pdv_get_width(m_pdv_p);
m_nRows = pdv_get_height(m_pdv_p);
int depth = pdv_get_depth(m_pdv_p);
if (depth != 8) {
#ifdef DEBUG_ON
dialog("CEDTPushPin::CEDTPushPin()","Depth not 8 bits per pixel");
#endif
pdv_close(m_pdv_p);
m_pdv_p = NULL;
return;
}
char *cameratype = pdv_get_cameratype(m_pdv_p);
#ifdef DEBUG_ON
dialog("CEDTPushPin::CEDTPushPin() got camera type",cameratype);
#endif
// Allocate four buffers for optimal pdv ring buffer pipeline (reduce if memory is at a premium)
pdv_multibuf(m_pdv_p, m_numbufs);
// Start only one image here.
pdv_start_image(m_pdv_p);
// Make sure that we can read a frame from the device, or else timeout and
// close it again. Record the time at which we took this first frame, converted
// into Microsoft DirectShow units.
u_int timevec[2]; //< Seconds and nanoseconds
u_char *image_p = pdv_wait_image_timed(m_pdv_p, timevec);
int timeouts = pdv_timeouts(m_pdv_p);
if (timeouts != 0) {
#ifdef DEBUG_ON
dialog("CEDTPushPin::CEDTPushPin()","Timed out reading from camera");
#endif
pdv_close(m_pdv_p);
m_pdv_p = NULL;
return;
}
// Convert nanoseconds and seconds to 100-nanosecond steps and store as start time
m_rtStartTime = ((LONGLONG)(timevec[1])) / 100 + ((LONGLONG)(timevec[0])) * 10000000L;
// Start images coming our way.
pdv_start_images(m_pdv_p, m_numbufs);
}
CEDTPushPin::~CEDTPushPin()
{
#ifdef DEBUG_ON
dialog("CEDTPushPin::CEDTPushPin", "Exiting\n");
#endif
if (m_pdv_p) { pdv_close(m_pdv_p); };
}
// Media type negotiation handlers
HRESULT CEDTPushPin::GetMediaType(CMediaType *pMediaType)
{
CheckPointer(pMediaType, E_POINTER);
CAutoLock cAutoLock(m_pFilter->pStateLock());
// We want 24-bit (RGB) video. We'd really like 8-bit luminance, but that doesn't seem to be a choice!
AM_MEDIA_TYPE mt;
ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE));
mt.majortype = MEDIATYPE_Video; // Ask for video media producers
mt.subtype = MEDIASUBTYPE_RGB24; // Ask for 24-bit RGB
*pMediaType = mt;
#ifdef DEBUG_ON
{ char size[1024];
sprintf(size, "Size %d by %d", m_nCols, m_nRows);
dialog("CEDTPushPin::GetMediaType", size);
}
#endif
ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE));
mt.majortype = MEDIATYPE_Video; // Ask for video media producers
mt.subtype = MEDIASUBTYPE_RGB24; // Ask for 24-bit RGB
mt.pbFormat = (BYTE*)CoTaskMemAlloc(sizeof(VIDEOINFOHEADER));
VIDEOINFOHEADER *pVideoHeader = (VIDEOINFOHEADER*)mt.pbFormat;
ZeroMemory(pVideoHeader, sizeof(VIDEOINFOHEADER));
pVideoHeader->bmiHeader.biBitCount = 24;
pVideoHeader->bmiHeader.biWidth = m_nCols;
pVideoHeader->bmiHeader.biHeight = m_nRows;
pVideoHeader->bmiHeader.biPlanes = 1;
pVideoHeader->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pVideoHeader->bmiHeader.biSizeImage = DIBSIZE(pVideoHeader->bmiHeader);
// Set the format type and size.
mt.formattype = FORMAT_VideoInfo;
mt.cbFormat = sizeof(VIDEOINFOHEADER);
// Set the sample size.
mt.bFixedSizeSamples = TRUE;
mt.lSampleSize = DIBSIZE(pVideoHeader->bmiHeader);
// Point the parameter at the media type and then return.
*pMediaType = mt;
return S_OK;
}
HRESULT CEDTPushPin::CheckMediaType(const CMediaType *pMediaType)
{
CAutoLock cAutoLock(m_pFilter->pStateLock());
// Make sure it is a video type, in particular 24-bit RGB
if (pMediaType->majortype != MEDIATYPE_Video) { return E_FAIL; }
if (pMediaType->subtype != MEDIASUBTYPE_RGB24) { return E_FAIL; }
// Make sure we have a video info header.
if ((pMediaType->formattype != FORMAT_VideoInfo) ||
(pMediaType->cbFormat < sizeof(VIDEOINFOHEADER)) ||
(pMediaType->pbFormat == NULL)) {
return E_FAIL;
}
// Make sure all of the parameters match what we expect
VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)pMediaType->pbFormat;
if (!IsRectEmpty(&(pVih->rcSource))) {
// XXX We might at some point like to implement source rectangles,
// to reduce video bandwidth. For now, we don't support this.
return E_FAIL;
}
/* XXX Check was here for valid frame rate, but how would we know what it should be?
if (pVih->AvgTimePerFrame != m_rtFrameLength) { return E_FAIL; }*/
return S_OK;
}
HRESULT CEDTPushPin::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pRequest)
{
CAutoLock cAutoLock(m_pFilter->pStateLock());
HRESULT hr;
VIDEOINFO *pvi = (VIDEOINFO*)(m_mt.Format());
if (pvi == NULL) {
return E_FAIL;
}
// Require at least one buffer
if (pRequest->cBuffers == 0) { pRequest->cBuffers = 1; }
// It needs to be at least large enough to hold an image
if ((long)pvi->bmiHeader.biSizeImage > pRequest->cbBuffer) {
pRequest->cbBuffer = pvi->bmiHeader.biSizeImage;
}
// Ask for these
ALLOCATOR_PROPERTIES props;
hr = pAlloc->SetProperties(pRequest, &props);
if (FAILED(hr)) { return hr; }
// Make sure it is as big as we need
if (props.cbBuffer < pRequest->cbBuffer) { return E_FAIL; }
return S_OK;
}
HRESULT CEDTPushPin::FillBuffer(IMediaSample *pSample)
{
#ifdef DEBUG_ON
dialog("CEDTPushPin::FillBuffer", "Entered");
#endif
//---------------------------------------------------------------------
// See page 278 and following to see the example code for this.
// Get a pointer to the place where the data is to go.
BYTE *pData;
pSample->GetPointer(&pData);
// If the video format has changed, set the new one. We'll check later to make sure
// it matches what we expect it to be.
CMediaType *pmt;
if (pSample->GetMediaType((AM_MEDIA_TYPE**)&pmt) == S_OK) {
SetMediaType(pmt);
DeleteMediaType(pmt);
}
// Make sure the video format is what we expect it to be.
if (m_mt.formattype != FORMAT_VideoInfo) {
#ifdef DEBUG_ON
dialog("CVRPNPushPin::handle_region_change", "Not video format!\n");
#endif
return VFW_E_INVALIDMEDIATYPE;
}
if (m_mt.cbFormat < sizeof(VIDEOINFOHEADER)) {
#ifdef DEBUG_ON
dialog("CVRPNPushPin::handle_region_change", "CVRPNSource::handle_region_change(): Header too small!\n");
#endif
return VFW_E_RUNTIME_ERROR;
}
// Number of rows and columns. This is different if we are using a target
// rectangle (rcTarget) than if we are not.
VIDEOINFOHEADER *pVih = reinterpret_cast<VIDEOINFOHEADER*>(m_mt.pbFormat);
int width, height;
if (IsRectEmpty(&pVih->rcTarget)) {
width = pVih->bmiHeader.biWidth;
height = pVih->bmiHeader.biHeight;
} else {
width = pVih->rcTarget.right;
height = pVih->bmiHeader.biHeight;
#ifdef DEBUG_ON
dialog("CVRPNPushPin::handle_region_change", "XXX Warning: may not work correctly with target rectangle\n");
#endif
}
if ( (width != m_nCols) || (height != m_nRows) ) {
#ifdef DEBUG_ON
dialog("CVRPNPushPin::handle_region_change", "Wrong size image!\n");
#endif
return VFW_E_INVALID_RECT;
}
// Make sure that the image is not compressed and that we have 8 bits
// per pixel.
if (pVih->bmiHeader.biCompression != BI_RGB) {
#ifdef DEBUG_ON
dialog("CVRPNPushPin::handle_region_change", "Compression not RGB\n");
switch (pVih->bmiHeader.biCompression) {
case BI_RLE8:
dialog("CVRPNPushPin::handle_region_change", " (It is BI_RLE8)\n");
break;
case BI_RLE4:
dialog("CVRPNPushPin::handle_region_change", " (It is BI_RLE4)\n");
case BI_BITFIELDS:
dialog("CVRPNPushPin::handle_region_change", " (It is BI_BITFIELDS)\n");
break;
default:
dialog("CVRPNPushPin::handle_region_change", " (Unknown compression type)\n");
}
#endif
return VFW_E_INVALIDMEDIATYPE;
}
int BytesPerPixel = pVih->bmiHeader.biBitCount / 8;
if (BytesPerPixel != 3) {
#ifdef DEBUG_ON
dialog("CVRPNPushPin::handle_region_change", "Not 3 bytes per pixel\n");
#endif
return VFW_E_INVALIDMEDIATYPE;
}
// A negative height indicates that the images are stored non-inverted in Y
// Not sure what to do with images that have negative height -- need to
// read the book some more to find out.
if (height < 0) {
#ifdef DEBUG_ON
dialog("CVRPNPushPin::handle_region_change", "Height is negative (internal error)\n");
#endif
return VFW_E_INVALIDMEDIATYPE;
}
//--------------------------------------------------------------------------------
// Get a video sample, or time out trying. If we don't have a device open,
// then fill the image with black.
if (m_pdv_p == NULL) {
// Find the stride to take when moving from one row of video to the
// next. This is rounded up to the nearest DWORD.
DWORD stride = (width * BytesPerPixel + 3) & ~3;
// Copy the image from our internal buffer into the buffer we were passed in,
// copying one row at a time and then skipping the appropriate distance for
// the internal buffer and for the buffer we're writing to
BYTE *outbuf = pData;
DWORD outstep = stride;
int r, c, offset;
for (r = 0; r < height; r++) {
for (c = 0; c < width; c++) {
offset = 3*c; //< First byte in the pixel in this line in output image
outbuf[0 + offset] = 0; //< Blue channel
outbuf[1 + offset] = 0; //< Green channel
outbuf[2 + offset] = 0; //< Red channel
}
outbuf += outstep;
}
// Have the media play out at one sample per second. This increments the
// m_rtStartTime value by a second's worth of 100-nm ticks and sets the
// new time each frame.
m_rtStartTime += 10000000L;
pSample->SetTime(&m_rtStartTime, &m_rtStartTime);
} else {
u_int timevec[2]; //< Seconds and nanoseconds
u_char *image_p = pdv_wait_image_timed(m_pdv_p, timevec);
// Convert nanoseconds and seconds to 100-nanosecond steps and store as start time.
// Then subtract the time when the first sample was taken to put us into units of
// time since the stream began. Set the sample's time to this value. No idea how
// far into the future the stop time is, so set the start time for both.
REFERENCE_TIME rtNowTime = ((LONGLONG)(timevec[1])) / 100 + ((LONGLONG)(timevec[0])) * 10000000L;
REFERENCE_TIME rtStreamTime = rtNowTime - m_rtStartTime;
pSample->SetTime(&rtStreamTime, &rtStreamTime);
// Start a new video sample coming our way for next time, keeping the hopper full
pdv_start_image(m_pdv_p);
// If we timed out, then restart the images coming our way
int timeouts = pdv_timeouts(m_pdv_p);
if (timeouts > m_num_timeouts)
{
/*
* pdv_timeout_cleanup helps recover gracefully after a timeout,
* particularly if multiple buffers were prestarted
*/
if (m_numbufs > 1)
{
int pending = pdv_timeout_cleanup(m_pdv_p);
pdv_start_images(m_pdv_p, pending);
}
m_num_timeouts = timeouts;
}
//--------------------------------------------------------------------------------
// Copy the image into the outgoing buffer.
// XXX This will copy unknown stuff into the buffer if there was
// a timeout and we didn't get any data...
// Find the stride to take when moving from one row of video to the
// next. This is rounded up to the nearest DWORD.
DWORD stride = (width * BytesPerPixel + 3) & ~3;
// Copy the image from our internal buffer into the buffer we were passed in,
// copying one row at a time and then skipping the appropriate distance for
// the internal buffer and for the buffer we're writing to
BYTE *mybuf = image_p;
DWORD mystep = width;
BYTE *outbuf = pData;
DWORD outstep = stride;
int r, c, offset;
for (r = 0; r < height; r++) {
for (c = 0; c < width; c++) {
offset = 3*c; //< First byte in the pixel in this line in output image
outbuf[0 + offset] = mybuf[c]; //< Blue channel
outbuf[1 + offset] = mybuf[c]; //< Green channel
outbuf[2 + offset] = mybuf[c]; //< Red channel
}
mybuf += mystep;
outbuf += outstep;
}
}
return S_OK;
};
//----------------------------------------------------------------------------
// CEDTSource Methods
CEDTSource::CEDTSource(IUnknown *pUnk, HRESULT *phr)
: CSource(NAME("EDTSource"), pUnk, CLSID_EDTSource)
{
// Create the output pin, which magically adds itself to the pin array
CEDTPushPin *pPin = new CEDTPushPin(phr, this);
if (pPin == NULL) { *phr = E_OUTOFMEMORY; }
}
CUnknown *WINAPI CEDTSource::CreateInstance(IUnknown *pUnk, HRESULT *phr)
{
CEDTSource *pNewFilter = new CEDTSource(pUnk, phr);
if (pNewFilter == NULL) { *phr = E_OUTOFMEMORY; }
return pNewFilter;
}
// EDT_directx_dll.cpp : Defines the entry point for the DLL application.
//
//----------------------------------------------------------------------------
// DirectShow specification of the filter. Described starting on page 261 of
// the DirectShow book.
const AMOVIESETUP_PIN psudEDTSourcePins[] =
{ { L"Output" // strName
, FALSE // bRendered
, TRUE // bOutput
, FALSE // bZero
, FALSE // bMany
, &CLSID_NULL // clsConnectsToFilter
, L"" // strConnectsToPin
, 0 // nTypes
, NULL // lpTypes
}};
const AMOVIESETUP_FILTER sudEDTSource =
{ &CLSID_EDTSource // clsID
, L"EDTSource" // strName
, MERIT_DO_NOT_USE // dwMerit prevents auto-connect from using
, 1 // nPins
, psudEDTSourcePins // lpPin
};
// List of class IDs and creator functions for the class factory. This
// provides the link between the OLE entry point in the DLL and an object
// being created. The class factory will call the static CreateInstance.
const unsigned NUM_TEMPLATES = 1;
CFactoryTemplate g_Templates[NUM_TEMPLATES] =
{
L"EDTSource", // Name
&CLSID_EDTSource, // CLSID
CEDTSource::CreateInstance, // Method to create an instance of MyComponent
NULL, // Initialization function
&sudEDTSource // Set-up information (for filters)
};
int g_cTemplates = NUM_TEMPLATES;
//----------------------------------------------------------------------------
// Add entry points to Windows DLLs to provide system-wide objects.
// There is also a separate VPRNSource.def module that defines the
// DLL entry points for our source filter.
STDAPI DllRegisterServer()
{
return AMovieDllRegisterServer2(TRUE);
}
STDAPI DllUnregisterServer()
{
return AMovieDllRegisterServer2(FALSE);
}
extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID);
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
{
return DllEntryPoint((HINSTANCE)(hModule), dwReason, lpReserved);
}