Subversion Repositories spk

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
11 cycrow 1
//-----------------------------------------------------------------------------
2
// File: DShowTextures.cpp
3
//
4
// Desc: DirectShow sample code - adds support for DirectShow videos playing
5
//       on a DirectX 9.0 texture surface. Turns the D3D texture tutorial into
6
//       a recreation of the VideoTex sample from previous versions of DirectX.
7
//
8
// Copyright (c) Microsoft Corporation.  All rights reserved.
9
//-----------------------------------------------------------------------------
10
 
11
#include "Textures.h"
12
#include "DShowTextures.h"
13
#include "DXUtil.h"
14
 
15
//-----------------------------------------------------------------------------
16
// Global Constants
17
//-----------------------------------------------------------------------------
18
 
19
// Define this if you want to render only the video component with no audio
20
//
21
//   #define NO_AUDIO_RENDERER
22
 
23
// An application can advertise the existence of its filter graph
24
// by registering the graph with a global Running Object Table (ROT).
25
// The GraphEdit application can detect and remotely view the running
26
// filter graph, allowing you to 'spy' on the graph with GraphEdit.
27
//
28
// To enable registration in this sample, define REGISTER_FILTERGRAPH.
29
//
30
#define REGISTER_FILTERGRAPH
31
 
32
LPDIRECT3DDEVICE9       g_pd3dDevice; // Our rendering device
33
LPDIRECT3DTEXTURE9      g_pTexture;   // Our texture
34
 
35
// File filter for OpenFile dialog
36
#define FILE_FILTER_TEXT \
37
    TEXT("Video Files (*.avi; *.qt; *.mov; *.mpg; *.mpeg; *.m1v)\0*.avi; *.qt; *.mov; *.mpg; *.mpeg; *.m1v\0")\
38
    TEXT("Audio files (*.wav; *.mpa; *.mp2; *.mp3; *.au; *.aif; *.aiff; *.snd)\0*.wav; *.mpa; *.mp2; *.mp3; *.au; *.aif; *.aiff; *.snd\0")\
39
    TEXT("MIDI Files (*.mid, *.midi, *.rmi)\0*.mid; *.midi; *.rmi\0") \
40
    TEXT("Image Files (*.jpg, *.bmp, *.gif, *.tga)\0*.jpg; *.bmp; *.gif; *.tga\0") \
41
    TEXT("All Files (*.*)\0*.*;\0\0")
42
 
43
//-----------------------------------------------------------------------------
44
// Global DirectShow pointers
45
//-----------------------------------------------------------------------------
46
CComPtr<IGraphBuilder>  g_pGB;          // GraphBuilder
47
CComPtr<IMediaControl>  g_pMC;          // Media Control
48
CComPtr<IMediaPosition> g_pMP;          // Media Position
49
CComPtr<IMediaEvent>    g_pME;          // Media Event
50
CComPtr<IBaseFilter>    g_pRenderer;    // our custom renderer
51
 
52
D3DFORMAT               g_TextureFormat; // Texture format
53
 
54
//extern HWND g_hWnd;
55
 
56
TCHAR g_achCopy[]     = TEXT("Bitwise copy of the sample");
57
TCHAR g_achOffscr[]   = TEXT("Using offscreen surfaces and StretchCopy()");
58
TCHAR g_achDynTextr[] = TEXT("Using Dynamic Textures");
59
TCHAR* g_pachRenderMethod = NULL;
60
 
61
 
62
 
63
//-----------------------------------------------------------------------------
64
// InitDShowTextureRenderer : Create DirectShow filter graph and run the graph
65
//-----------------------------------------------------------------------------
66
HRESULT InitDShowTextureRenderer( const char *szVideoFilename, LPDIRECT3DTEXTURE9 texture, LPDIRECT3DDEVICE9 device )
67
{
68
	g_pTexture = texture;
69
	g_pd3dDevice = device;
70
 
71
    HRESULT hr = S_OK;
72
 
73
    CComPtr<IBaseFilter>    pFSrc;          // Source Filter
74
    CComPtr<IPin>           pFSrcPinOut;    // Source Filter Output Pin
75
    CTextureRenderer        *pCTR=0;        // DirectShow Texture renderer
76
 
77
    // Create the filter graph
78
    if (FAILED(g_pGB.CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC)))
79
        return E_FAIL;
80
 
81
#ifdef REGISTER_FILTERGRAPH
82
    // Register the graph in the Running Object Table (for debug purposes)
83
    AddToROT(g_pGB);
84
#endif
85
 
86
    // Create the Texture Renderer object
87
    pCTR = new CTextureRenderer(NULL, &hr);
88
    if (FAILED(hr) || !pCTR)
89
    {
90
        delete pCTR;
91
        Msg(TEXT("Could not create texture renderer object!  hr=0x%x"), hr);
92
        return E_FAIL;
93
    }
94
 
95
    // Get a pointer to the IBaseFilter on the TextureRenderer, add it to graph
96
    g_pRenderer = pCTR;
97
    if (FAILED(hr = g_pGB->AddFilter(g_pRenderer, L"TEXTURERENDERER")))
98
    {
99
        Msg(TEXT("Could not add renderer filter to graph!  hr=0x%x"), hr);
100
        return hr;
101
    }
102
 
103
    // Determine the file to load based on windows directory
104
    // Use the standard win32 API to do this.
105
    TCHAR strFileName[MAX_PATH] = {0};
106
    WCHAR wFileName[MAX_PATH];
107
 
108
	lstrcpyn( strFileName, szVideoFilename, MAX_PATH - 1 );
109
 
110
	/*
111
    if (! GetClipFileName(strFileName))
112
    {
113
        DWORD dwDlgErr = CommDlgExtendedError();
114
 
115
        // Don't show output if user cancelled the selection (no dlg error)
116
        if (dwDlgErr)
117
        {
118
            Msg(TEXT("GetClipFileName Failed! Error=0x%x\r\n"), GetLastError());
119
        }
120
        return E_FAIL;
121
    }
122
*/
123
    strFileName[MAX_PATH-1] = 0;  // NULL-terminate
124
    wFileName[MAX_PATH-1] = 0;    // NULL-terminate
125
 
126
    USES_CONVERSION;
127
    (void)StringCchCopyW(wFileName, NUMELMS(wFileName), T2W(strFileName));
128
 
129
	return hr;
130
 
131
    // Add the source filter to the graph.
132
    hr = g_pGB->AddSourceFilter (wFileName, L"SOURCE", &pFSrc);
133
 
134
    // If the media file was not found, inform the user.
135
    if (hr == VFW_E_NOT_FOUND)
136
    {
137
        Msg(TEXT("Could not add source filter to graph!  (hr==VFW_E_NOT_FOUND)\r\n\r\n")
138
            TEXT("This sample reads a media file from your windows directory.\r\n")
139
            TEXT("This file is missing from this machine."));
140
        return hr;
141
    }
142
    else if(FAILED(hr))
143
    {
144
        Msg(TEXT("Could not add source filter to graph!  hr=0x%x"), hr);
145
        return hr;
146
    }
147
 
148
    if (FAILED(hr = pFSrc->FindPin(L"Output", &pFSrcPinOut)))
149
    {
150
        Msg(TEXT("Could not find output pin!  hr=0x%x"), hr);
151
        return hr;
152
    }
153
 
154
    // Render the source filter's output pin.  The Filter Graph Manager
155
    // will connect the video stream to the loaded CTextureRenderer
156
    // and will load and connect an audio renderer (if needed).
157
/*
158
    if (FAILED(hr = g_pGB->Render(pFSrcPinOut)))
159
    {
160
        Msg(TEXT("Could not render source output pin!  hr=0x%x"), hr);
161
        return hr;
162
    }
163
*/
164
    // Get the graph's media control, event & position interfaces
165
    g_pGB.QueryInterface(&g_pMC);
166
    g_pGB.QueryInterface(&g_pMP);
167
    g_pGB.QueryInterface(&g_pME);
168
 
169
	/*
170
    // Start the graph running;
171
    if (FAILED(hr = g_pMC->Run()))
172
    {
173
        Msg(TEXT("Could not run the DirectShow graph!  hr=0x%x"), hr);
174
        return hr;
175
    }
176
*/
177
 
178
    return S_OK;
179
}
180
 
181
 
182
//-----------------------------------------------------------------------------
183
// CheckMovieStatus: If the movie has ended, rewind to beginning
184
//-----------------------------------------------------------------------------
185
void CheckMovieStatus(void)
186
{
187
    long lEventCode;
188
    LONG_PTR lParam1, lParam2;
189
    HRESULT hr;
190
 
191
    if (!g_pME)
192
        return;
193
 
194
    // Check for completion events
195
    hr = g_pME->GetEvent(&lEventCode, &lParam1, &lParam2, 0);
196
    if (SUCCEEDED(hr))
197
    {
198
        // If we have reached the end of the media file, reset to beginning
199
        if (EC_COMPLETE == lEventCode)
200
        {
201
            hr = g_pMP->put_CurrentPosition(0);
202
        }
203
 
204
        // Free any memory associated with this event
205
        hr = g_pME->FreeEventParams(lEventCode, lParam1, lParam2);
206
    }
207
}
208
 
209
 
210
//-----------------------------------------------------------------------------
211
// CleanupDShow
212
//-----------------------------------------------------------------------------
213
void CleanupDShow(void)
214
{
215
#ifdef REGISTER_FILTERGRAPH
216
    // Pull graph from Running Object Table (Debug)
217
    RemoveFromROT();
218
#endif
219
 
220
    // Shut down the graph
221
    if (!(!g_pMC)) g_pMC->Stop();
222
 
223
    if (!(!g_pMC)) g_pMC.Release();
224
    if (!(!g_pME)) g_pME.Release();
225
    if (!(!g_pMP)) g_pMP.Release();
226
    if (!(!g_pGB)) g_pGB.Release();
227
    if (!(!g_pRenderer)) g_pRenderer.Release();
228
}
229
 
230
 
231
//-----------------------------------------------------------------------------
232
// CTextureRenderer constructor
233
//-----------------------------------------------------------------------------
234
CTextureRenderer::CTextureRenderer( LPUNKNOWN pUnk, HRESULT *phr )
235
                                  : CBaseVideoRenderer(__uuidof(CLSID_TextureRenderer),
236
                                    NAME("Texture Renderer"), pUnk, phr),
237
                                    m_bUseDynamicTextures(FALSE)
238
{
239
    // Store and AddRef the texture for our use.
240
    ASSERT(phr);
241
    if (phr)
242
        *phr = S_OK;
243
}
244
 
245
 
246
//-----------------------------------------------------------------------------
247
// CTextureRenderer destructor
248
//-----------------------------------------------------------------------------
249
CTextureRenderer::~CTextureRenderer()
250
{
251
    // Do nothing
252
}
253
 
254
 
255
//-----------------------------------------------------------------------------
256
// CheckMediaType: This method forces the graph to give us an R8G8B8 video
257
// type, making our copy to texture memory trivial.
258
//-----------------------------------------------------------------------------
259
HRESULT CTextureRenderer::CheckMediaType(const CMediaType *pmt)
260
{
261
    HRESULT   hr = E_FAIL;
262
    VIDEOINFO *pvi=0;
263
 
264
    CheckPointer(pmt,E_POINTER);
265
 
266
    // Reject the connection if this is not a video type
267
    if( *pmt->FormatType() != FORMAT_VideoInfo ) {
268
        return E_INVALIDARG;
269
    }
270
 
271
    // Only accept RGB24 video
272
    pvi = (VIDEOINFO *)pmt->Format();
273
 
274
    if(IsEqualGUID( *pmt->Type(),    MEDIATYPE_Video)  &&
275
       IsEqualGUID( *pmt->Subtype(), MEDIASUBTYPE_RGB24))
276
    {
277
        hr = S_OK;
278
    }
279
 
280
    return hr;
281
}
282
 
283
//-----------------------------------------------------------------------------
284
// SetMediaType: Graph connection has been made.
285
//-----------------------------------------------------------------------------
286
HRESULT CTextureRenderer::SetMediaType(const CMediaType *pmt)
287
{
288
    HRESULT hr;
289
 
290
    UINT uintWidth = 2;
291
    UINT uintHeight = 2;
292
 
293
    // Retrive the size of this media type
294
    D3DCAPS9 caps;
295
    VIDEOINFO *pviBmp;                      // Bitmap info header
296
    pviBmp = (VIDEOINFO *)pmt->Format();
297
 
298
    m_lVidWidth  = pviBmp->bmiHeader.biWidth;
299
    m_lVidHeight = abs(pviBmp->bmiHeader.biHeight);
300
    m_lVidPitch  = (m_lVidWidth * 3 + 3) & ~(3); // We are forcing RGB24
301
 
302
    // here let's check if we can use dynamic textures
303
    ZeroMemory( &caps, sizeof(D3DCAPS9));
304
    hr = g_pd3dDevice->GetDeviceCaps( &caps );
305
    if( caps.Caps2 & D3DCAPS2_DYNAMICTEXTURES )
306
    {
307
        m_bUseDynamicTextures = TRUE;
308
    }
309
 
310
    if( caps.TextureCaps & D3DPTEXTURECAPS_POW2 )
311
    {
312
        while( (LONG)uintWidth < m_lVidWidth )
313
        {
314
            uintWidth = uintWidth << 1;
315
        }
316
        while( (LONG)uintHeight < m_lVidHeight )
317
        {
318
            uintHeight = uintHeight << 1;
319
        }
320
    }
321
    else
322
    {
323
        uintWidth = m_lVidWidth;
324
        uintHeight = m_lVidHeight;
325
    }
326
 
327
    // Create the texture that maps to this media type
328
    hr = E_UNEXPECTED;
329
    if( m_bUseDynamicTextures )
330
    {
331
        hr = g_pd3dDevice->CreateTexture(uintWidth, uintHeight, 1, D3DUSAGE_DYNAMIC,
332
                                         D3DFMT_X8R8G8B8,D3DPOOL_DEFAULT,
333
                                         &g_pTexture, NULL);
334
        g_pachRenderMethod = g_achDynTextr;
335
        if( FAILED(hr))
336
        {
337
            m_bUseDynamicTextures = FALSE;
338
        }
339
    }
340
    if( FALSE == m_bUseDynamicTextures )
341
    {
342
        hr = g_pd3dDevice->CreateTexture(uintWidth, uintHeight, 1, 0,
343
                                         D3DFMT_X8R8G8B8,D3DPOOL_MANAGED,
344
                                         &g_pTexture, NULL);
345
        g_pachRenderMethod = g_achCopy;
346
    }
347
    if( FAILED(hr))
348
    {
349
        Msg(TEXT("Could not create the D3DX texture!  hr=0x%x"), hr);
350
        return hr;
351
    }
352
 
353
    // CreateTexture can silently change the parameters on us
354
    D3DSURFACE_DESC ddsd;
355
    ZeroMemory(&ddsd, sizeof(ddsd));
356
 
357
    if ( FAILED( hr = g_pTexture->GetLevelDesc( 0, &ddsd ) ) ) {
358
        Msg(TEXT("Could not get level Description of D3DX texture! hr = 0x%x"), hr);
359
        return hr;
360
    }
361
 
362
 
363
    CComPtr<IDirect3DSurface9> pSurf;
364
 
365
    if (SUCCEEDED(hr = g_pTexture->GetSurfaceLevel(0, &pSurf)))
366
        pSurf->GetDesc(&ddsd);
367
 
368
    // Save format info
369
    g_TextureFormat = ddsd.Format;
370
 
371
    if (g_TextureFormat != D3DFMT_X8R8G8B8 &&
372
        g_TextureFormat != D3DFMT_A1R5G5B5) {
373
        Msg(TEXT("Texture is format we can't handle! Format = 0x%x"), g_TextureFormat);
374
        return VFW_E_TYPE_NOT_ACCEPTED;
375
    }
376
 
377
    return S_OK;
378
}
379
 
380
 
381
//-----------------------------------------------------------------------------
382
// DoRenderSample: A sample has been delivered. Copy it to the texture.
383
//-----------------------------------------------------------------------------
384
HRESULT CTextureRenderer::DoRenderSample( IMediaSample * pSample )
385
{
386
    BYTE  *pBmpBuffer, *pTxtBuffer; // Bitmap buffer, texture buffer
387
    LONG  lTxtPitch;                // Pitch of bitmap, texture
388
 
389
    BYTE  * pbS = NULL;
390
    DWORD * pdwS = NULL;
391
    DWORD * pdwD = NULL;
392
    UINT row, col, dwordWidth;
393
 
394
    CheckPointer(pSample,E_POINTER);
395
    CheckPointer(g_pTexture,E_UNEXPECTED);
396
 
397
    // Get the video bitmap buffer
398
    pSample->GetPointer( &pBmpBuffer );
399
 
400
    // Lock the Texture
401
    D3DLOCKED_RECT d3dlr;
402
    if( m_bUseDynamicTextures )
403
    {
404
        if( FAILED(g_pTexture->LockRect(0, &d3dlr, 0, D3DLOCK_DISCARD)))
405
            return E_FAIL;
406
    }
407
    else
408
    {
409
        if (FAILED(g_pTexture->LockRect(0, &d3dlr, 0, 0)))
410
            return E_FAIL;
411
    }
412
    // Get the texture buffer & pitch
413
    pTxtBuffer = static_cast<byte *>(d3dlr.pBits);
414
    lTxtPitch = d3dlr.Pitch;
415
 
416
 
417
    // Copy the bits
418
 
419
    if (g_TextureFormat == D3DFMT_X8R8G8B8)
420
    {
421
        // Instead of copying data bytewise, we use DWORD alignment here.
422
        // We also unroll loop by copying 4 pixels at once.
423
        //
424
        // original BYTE array is [b0][g0][r0][b1][g1][r1][b2][g2][r2][b3][g3][r3]
425
        //
426
        // aligned DWORD array is     [b1 r0 g0 b0][g2 b2 r1 g1][r3 g3 b3 r2]
427
        //
428
        // We want to transform it to [ff r0 g0 b0][ff r1 g1 b1][ff r2 g2 b2][ff r3 b3 g3]
429
        // below, bitwise operations do exactly this.
430
 
431
        dwordWidth = m_lVidWidth / 4; // aligned width of the row, in DWORDS
432
                                      // (pixel by 3 bytes over sizeof(DWORD))
433
 
434
        for( row = 0; row< (UINT)m_lVidHeight; row++)
435
        {
436
            pdwS = ( DWORD*)pBmpBuffer;
437
            pdwD = ( DWORD*)pTxtBuffer;
438
 
439
            for( col = 0; col < dwordWidth; col ++ )
440
            {
441
                pdwD[0] =  pdwS[0] | 0xFF000000;
442
                pdwD[1] = ((pdwS[1]<<8)  | 0xFF000000) | (pdwS[0]>>24);
443
                pdwD[2] = ((pdwS[2]<<16) | 0xFF000000) | (pdwS[1]>>16);
444
                pdwD[3] = 0xFF000000 | (pdwS[2]>>8);
445
                pdwD +=4;
446
                pdwS +=3;
447
            }
448
 
449
            // we might have remaining (misaligned) bytes here
450
            pbS = (BYTE*) pdwS;
451
            for( col = 0; col < (UINT)m_lVidWidth % 4; col++)
452
            {
453
                *pdwD = 0xFF000000     |
454
                        (pbS[2] << 16) |
455
                        (pbS[1] <<  8) |
456
                        (pbS[0]);
457
                pdwD++;
458
                pbS += 3;
459
            }
460
 
461
            pBmpBuffer  += m_lVidPitch;
462
            pTxtBuffer += lTxtPitch;
463
        }// for rows
464
    }
465
 
466
    if (g_TextureFormat == D3DFMT_A1R5G5B5)
467
    {
468
        for(int y = 0; y < m_lVidHeight; y++ )
469
        {
470
            BYTE *pBmpBufferOld = pBmpBuffer;
471
            BYTE *pTxtBufferOld = pTxtBuffer;
472
 
473
            for (int x = 0; x < m_lVidWidth; x++)
474
            {
475
                *(WORD *)pTxtBuffer = (WORD)
476
                    (0x8000 +
477
                    ((pBmpBuffer[2] & 0xF8) << 7) +
478
                    ((pBmpBuffer[1] & 0xF8) << 2) +
479
                    (pBmpBuffer[0] >> 3));
480
 
481
                pTxtBuffer += 2;
482
                pBmpBuffer += 3;
483
            }
484
 
485
            pBmpBuffer = pBmpBufferOld + m_lVidPitch;
486
            pTxtBuffer = pTxtBufferOld + lTxtPitch;
487
        }
488
    }
489
 
490
    // Unlock the Texture
491
    if (FAILED(g_pTexture->UnlockRect(0)))
492
        return E_FAIL;
493
 
494
    return S_OK;
495
}
496
 
497
 
498
#ifdef REGISTER_FILTERGRAPH
499
 
500
//-----------------------------------------------------------------------------
501
// Running Object Table functions: Used to debug. By registering the graph
502
// in the running object table, GraphEdit is able to connect to the running
503
// graph. This code should be removed before the application is shipped in
504
// order to avoid third parties from spying on your graph.
505
//-----------------------------------------------------------------------------
506
DWORD dwROTReg = 0xfedcba98;
507
 
508
HRESULT AddToROT(IUnknown *pUnkGraph)
509
{
510
    IMoniker * pmk;
511
    IRunningObjectTable *pROT;
512
    if (FAILED(GetRunningObjectTable(0, &pROT))) {
513
        return E_FAIL;
514
    }
515
 
516
    WCHAR wsz[256];
517
    (void)StringCchPrintfW(wsz, NUMELMS(wsz),L"FilterGraph %08x  pid %08x\0", (DWORD_PTR) 0, GetCurrentProcessId());
518
 
519
    HRESULT hr = CreateItemMoniker(L"!", wsz, &pmk);
520
    if (SUCCEEDED(hr))
521
    {
522
        // Use the ROTFLAGS_REGISTRATIONKEEPSALIVE to ensure a strong reference
523
        // to the object.  Using this flag will cause the object to remain
524
        // registered until it is explicitly revoked with the Revoke() method.
525
        //
526
        // Not using this flag means that if GraphEdit remotely connects
527
        // to this graph and then GraphEdit exits, this object registration
528
        // will be deleted, causing future attempts by GraphEdit to fail until
529
        // this application is restarted or until the graph is registered again.
530
        hr = pROT->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, pUnkGraph,
531
                            pmk, &dwROTReg);
532
        pmk->Release();
533
    }
534
 
535
    pROT->Release();
536
    return hr;
537
}
538
 
539
 
540
void RemoveFromROT(void)
541
{
542
    IRunningObjectTable *pirot=0;
543
 
544
    if (SUCCEEDED(GetRunningObjectTable(0, &pirot)))
545
    {
546
        pirot->Revoke(dwROTReg);
547
        pirot->Release();
548
    }
549
}
550
 
551
#endif
552
 
553
 
554
//-----------------------------------------------------------------------------
555
// Msg: Display an error message box if needed
556
//-----------------------------------------------------------------------------
557
void Msg(TCHAR *szFormat, ...)
558
{
559
    TCHAR szBuffer[1024];  // Large buffer for long filenames or URLs
560
    const size_t NUMCHARS = sizeof(szBuffer) / sizeof(szBuffer[0]);
561
    const int LASTCHAR = NUMCHARS - 1;
562
 
563
    // Format the input string
564
    va_list pArgs;
565
    va_start(pArgs, szFormat);
566
 
567
    // Use a bounded buffer size to prevent buffer overruns.  Limit count to
568
    // character size minus one to allow for a NULL terminating character.
569
    (void)StringCchVPrintf(szBuffer, NUMCHARS - 1, szFormat, pArgs);
570
    va_end(pArgs);
571
 
572
    // Ensure that the formatted string is NULL-terminated
573
    szBuffer[LASTCHAR] = TEXT('\0');
574
 
575
    MessageBox(NULL, szBuffer, TEXT("DirectShow Texture3D9 Sample"),
576
               MB_OK | MB_ICONERROR);
577
}
578
 
579
 
580
/*
581
 
582
BOOL GetClipFileName(LPTSTR szName)
583
{
584
    OPENFILENAME ofn;
585
    ZeroMemory(&ofn, sizeof(ofn));
586
 
587
 
588
    // Reset filename
589
    *szName = 0;
590
 
591
    // Fill in standard structure fields
592
    ofn.lStructSize       = sizeof(OPENFILENAME);
593
//    ofn.hwndOwner         = g_hWnd;
594
    ofn.lpstrFilter       = FILE_FILTER_TEXT;
595
    ofn.lpstrCustomFilter = NULL;
596
    ofn.nFilterIndex      = 1;
597
    ofn.lpstrFile         = szName;
598
    ofn.nMaxFile          = MAX_PATH;
599
    ofn.lpstrTitle        = TEXT("Open Media File...\0");
600
    ofn.lpstrFileTitle    = NULL;
601
    ofn.lpstrDefExt       = TEXT("*\0");
602
    ofn.Flags             = OFN_FILEMUSTEXIST | OFN_READONLY | OFN_PATHMUSTEXIST;
603
 
604
 
605
    // Create the standard file open dialog and return its result
606
    return GetOpenFileName((LPOPENFILENAME)&ofn);
607
}
608
 
609
*/