1 | /******************************************************************************
|
---|
2 | * SAGE - Scalable Adaptive Graphics Environment
|
---|
3 | *
|
---|
4 | * Copyright (C) 2004 Electronic Visualization Laboratory,
|
---|
5 | * University of Illinois at Chicago
|
---|
6 | *
|
---|
7 | * All rights reserved.
|
---|
8 | *
|
---|
9 | * Redistribution and use in source and binary forms, with or without
|
---|
10 | * modification, are permitted provided that the following conditions are met:
|
---|
11 | *
|
---|
12 | * * Redistributions of source code must retain the above copyright
|
---|
13 | * notice, this list of conditions and the following disclaimer.
|
---|
14 | * * Redistributions in binary form must reproduce the above
|
---|
15 | * copyright notice, this list of conditions and the following disclaimer
|
---|
16 | * in the documentation and/or other materials provided with the distribution.
|
---|
17 | * * Neither the name of the University of Illinois at Chicago nor
|
---|
18 | * the names of its contributors may be used to endorse or promote
|
---|
19 | * products derived from this software without specific prior written permission.
|
---|
20 | *
|
---|
21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
---|
22 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
---|
23 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
---|
24 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
---|
25 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
---|
26 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
---|
27 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
---|
28 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
---|
29 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
---|
30 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
---|
31 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
---|
32 | *
|
---|
33 | * Direct questions, comments etc about SAGE to http://www.evl.uic.edu/cavern/forum/
|
---|
34 | *
|
---|
35 | *****************************************************************************/
|
---|
36 |
|
---|
37 | //-----------------------------------------------------------------------------
|
---|
38 | // $Id: DecklinkCaptureDlg.cpp,v 1.9 2006/04/11 01:11:07 ivanr Exp $
|
---|
39 | //
|
---|
40 | // Desc: DirectShow capture sample
|
---|
41 | //
|
---|
42 | // Copyright (c) Blackmagic Design 2005. All rights reserved.
|
---|
43 | //
|
---|
44 | // Modified by khchoi (khchoi@netmedia.gist.ac.kr) 2006/08/28
|
---|
45 | //-----------------------------------------------------------------------------
|
---|
46 |
|
---|
47 | #include "stdafx.h"
|
---|
48 | #include "DecklinkCapture.h"
|
---|
49 | #include "DecklinkCaptureDlg.h"
|
---|
50 | #define _SMART_TREE_
|
---|
51 | #define _MAIN_
|
---|
52 | #include "GrabberProc.h"
|
---|
53 | #include "Utils2.h"
|
---|
54 |
|
---|
55 | #include <initguid.h> // TODO: move this to a lib
|
---|
56 | //#include "DecklinkSample_uuids.h"
|
---|
57 |
|
---|
58 | #undef lstrlenW
|
---|
59 |
|
---|
60 | #ifdef _DEBUG
|
---|
61 | #define new DEBUG_NEW
|
---|
62 | #endif
|
---|
63 |
|
---|
64 | #define WM_GRAPHNOTIFY WM_APP+1 // for Filter Graph event notification
|
---|
65 |
|
---|
66 | int FrameRate = FR20; // 20fps
|
---|
67 |
|
---|
68 | //-----------------------------------------------------------------------------
|
---|
69 | // CAboutDlg
|
---|
70 | //-----------------------------------------------------------------------------
|
---|
71 |
|
---|
72 | // CAboutDlg dialog used for App About
|
---|
73 |
|
---|
74 | class CAboutDlg : public CDialog
|
---|
75 | {
|
---|
76 | public:
|
---|
77 | CAboutDlg();
|
---|
78 |
|
---|
79 | // Dialog Data
|
---|
80 | enum { IDD = IDD_ABOUTBOX };
|
---|
81 |
|
---|
82 | protected:
|
---|
83 | virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
|
---|
84 |
|
---|
85 | // Implementation
|
---|
86 | protected:
|
---|
87 | DECLARE_MESSAGE_MAP()
|
---|
88 | };
|
---|
89 |
|
---|
90 | CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
|
---|
91 | {
|
---|
92 | }
|
---|
93 |
|
---|
94 | void CAboutDlg::DoDataExchange(CDataExchange* pDX)
|
---|
95 | {
|
---|
96 | CDialog::DoDataExchange(pDX);
|
---|
97 | }
|
---|
98 |
|
---|
99 | BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
|
---|
100 | END_MESSAGE_MAP()
|
---|
101 |
|
---|
102 |
|
---|
103 | //-----------------------------------------------------------------------------
|
---|
104 | // CDecklinkCaptureDlg dialog
|
---|
105 | //-----------------------------------------------------------------------------
|
---|
106 | //-----------------------------------------------------------------------------
|
---|
107 | // Constructor
|
---|
108 | //
|
---|
109 | CDecklinkCaptureDlg::CDecklinkCaptureDlg(CWnd* pParent /*=NULL*/)
|
---|
110 | : CDialog(CDecklinkCaptureDlg::IDD, pParent)
|
---|
111 | , m_pIVW(NULL)
|
---|
112 | {
|
---|
113 | m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
|
---|
114 | }
|
---|
115 |
|
---|
116 | CDecklinkCaptureDlg::~CDecklinkCaptureDlg() {
|
---|
117 | grabberCallback.m_GrabProc.FreeFilter();
|
---|
118 | }
|
---|
119 |
|
---|
120 | //-----------------------------------------------------------------------------
|
---|
121 | // DoDataExchange
|
---|
122 | //
|
---|
123 | void CDecklinkCaptureDlg::DoDataExchange(CDataExchange* pDX)
|
---|
124 | {
|
---|
125 | CDialog::DoDataExchange(pDX);
|
---|
126 | DDX_Control(pDX, IDC_COMBO_VIDEOFORMATS, m_videoFormatCtrl);
|
---|
127 | DDX_Control(pDX, IDC_COMBO_AUDIOFORMATS, m_audioFormatCtrl);
|
---|
128 |
|
---|
129 | DDX_Control(pDX, IDC_STATIC_PREVIEW, m_preview);
|
---|
130 |
|
---|
131 | DDX_Control(pDX, IDC_EDIT_CAPTUREFILE, m_captureFileCtrl);
|
---|
132 | DDX_Control(pDX, IDC_COMBO_COMPRESSION, m_compressionCtrl);
|
---|
133 | DDX_Control(pDX, IDC_COMBO_COMPRESSION2, m_framerateCtrl);
|
---|
134 | DDX_Control(pDX, IDC_COMBO_VIDEODEVICE, m_videoDeviceCtrl);
|
---|
135 | DDX_Control(pDX, IDC_COMBO_AUDIODEVICE, m_audioDeviceCtrl);
|
---|
136 |
|
---|
137 | DDX_Control(pDX, IDC_SAGE_ADDR, m_sageStreamCtrl);
|
---|
138 | DDX_Control(pDX, IDC_SAGE_EDIT_IP, m_sageIPCtrl);
|
---|
139 |
|
---|
140 | DDX_Control(pDX, IDC_CHECK_AUDIOMUTE, m_MuteCtrl);
|
---|
141 | }
|
---|
142 |
|
---|
143 | BEGIN_MESSAGE_MAP(CDecklinkCaptureDlg, CDialog)
|
---|
144 | ON_WM_SYSCOMMAND()
|
---|
145 | ON_WM_PAINT()
|
---|
146 | ON_WM_QUERYDRAGICON()
|
---|
147 | //}}AFX_MSG_MAP
|
---|
148 | ON_CBN_SELCHANGE(IDC_COMBO_VIDEOFORMATS, OnCbnSelchangeComboVideoformats)
|
---|
149 | ON_CBN_SELCHANGE(IDC_COMBO_AUDIOFORMATS, OnCbnSelchangeComboAudioformats)
|
---|
150 | ON_BN_CLICKED(IDC_CHECK_AUDIOMUTE, OnBnClickedCheckAudiomute)
|
---|
151 | ON_BN_CLICKED(IDC_BUTTON_BROWSE, OnBnClickedButtonBrowse)
|
---|
152 | ON_BN_CLICKED(IDC_BUTTON_CAPTURE, OnBnClickedButtonCapture)
|
---|
153 | ON_BN_CLICKED(IDC_BUTTON_STOP, OnBnClickedButtonStop)
|
---|
154 | ON_CBN_SELCHANGE(IDC_COMBO_COMPRESSION, OnCbnSelchangeComboCompression)
|
---|
155 | ON_CBN_SELCHANGE(IDC_COMBO_VIDEODEVICE, OnCbnSelchangeComboVideodevice)
|
---|
156 | ON_CBN_SELCHANGE(IDC_COMBO_AUDIODEVICE, OnCbnSelchangeComboAudiodevice)
|
---|
157 | ON_BN_CLICKED(IDC_BUTTON_CODECPROPERTIES, &CDecklinkCaptureDlg::OnBnClickedButtonCodecproperties)
|
---|
158 | ON_BN_CLICKED(IDC_SAGE_START, &CDecklinkCaptureDlg::OnBnClickedSageStart)
|
---|
159 | ON_BN_CLICKED(IDC_SAGE_STOP, &CDecklinkCaptureDlg::OnBnClickedSageStop)
|
---|
160 | ON_CBN_SELCHANGE(IDC_SAGE_ADDR, &CDecklinkCaptureDlg::OnCbnSelchangeSageAddr)
|
---|
161 | ON_EN_CHANGE(IDC_SAGE_EDIT_IP, &CDecklinkCaptureDlg::OnEnChangeSageEditIp)
|
---|
162 | ON_BN_CLICKED(IDC_SAGE_REG, &CDecklinkCaptureDlg::OnBnClickedSageReg)
|
---|
163 | ON_CBN_SELCHANGE(IDC_COMBO_COMPRESSION2, &CDecklinkCaptureDlg::OnCbnSelchangeComboCompression2)
|
---|
164 | END_MESSAGE_MAP()
|
---|
165 |
|
---|
166 |
|
---|
167 | //-----------------------------------------------------------------------------
|
---|
168 | // CDecklinkCaptureDlg message handlers
|
---|
169 | //-----------------------------------------------------------------------------
|
---|
170 | //-----------------------------------------------------------------------------
|
---|
171 | // OnInitDialog
|
---|
172 | // Called before the dialog is displayed, use this message handler to initialise
|
---|
173 | // our app
|
---|
174 | BOOL CDecklinkCaptureDlg::OnInitDialog()
|
---|
175 | {
|
---|
176 | CDialog::OnInitDialog();
|
---|
177 |
|
---|
178 | // Add "About..." menu item to system menu.
|
---|
179 |
|
---|
180 | // IDM_ABOUTBOX must be in the system command range.
|
---|
181 | ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
|
---|
182 | ASSERT(IDM_ABOUTBOX < 0xF000);
|
---|
183 |
|
---|
184 | CMenu* pSysMenu = GetSystemMenu(FALSE);
|
---|
185 | if (pSysMenu != NULL)
|
---|
186 | {
|
---|
187 | CString strAboutMenu;
|
---|
188 | strAboutMenu.LoadString(IDS_ABOUTBOX);
|
---|
189 | if (!strAboutMenu.IsEmpty())
|
---|
190 | {
|
---|
191 | pSysMenu->AppendMenu(MF_SEPARATOR);
|
---|
192 | pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
|
---|
193 | }
|
---|
194 | }
|
---|
195 |
|
---|
196 | // Set the icon for this dialog. The framework does this automatically
|
---|
197 | // when the application's main window is not a dialog
|
---|
198 | SetIcon(m_hIcon, TRUE); // Set big icon
|
---|
199 | SetIcon(m_hIcon, FALSE); // Set small icon
|
---|
200 |
|
---|
201 | // create a basic capture graph and preview the incoming video
|
---|
202 | m_pGraph = NULL;
|
---|
203 | m_pVideoCapture = NULL;
|
---|
204 | m_pAudioCapture = NULL;
|
---|
205 | m_pVideoRenderer = NULL;
|
---|
206 | m_pSmartT = NULL;
|
---|
207 |
|
---|
208 | m_pControl = NULL;
|
---|
209 |
|
---|
210 | m_pIVW = NULL;
|
---|
211 | m_pMediaEvent = NULL;
|
---|
212 |
|
---|
213 | m_ROTRegister = 0;
|
---|
214 | m_bAudioMute = FALSE;
|
---|
215 |
|
---|
216 | m_compressor = 0;
|
---|
217 | m_framerate = 0;
|
---|
218 | m_bEnableCompressionCtrl = TRUE;
|
---|
219 |
|
---|
220 | m_captureFile = "<Select File>";
|
---|
221 |
|
---|
222 | // initialise default video media type
|
---|
223 | ZeroMemory(&m_vihDefault, sizeof(m_vihDefault));
|
---|
224 | m_vihDefault.AvgTimePerFrame = 333667;
|
---|
225 | m_vihDefault.bmiHeader.biWidth = 720;
|
---|
226 | m_vihDefault.bmiHeader.biHeight = 486;
|
---|
227 | m_vihDefault.bmiHeader.biBitCount = 16;
|
---|
228 | m_vihDefault.bmiHeader.biCompression = 'YVYU';
|
---|
229 |
|
---|
230 | LoadIpInfo();
|
---|
231 |
|
---|
232 | /*m_sageStreamCtrl.InsertString(0, _T("Yorda"));
|
---|
233 | m_sageStreamCtrl.InsertString(1, _T("Caesar"));
|
---|
234 | m_sageStreamCtrl.InsertString(2, _T("PathFinder"));
|
---|
235 | m_sageStreamCtrl.SetCurSel(1);
|
---|
236 | m_sageIPCtrl.SetWindowText("67.58.62.21");
|
---|
237 | */
|
---|
238 |
|
---|
239 | // initialise default audio media type
|
---|
240 | ZeroMemory(&m_wfexDefault, sizeof(m_wfexDefault));
|
---|
241 | m_wfexDefault.nChannels = 2; // the only field of interest
|
---|
242 |
|
---|
243 | // retrieve last state
|
---|
244 | QueryRegistry();
|
---|
245 |
|
---|
246 | m_captureFileCtrl.SetWindowText(m_captureFile);
|
---|
247 |
|
---|
248 | EnableControls();
|
---|
249 |
|
---|
250 | // create a preview graph
|
---|
251 | // add the filters that will be used by all the graphs; preview, uncompressed capture, dv capture,
|
---|
252 | // mpeg capture and windows media capture
|
---|
253 | HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, reinterpret_cast<void**>(&m_pGraph));
|
---|
254 | if (SUCCEEDED(hr))
|
---|
255 | {
|
---|
256 | #ifdef _DEBUG
|
---|
257 | // hr = CDSUtils::AddGraphToRot(m_pGraph, &m_ROTRegister);
|
---|
258 | #endif
|
---|
259 | hr = m_pGraph->QueryInterface(IID_IMediaControl, reinterpret_cast<void**>(&m_pControl));
|
---|
260 | if (SUCCEEDED(hr))
|
---|
261 | {
|
---|
262 | // locate the video capture devices
|
---|
263 | hr = PopulateDeviceControl(&CLSID_VideoInputDeviceCategory, &m_videoDeviceCtrl);
|
---|
264 | if (SUCCEEDED(hr))
|
---|
265 | {
|
---|
266 | hr = PopulateDeviceControl(&CLSID_AudioInputDeviceCategory, &m_audioDeviceCtrl);
|
---|
267 | if (SUCCEEDED(hr))
|
---|
268 | {
|
---|
269 | PWSTR pVideoName = (PWSTR)m_videoDeviceCtrl.GetItemData(m_videoDeviceCtrl.SetCurSel(0));
|
---|
270 | PWSTR pAudioName = (PWSTR)m_audioDeviceCtrl.GetItemData(m_audioDeviceCtrl.SetCurSel(0));
|
---|
271 | if (pVideoName && pAudioName)
|
---|
272 | {
|
---|
273 | hr = CDSUtils::AddFilter2(m_pGraph, CLSID_VideoInputDeviceCategory, pVideoName, &m_pVideoCapture);
|
---|
274 | if (SUCCEEDED(hr))
|
---|
275 | {
|
---|
276 | hr = CDSUtils::AddFilter2(m_pGraph, CLSID_AudioInputDeviceCategory, pAudioName, &m_pAudioCapture);
|
---|
277 | if (SUCCEEDED(hr))
|
---|
278 | {
|
---|
279 | PopulateVideoControl(); // populate the video format control with the video formats of the currently selected device
|
---|
280 | PopulateAudioControl(); // populate the audio format control with the audio formats of the currently selected device
|
---|
281 | PopulateCompressionControl();
|
---|
282 |
|
---|
283 | // locate video screen renderer for the preview window
|
---|
284 | hr = CDSUtils::AddFilter(m_pGraph, CLSID_VideoRendererDefault, L"Video Renderer", &m_pVideoRenderer);
|
---|
285 | if (SUCCEEDED(hr))
|
---|
286 | {
|
---|
287 | hr = CreatePreviewGraph();
|
---|
288 | }
|
---|
289 | }
|
---|
290 | }
|
---|
291 | }
|
---|
292 | }
|
---|
293 | }
|
---|
294 | }
|
---|
295 | }
|
---|
296 |
|
---|
297 | return TRUE; // return TRUE unless you set the focus to a control
|
---|
298 | }
|
---|
299 |
|
---|
300 | //-----------------------------------------------------------------------------
|
---|
301 | // DestroyWindow
|
---|
302 | // Called when the window is being destroyed, clean up and free all resources.
|
---|
303 | BOOL CDecklinkCaptureDlg::DestroyWindow()
|
---|
304 | {
|
---|
305 | m_regUtils.Close();
|
---|
306 | #ifdef _DEBUG
|
---|
307 | CDSUtils::RemoveGraphFromRot(m_ROTRegister);
|
---|
308 | #endif
|
---|
309 | DestroyGraph();
|
---|
310 |
|
---|
311 | grabberCallback.m_GrabProc.Shutdown();
|
---|
312 |
|
---|
313 | SAFE_RELEASE(m_pControl);
|
---|
314 |
|
---|
315 | // Hide Video Window and remove owner. This has to be done prior to
|
---|
316 | // destroying any window that displays video/still.
|
---|
317 | if (m_pIVW)
|
---|
318 | {
|
---|
319 | m_pIVW->put_Visible(OAFALSE);
|
---|
320 | m_pIVW->put_Owner(NULL);
|
---|
321 | }
|
---|
322 |
|
---|
323 | SAFE_RELEASE(m_pIVW);
|
---|
324 | SAFE_RELEASE(m_pMediaEvent);
|
---|
325 |
|
---|
326 | grabberCallback.m_GrabProc.FreeFilter();
|
---|
327 | SAFE_RELEASE(m_pVideoRenderer);
|
---|
328 | SAFE_RELEASE(m_pAudioCapture);
|
---|
329 | SAFE_RELEASE(m_pVideoCapture);
|
---|
330 |
|
---|
331 | SAFE_RELEASE(m_pGraph);
|
---|
332 |
|
---|
333 | // free mediatypes attached to format controls
|
---|
334 | int count = m_videoFormatCtrl.GetCount();
|
---|
335 | for (int item=0; item<count; ++item)
|
---|
336 | {
|
---|
337 | DeleteMediaType((AM_MEDIA_TYPE*)m_videoFormatCtrl.GetItemData(item));
|
---|
338 | }
|
---|
339 |
|
---|
340 | count = m_audioFormatCtrl.GetCount();
|
---|
341 | int item;
|
---|
342 | for (item=0; item<count; ++item)
|
---|
343 | {
|
---|
344 | DeleteMediaType((AM_MEDIA_TYPE*)m_audioFormatCtrl.GetItemData(item));
|
---|
345 | }
|
---|
346 |
|
---|
347 | // release the device names attached to the item's data
|
---|
348 | count = m_videoDeviceCtrl.GetCount();
|
---|
349 | for (item=0; item<count; ++item)
|
---|
350 | {
|
---|
351 | PWSTR pName = (PWSTR)m_videoDeviceCtrl.GetItemData(item);
|
---|
352 | delete [] pName;
|
---|
353 | }
|
---|
354 |
|
---|
355 | count = m_audioDeviceCtrl.GetCount();
|
---|
356 | for (item=0; item<count; ++item)
|
---|
357 | {
|
---|
358 | PWSTR pName = (PWSTR)m_audioDeviceCtrl.GetItemData(item);
|
---|
359 | delete [] pName;
|
---|
360 | }
|
---|
361 |
|
---|
362 | return CDialog::DestroyWindow();
|
---|
363 | }
|
---|
364 |
|
---|
365 | //-----------------------------------------------------------------------------
|
---|
366 | // OnSysCommand
|
---|
367 | //
|
---|
368 | void CDecklinkCaptureDlg::OnSysCommand(UINT nID, LPARAM lParam)
|
---|
369 | {
|
---|
370 | if ((nID & 0xFFF0) == IDM_ABOUTBOX)
|
---|
371 | {
|
---|
372 | CAboutDlg dlgAbout;
|
---|
373 | dlgAbout.DoModal();
|
---|
374 | }
|
---|
375 | else
|
---|
376 | {
|
---|
377 | CDialog::OnSysCommand(nID, lParam);
|
---|
378 | }
|
---|
379 | }
|
---|
380 |
|
---|
381 | //-----------------------------------------------------------------------------
|
---|
382 | // OnPaint
|
---|
383 | // If you add a minimize button to your dialog, you will need the code below
|
---|
384 | // to draw the icon. For MFC applications using the document/view model,
|
---|
385 | // this is automatically done for you by the framework.
|
---|
386 | void CDecklinkCaptureDlg::OnPaint()
|
---|
387 | {
|
---|
388 | if (IsIconic())
|
---|
389 | {
|
---|
390 | CPaintDC dc(this); // device context for painting
|
---|
391 |
|
---|
392 | SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
|
---|
393 |
|
---|
394 | // Center icon in client rectangle
|
---|
395 | int cxIcon = GetSystemMetrics(SM_CXICON);
|
---|
396 | int cyIcon = GetSystemMetrics(SM_CYICON);
|
---|
397 | CRect rect;
|
---|
398 | GetClientRect(&rect);
|
---|
399 | int x = (rect.Width() - cxIcon + 1) / 2;
|
---|
400 | int y = (rect.Height() - cyIcon + 1) / 2;
|
---|
401 |
|
---|
402 | // Draw the icon
|
---|
403 | dc.DrawIcon(x, y, m_hIcon);
|
---|
404 | }
|
---|
405 | else
|
---|
406 | {
|
---|
407 | CDialog::OnPaint();
|
---|
408 | }
|
---|
409 | }
|
---|
410 |
|
---|
411 | //-----------------------------------------------------------------------------
|
---|
412 | // HandleGraphEvent
|
---|
413 | // At the moment we just read the event, discard it and release memory used to store it.
|
---|
414 | void CDecklinkCaptureDlg::HandleGraphEvent(void)
|
---|
415 | {
|
---|
416 | LONG lEventCode, lEventParam1, lEventParam2;
|
---|
417 |
|
---|
418 | if (!m_pMediaEvent)
|
---|
419 | {
|
---|
420 | return;
|
---|
421 | }
|
---|
422 |
|
---|
423 | while (SUCCEEDED(m_pMediaEvent->GetEvent(&lEventCode, reinterpret_cast<LONG_PTR *>(&lEventParam1), reinterpret_cast<LONG_PTR *>(&lEventParam2), 0)))
|
---|
424 | {
|
---|
425 | // just free memory associated with event
|
---|
426 | m_pMediaEvent->FreeEventParams(lEventCode, lEventParam1, lEventParam2);
|
---|
427 | }
|
---|
428 | }
|
---|
429 |
|
---|
430 | //-----------------------------------------------------------------------------
|
---|
431 | // WindowProc
|
---|
432 | // Have to add our own message handling loop to handle events from the preview video
|
---|
433 | // window and to pass Window events onto it - this is so it redraws itself correctly etc.
|
---|
434 | LRESULT CDecklinkCaptureDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
|
---|
435 | {
|
---|
436 | switch (message)
|
---|
437 | {
|
---|
438 | case WM_GRAPHNOTIFY:
|
---|
439 | HandleGraphEvent();
|
---|
440 | break;
|
---|
441 | }
|
---|
442 |
|
---|
443 | // Pass all msgs to video window. vid window exists as child of static
|
---|
444 | // picture frame. This ensures video window redraws itself etc.
|
---|
445 | if (m_pIVW)
|
---|
446 | {
|
---|
447 | m_pIVW->NotifyOwnerMessage(reinterpret_cast<LONG_PTR>(m_hWnd) /* from me */, message, wParam, lParam);
|
---|
448 | }
|
---|
449 |
|
---|
450 | return CDialog::WindowProc(message, wParam, lParam);
|
---|
451 | }
|
---|
452 |
|
---|
453 | //-----------------------------------------------------------------------------
|
---|
454 | // OnQueryDragIcon
|
---|
455 | // The system calls this function to obtain the cursor to display while the user drags
|
---|
456 | // the minimized window.
|
---|
457 | HCURSOR CDecklinkCaptureDlg::OnQueryDragIcon()
|
---|
458 | {
|
---|
459 | return static_cast<HCURSOR>(m_hIcon);
|
---|
460 | }
|
---|
461 |
|
---|
462 | //-----------------------------------------------------------------------------
|
---|
463 | // CreatePreviewGraph
|
---|
464 | // Create a graph to preview the input
|
---|
465 | // NOTE: There are many ways of building graphs, you could opt for the ICaptureGraphBuilder interface which would
|
---|
466 | // make things are lot simpler, however it doesn't always build the most efficient graphs.
|
---|
467 | HRESULT CDecklinkCaptureDlg::CreatePreviewGraph()
|
---|
468 | {
|
---|
469 | HRESULT hr = S_OK;
|
---|
470 |
|
---|
471 | if (m_pGraph)
|
---|
472 | {
|
---|
473 | // locate smart-T
|
---|
474 | // NOTE: The smart-T appears to hold references to its upstream connections even when its input pin
|
---|
475 | // is diconnected. The smart-T has to be removed from the graph in order to clear these references which
|
---|
476 | // is why the filter is enumerated and added every time the preview graph is built and removed whenever
|
---|
477 | // it is destroyed.
|
---|
478 |
|
---|
479 | if (SUCCEEDED(hr))
|
---|
480 | {
|
---|
481 | //SaveLog.ToLog("\n For uncompressed in Preview");
|
---|
482 |
|
---|
483 | hr = CDSUtils::ConnectFilters(m_pGraph, m_pVideoCapture, NULL, m_pVideoRenderer, NULL);
|
---|
484 | }
|
---|
485 | }
|
---|
486 | else
|
---|
487 | {
|
---|
488 | hr = E_POINTER;
|
---|
489 | }
|
---|
490 |
|
---|
491 | if (SUCCEEDED(hr))
|
---|
492 | {
|
---|
493 | // the video path has been connected, initialise the preview window
|
---|
494 | InitialiseVideoPreview();
|
---|
495 |
|
---|
496 | // optionally connect the audio path
|
---|
497 | if (FALSE == m_bAudioMute)
|
---|
498 | {
|
---|
499 | // connect the Decklink audio capture pin to the mux
|
---|
500 | //hjhur //hr = CDSUtils::RenderFilter(m_pGraph, m_pAudioCapture, L"Capture");
|
---|
501 | }
|
---|
502 |
|
---|
503 | // run the graph so that we can preview the input video
|
---|
504 | if (m_pControl)
|
---|
505 | {
|
---|
506 | hr = m_pControl->Run();
|
---|
507 | }
|
---|
508 | else
|
---|
509 | {
|
---|
510 | hr = E_POINTER;
|
---|
511 | }
|
---|
512 | }
|
---|
513 |
|
---|
514 | EXECUTE_ASSERT(ERROR_SUCCESS ==SaveGraphFile(m_pGraph, L"Preview.GRF"));
|
---|
515 |
|
---|
516 | return hr;
|
---|
517 | }
|
---|
518 |
|
---|
519 |
|
---|
520 | //-----------------------------------------------------------------------------
|
---|
521 | // CreateCaptureGraph
|
---|
522 | // Create a graph to capture the input
|
---|
523 | HRESULT CDecklinkCaptureDlg::CreateCaptureGraph(bool sage)
|
---|
524 | {
|
---|
525 | HRESULT hr = S_OK;
|
---|
526 |
|
---|
527 | CButton* pCheck = (CButton*)GetDlgItem(IDC_CHECK_AUDIOMUTE);
|
---|
528 | if (pCheck)
|
---|
529 | {
|
---|
530 | m_bAudioMute = pCheck->GetState() & 0x0003;
|
---|
531 | grabberCallback.m_GrabProc.SetAudioOn(!m_bAudioMute);
|
---|
532 | }
|
---|
533 |
|
---|
534 | if (m_pGraph)
|
---|
535 | {
|
---|
536 | // locate smart-T
|
---|
537 | // NOTE: The smart-T appears to hold references to its upstream connections even when its input pin
|
---|
538 | // is diconnected. The smart-T has to be removed from the graph in order to clear these references which
|
---|
539 | // is why the filter is enumerated and added every time the preview graph is built and removed whenever
|
---|
540 | // it is destroyed.
|
---|
541 | #ifdef _SMART_TREE_
|
---|
542 | if(m_pSmartT == NULL) {
|
---|
543 | //SaveLog.ToLog("\n Create Smart Tree");
|
---|
544 | hr = CDSUtils::AddFilter(m_pGraph, CLSID_SmartTee, L"Smart Tee", &m_pSmartT);
|
---|
545 | }
|
---|
546 | #else
|
---|
547 | hr = 1;
|
---|
548 | #endif
|
---|
549 | if (SUCCEEDED(hr))
|
---|
550 | {
|
---|
551 | //SaveLog.ToLog("\n For uncompressed in Capture");
|
---|
552 | // uncompressed, mpeg and wm preview
|
---|
553 | // create the following:
|
---|
554 | //
|
---|
555 | // Decklink Video Capture -> Smart-T -> AVI Decompressor -> Video Renderer
|
---|
556 | // Decklink Audio Capture -> Default Audio Renderer
|
---|
557 | //
|
---|
558 |
|
---|
559 | // render the preview pin on the smart-T filter
|
---|
560 | // first connect the Decklink video capture pin to the smart-T
|
---|
561 | #ifdef _SMART_TREE_
|
---|
562 | hr = CDSUtils::ConnectFilters(m_pGraph, m_pVideoCapture, NULL, m_pSmartT, NULL);
|
---|
563 | if(FAILED(hr)) {
|
---|
564 | //SaveLog.ToLog("\n Can not connect Smart filter to VideoCature");
|
---|
565 | return hr;
|
---|
566 | }
|
---|
567 | #endif
|
---|
568 | if (SUCCEEDED(hr))
|
---|
569 | {
|
---|
570 | #ifdef _SMART_TREE_
|
---|
571 | int color_mode = m_compressionCtrl.GetItemData(m_compressionCtrl.GetCurSel());
|
---|
572 | //SaveLog.ToLog("\n Color mode : "); SaveLog.ToLog(color_mode);
|
---|
573 | hr = grabberCallback.m_GrabProc.SetFilter(m_pGraph, m_pSmartT, L"Preview", color_mode, sage);
|
---|
574 |
|
---|
575 | if(FAILED(hr)) {
|
---|
576 | //SaveLog.ToLog("\n Can not connect Grabber to Smart filter");
|
---|
577 | return hr;
|
---|
578 | }
|
---|
579 | #else
|
---|
580 | int color_mode = m_compressionCtrl.GetItemData(m_compressionCtrl.GetCurSel();
|
---|
581 | hr = grabberCallback.m_GrabProc.SetFilter(m_pGraph, m_pVideoCapture, L"Capture", color_mode);
|
---|
582 | #endif
|
---|
583 | // now connect the preview pin of the smart-T to the video renderer
|
---|
584 | hr = CDSUtils::ConnectFilters(m_pGraph, m_pSmartT, L"Capture", m_pVideoRenderer, NULL);
|
---|
585 | if(FAILED(hr)) {
|
---|
586 | //SaveLog.ToLog("\n Can not connect VideoRenderer to SmartT");
|
---|
587 | return hr;
|
---|
588 | }
|
---|
589 | }
|
---|
590 | }
|
---|
591 | }
|
---|
592 | else
|
---|
593 | {
|
---|
594 | hr = E_POINTER;
|
---|
595 | }
|
---|
596 |
|
---|
597 | if (SUCCEEDED(hr))
|
---|
598 | {
|
---|
599 | // the video path has been connected, initialise the preview window
|
---|
600 | InitialiseVideoPreview();
|
---|
601 | // optionally connect the audio path
|
---|
602 | if (FALSE == m_bAudioMute)
|
---|
603 | {
|
---|
604 |
|
---|
605 | int streamID = m_sageStreamCtrl.GetCurSel();
|
---|
606 | if (streamID >= 0)
|
---|
607 | {
|
---|
608 | // connect the Decklink audio capture pin to the mux
|
---|
609 | //hr = CDSUtils::RenderFilter(m_pGraph, m_pAudioCapture, L"Capture");
|
---|
610 | int audioDevice = m_audioDeviceCtrl.GetCurSel();
|
---|
611 | if(audioDevice == 1) { // from HD cam
|
---|
612 | //grabberCallback.m_GrabProc.SetAudioCaptureMode(false);
|
---|
613 | hr = grabberCallback.m_GrabProc.SetAudioFilter(m_pGraph, m_pAudioCapture, L"Capture");
|
---|
614 | } else { // capture
|
---|
615 | grabberCallback.m_GrabProc.SetAudioCaptureMode(true);
|
---|
616 | }
|
---|
617 | }
|
---|
618 | }
|
---|
619 |
|
---|
620 | // run the graph so that we can preview the input video
|
---|
621 | if (m_pControl)
|
---|
622 | {
|
---|
623 | hr = m_pControl->Run();
|
---|
624 | }
|
---|
625 | else
|
---|
626 | {
|
---|
627 | hr = E_POINTER;
|
---|
628 | }
|
---|
629 | }
|
---|
630 |
|
---|
631 | EXECUTE_ASSERT(ERROR_SUCCESS ==SaveGraphFile(m_pGraph, L"CreateCaptureGraph.GRF"));
|
---|
632 | return hr;
|
---|
633 | }
|
---|
634 |
|
---|
635 |
|
---|
636 | //-----------------------------------------------------------------------------
|
---|
637 | // DestroyGraph
|
---|
638 | // Remove all intermediate filters, keep any Decklink and video render filters as
|
---|
639 | // these are used by all the graphs.
|
---|
640 | HRESULT CDecklinkCaptureDlg::DestroyGraph(bool sage)
|
---|
641 | {
|
---|
642 | HRESULT hr = S_OK;
|
---|
643 |
|
---|
644 | if (m_pGraph && m_pControl)
|
---|
645 | {
|
---|
646 | m_pControl->Stop();
|
---|
647 |
|
---|
648 | // release our outstanding reference on this filter so it can be removed from the graph
|
---|
649 | #ifdef _SMART_TREE_
|
---|
650 | SAFE_RELEASE(m_pSmartT);
|
---|
651 | #endif
|
---|
652 |
|
---|
653 | // retrieve the name of the capture device, don't remove it in this method
|
---|
654 | PWSTR pNameVideoCapture = (PWSTR)m_videoDeviceCtrl.GetItemData(m_videoDeviceCtrl.GetCurSel());
|
---|
655 | PWSTR pNameAudioCapture = (PWSTR)m_audioDeviceCtrl.GetItemData(m_audioDeviceCtrl.GetCurSel());
|
---|
656 |
|
---|
657 | CComPtr<IEnumFilters> pEnum = NULL;
|
---|
658 | hr = m_pGraph->EnumFilters(&pEnum);
|
---|
659 | if (SUCCEEDED(hr))
|
---|
660 | {
|
---|
661 | IBaseFilter* pFilter = NULL;
|
---|
662 | while (S_OK == pEnum->Next(1, &pFilter, NULL))
|
---|
663 | {
|
---|
664 | FILTER_INFO filterInfo = {0};
|
---|
665 | hr = pFilter->QueryFilterInfo(&filterInfo);
|
---|
666 | if (SUCCEEDED(hr))
|
---|
667 | {
|
---|
668 | SAFE_RELEASE(filterInfo.pGraph);
|
---|
669 |
|
---|
670 | if ((NULL == wcsstr(filterInfo.achName, pNameVideoCapture)) && (NULL == wcsstr(filterInfo.achName, pNameAudioCapture)) && (NULL == wcsstr(filterInfo.achName, L"Video Renderer")))
|
---|
671 | {
|
---|
672 | hr = m_pGraph->RemoveFilter(pFilter);
|
---|
673 | if (SUCCEEDED(hr))
|
---|
674 | {
|
---|
675 | hr = pEnum->Reset();
|
---|
676 | }
|
---|
677 | }
|
---|
678 | }
|
---|
679 | SAFE_RELEASE(pFilter);
|
---|
680 | }
|
---|
681 | }
|
---|
682 | }
|
---|
683 | else
|
---|
684 | {
|
---|
685 | hr = E_POINTER;
|
---|
686 | }
|
---|
687 |
|
---|
688 | return hr;
|
---|
689 | }
|
---|
690 |
|
---|
691 | //-----------------------------------------------------------------------------
|
---|
692 | // InitialiseVideoPreview
|
---|
693 | // In short get the video screen renderer to draw into the picture control, which is our preview window
|
---|
694 | // the following code sets this up, in addition to adding the HandleGraphEvent and WindowProc methods
|
---|
695 | // read the DXSDK docos for more detailed information
|
---|
696 | void CDecklinkCaptureDlg::InitialiseVideoPreview(void)
|
---|
697 | {
|
---|
698 | // modify the preview window
|
---|
699 | if (m_pVideoRenderer)
|
---|
700 | {
|
---|
701 | if (NULL == m_pIVW)
|
---|
702 | {
|
---|
703 | if (SUCCEEDED(m_pVideoRenderer->QueryInterface(IID_IVideoWindow, reinterpret_cast<void**>(&m_pIVW))))
|
---|
704 | {
|
---|
705 | // get the window to handle redraws, etc
|
---|
706 | // Set msg drain of VideoWindow to point to our dialog window. The dialog's
|
---|
707 | // window procedure then handles events from the VideoWindow.
|
---|
708 | HRESULT hr = m_pIVW->put_MessageDrain(reinterpret_cast<OAHWND>(m_hWnd));
|
---|
709 |
|
---|
710 | if (NULL == m_pMediaEvent)
|
---|
711 | {
|
---|
712 | // Make graph send WM_GRAPHNOTIFY when it wants our attention see "Learning
|
---|
713 | // When an Event Occurs" in the DX9 documentation.
|
---|
714 | hr = m_pGraph->QueryInterface(IID_IMediaEventEx, reinterpret_cast<void**>(&m_pMediaEvent));
|
---|
715 | if (SUCCEEDED(hr))
|
---|
716 | {
|
---|
717 | hr = m_pMediaEvent->SetNotifyWindow(reinterpret_cast<OAHWND>(m_hWnd), WM_GRAPHNOTIFY, 0);
|
---|
718 | }
|
---|
719 |
|
---|
720 | // object created for it.
|
---|
721 | RECT rc;
|
---|
722 | m_preview.GetClientRect(&rc);
|
---|
723 | m_pIVW->SetWindowPosition(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top);
|
---|
724 |
|
---|
725 | // VideoWindow is a child window of the bounding rect
|
---|
726 | hr = m_pIVW->put_WindowStyle(WS_CHILD);
|
---|
727 | hr = m_pIVW->put_Owner(reinterpret_cast<OAHWND>(m_preview.GetSafeHwnd()));
|
---|
728 | hr = m_pIVW->SetWindowForeground(0);
|
---|
729 | }
|
---|
730 | }
|
---|
731 | }
|
---|
732 | }
|
---|
733 | }
|
---|
734 |
|
---|
735 | //-----------------------------------------------------------------------------
|
---|
736 | // PopulateDeviceControl
|
---|
737 | // Fill device combo box with available devices of the specified category
|
---|
738 | HRESULT CDecklinkCaptureDlg::PopulateDeviceControl(const GUID* pCategory, CComboBox* pCtrl)
|
---|
739 | {
|
---|
740 | HRESULT hr = S_OK;
|
---|
741 | if (pCategory && pCtrl)
|
---|
742 | {
|
---|
743 | // first enumerate the system devices for the specifed class and filter name
|
---|
744 | CComPtr<ICreateDevEnum> pSysDevEnum = NULL;
|
---|
745 | hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, reinterpret_cast<void**>(&pSysDevEnum));
|
---|
746 |
|
---|
747 | if (SUCCEEDED(hr))
|
---|
748 | {
|
---|
749 | CComPtr<IEnumMoniker> pEnumCat = NULL;
|
---|
750 | hr = pSysDevEnum->CreateClassEnumerator(*pCategory, &pEnumCat, 0);
|
---|
751 |
|
---|
752 | if (S_OK == hr)
|
---|
753 | {
|
---|
754 | IMoniker* pMoniker = NULL;
|
---|
755 | bool Loop = true;
|
---|
756 | while ((S_OK == pEnumCat->Next(1, &pMoniker, NULL)) && Loop)
|
---|
757 | {
|
---|
758 | IPropertyBag* pPropBag = NULL;
|
---|
759 | hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, reinterpret_cast<void**>(&pPropBag));
|
---|
760 |
|
---|
761 | if (SUCCEEDED(hr))
|
---|
762 | {
|
---|
763 | VARIANT varName;
|
---|
764 | VariantInit(&varName);
|
---|
765 | hr = pPropBag->Read(L"FriendlyName", &varName, 0);
|
---|
766 | if (SUCCEEDED(hr))
|
---|
767 | {
|
---|
768 | size_t len = wcslen(varName.bstrVal) + 1;
|
---|
769 | PWSTR pName = new WCHAR [len];
|
---|
770 | StringCchCopyW(pName, len, varName.bstrVal);
|
---|
771 | CW2AEX<> buf(varName.bstrVal);
|
---|
772 | pCtrl->SetItemData(pCtrl->AddString(buf), (DWORD)pName);
|
---|
773 | }
|
---|
774 |
|
---|
775 | VariantClear(&varName);
|
---|
776 |
|
---|
777 | // contained within a loop, decrement the reference count
|
---|
778 | SAFE_RELEASE(pPropBag);
|
---|
779 | }
|
---|
780 | SAFE_RELEASE(pMoniker);
|
---|
781 | }
|
---|
782 | }
|
---|
783 | }
|
---|
784 | }
|
---|
785 | else
|
---|
786 | {
|
---|
787 | hr = E_POINTER;
|
---|
788 | }
|
---|
789 |
|
---|
790 | return hr;
|
---|
791 | }
|
---|
792 |
|
---|
793 | //-----------------------------------------------------------------------------
|
---|
794 | // PopulateVideoControl
|
---|
795 | // Fill video format combo box with supported video formats using the IAMStreamConfig
|
---|
796 | // interface.
|
---|
797 | HRESULT CDecklinkCaptureDlg::PopulateVideoControl()
|
---|
798 | {
|
---|
799 | HRESULT hr = S_OK;
|
---|
800 |
|
---|
801 | if (m_pVideoCapture)
|
---|
802 | {
|
---|
803 | // free mediatypes attached to format controls
|
---|
804 | int count = m_videoFormatCtrl.GetCount();
|
---|
805 | if (count)
|
---|
806 | {
|
---|
807 | for (int item=0; item<count; ++item)
|
---|
808 | {
|
---|
809 | DeleteMediaType((AM_MEDIA_TYPE*)m_videoFormatCtrl.GetItemData(item));
|
---|
810 | }
|
---|
811 | m_videoFormatCtrl.ResetContent();
|
---|
812 | }
|
---|
813 |
|
---|
814 | // locate the video capture pin and QI for stream control
|
---|
815 | CComPtr<IAMStreamConfig> pISC = NULL;
|
---|
816 | hr = CDSUtils::FindPinInterface(m_pVideoCapture, &MEDIATYPE_Video, PINDIR_OUTPUT, IID_IAMStreamConfig, reinterpret_cast<void**>(&pISC));
|
---|
817 | if (SUCCEEDED(hr))
|
---|
818 | {
|
---|
819 | // loop through all the capabilities (video formats) and populate the control
|
---|
820 | int count, size;
|
---|
821 | hr = pISC->GetNumberOfCapabilities(&count, &size);
|
---|
822 | if (SUCCEEDED(hr))
|
---|
823 | {
|
---|
824 | if (sizeof(VIDEO_STREAM_CONFIG_CAPS) == size)
|
---|
825 | {
|
---|
826 | AM_MEDIA_TYPE* pmt = NULL;
|
---|
827 | VIDEO_STREAM_CONFIG_CAPS vscc;
|
---|
828 | VIDEOINFOHEADER* pvih = NULL;
|
---|
829 |
|
---|
830 | for (int index=0; index<count; ++index)
|
---|
831 | {
|
---|
832 | hr = pISC->GetStreamCaps(index, &pmt, reinterpret_cast<BYTE*>(&vscc));
|
---|
833 | if (SUCCEEDED(hr))
|
---|
834 | {
|
---|
835 | char buffer[128];
|
---|
836 | WORD PixelFormat;
|
---|
837 | float FrameRate;
|
---|
838 |
|
---|
839 | ZeroMemory(buffer, sizeof(buffer));
|
---|
840 |
|
---|
841 | pvih = (VIDEOINFOHEADER*)pmt->pbFormat;
|
---|
842 |
|
---|
843 | char* pPixelFormatLUT[] = {"4:2:2", "4:4:4"};
|
---|
844 | if (pvih->bmiHeader.biBitCount == 16) PixelFormat = 8;
|
---|
845 | else if (pvih->bmiHeader.biBitCount == 20) PixelFormat = 10;
|
---|
846 | else PixelFormat = pvih->bmiHeader.biBitCount;
|
---|
847 |
|
---|
848 | // provide a useful description of the formats
|
---|
849 | if (486 == pvih->bmiHeader.biHeight)
|
---|
850 | {
|
---|
851 | if (417083 == pvih->AvgTimePerFrame)
|
---|
852 | {
|
---|
853 | StringCbPrintfA(buffer, sizeof(buffer), "NTSC %d-bit %s (3:2 pulldown removal)", PixelFormat, pPixelFormatLUT[(30 == PixelFormat)]);
|
---|
854 | }
|
---|
855 | else
|
---|
856 | {
|
---|
857 | StringCbPrintfA(buffer, sizeof(buffer), "NTSC %d-bit %s", PixelFormat, pPixelFormatLUT[(30 == PixelFormat)]);
|
---|
858 | }
|
---|
859 | }
|
---|
860 | else if (576 == pvih->bmiHeader.biHeight)
|
---|
861 | {
|
---|
862 | StringCbPrintfA(buffer, sizeof(buffer), "PAL %d-bit %s", PixelFormat, pPixelFormatLUT[(30 == PixelFormat)]);
|
---|
863 | }
|
---|
864 | else
|
---|
865 | {
|
---|
866 | char* pFrameRateFormat[] = {"%.2f", "%.0f"};
|
---|
867 | FrameRate = (float)(long)UNITS / pvih->AvgTimePerFrame;
|
---|
868 |
|
---|
869 | if ((720 == pvih->bmiHeader.biHeight) && (59.94 > FrameRate))
|
---|
870 | {
|
---|
871 | if ((FrameRate - (int)FrameRate) > 0.01)
|
---|
872 | {
|
---|
873 | StringCbPrintfA(buffer, sizeof(buffer), "HD720 %.2fp %d-bit %s (Overcranked 60p)", FrameRate, PixelFormat, pPixelFormatLUT[(30 == PixelFormat)]);
|
---|
874 | }
|
---|
875 | else
|
---|
876 | {
|
---|
877 | StringCbPrintfA(buffer, sizeof(buffer), "HD720 %.0fp %d-bit %s (Overcranked 60p)", FrameRate, PixelFormat, pPixelFormatLUT[(30 == PixelFormat)]);
|
---|
878 | }
|
---|
879 | }
|
---|
880 | else if ((720 == pvih->bmiHeader.biHeight) && (59.94 <= FrameRate))
|
---|
881 | {
|
---|
882 | if ((FrameRate - (int)FrameRate) > 0.01)
|
---|
883 | {
|
---|
884 | StringCbPrintfA(buffer, sizeof(buffer), "HD720 %.2fp %d-bit %s", FrameRate, PixelFormat, pPixelFormatLUT[(30 == PixelFormat)]);
|
---|
885 | }
|
---|
886 | else
|
---|
887 | {
|
---|
888 | StringCbPrintfA(buffer, sizeof(buffer), "HD720 %.0fp %d-bit %s", FrameRate, PixelFormat, pPixelFormatLUT[(30 == PixelFormat)]);
|
---|
889 | }
|
---|
890 | }
|
---|
891 | else if ((1080 == pvih->bmiHeader.biHeight) && (50.00 <= FrameRate))
|
---|
892 | {
|
---|
893 | if ((FrameRate - (int)FrameRate) > 0.01)
|
---|
894 | {
|
---|
895 | StringCbPrintfA(buffer, sizeof(buffer), "HD1080 %.2fi %d-bit %s", FrameRate, PixelFormat, pPixelFormatLUT[(30 == PixelFormat)]);
|
---|
896 | }
|
---|
897 | else
|
---|
898 | {
|
---|
899 | StringCbPrintfA(buffer, sizeof(buffer), "HD1080 %.0fi %d-bit %s", FrameRate, PixelFormat, pPixelFormatLUT[(30 == PixelFormat)]);
|
---|
900 | }
|
---|
901 | }
|
---|
902 | else
|
---|
903 | {
|
---|
904 | if ((FrameRate - (int)FrameRate) > 0.01)
|
---|
905 | {
|
---|
906 | StringCbPrintfA(buffer, sizeof(buffer), "HD1080 %.2fPsF %d-bit %s", FrameRate, PixelFormat, pPixelFormatLUT[(30 == PixelFormat)]);
|
---|
907 | }
|
---|
908 | else
|
---|
909 | {
|
---|
910 | StringCbPrintfA(buffer, sizeof(buffer), "HD1080 %.0fPsF %d-bit %s", FrameRate, PixelFormat, pPixelFormatLUT[(30 == PixelFormat)]);
|
---|
911 | }
|
---|
912 | }
|
---|
913 | }
|
---|
914 |
|
---|
915 | // add the item description to combo box
|
---|
916 | int n = m_videoFormatCtrl.AddString(buffer);
|
---|
917 | // store media type pointer in item's data section
|
---|
918 | //m_videoFormatCtrl.SetItemData(n, (DWORD_PTR)pmt);
|
---|
919 | m_videoFormatCtrl.SetItemData(n, (DWORD_PTR)pmt);
|
---|
920 |
|
---|
921 | // set default format
|
---|
922 | if ((pvih->AvgTimePerFrame == m_vihDefault.AvgTimePerFrame) &&
|
---|
923 | (pvih->bmiHeader.biWidth == m_vihDefault.bmiHeader.biWidth) &&
|
---|
924 | (pvih->bmiHeader.biHeight == m_vihDefault.bmiHeader.biHeight) &&
|
---|
925 | (pvih->bmiHeader.biBitCount == m_vihDefault.bmiHeader.biBitCount))
|
---|
926 | {
|
---|
927 | m_videoFormatCtrl.SetCurSel(n);
|
---|
928 | pISC->SetFormat(pmt);
|
---|
929 | }
|
---|
930 | }
|
---|
931 | }
|
---|
932 | }
|
---|
933 | else
|
---|
934 | {
|
---|
935 | m_videoFormatCtrl.AddString("ERROR: Unable to retrieve video formats");
|
---|
936 | }
|
---|
937 | }
|
---|
938 | }
|
---|
939 | }
|
---|
940 | else
|
---|
941 | {
|
---|
942 | hr = E_POINTER;
|
---|
943 | }
|
---|
944 |
|
---|
945 | // initial
|
---|
946 | m_videoFormatCtrl.SetCurSel(4);
|
---|
947 | OnCbnSelchangeComboVideoformats();
|
---|
948 |
|
---|
949 | return hr;
|
---|
950 | }
|
---|
951 |
|
---|
952 | //-----------------------------------------------------------------------------
|
---|
953 | // PopulateAudioControl
|
---|
954 | // Fill audio format combo box with supported audio formats using the IAMStreamConfig
|
---|
955 | // interface.
|
---|
956 | HRESULT CDecklinkCaptureDlg::PopulateAudioControl()
|
---|
957 | {
|
---|
958 | HRESULT hr = S_OK;
|
---|
959 |
|
---|
960 | if (m_pAudioCapture)
|
---|
961 | {
|
---|
962 | // free mediatypes attached to format controls
|
---|
963 | int count = m_audioFormatCtrl.GetCount();
|
---|
964 | if (count)
|
---|
965 | {
|
---|
966 | for (int item=0; item<count; ++item)
|
---|
967 | {
|
---|
968 | DeleteMediaType((AM_MEDIA_TYPE*)m_audioFormatCtrl.GetItemData(item));
|
---|
969 | }
|
---|
970 | m_audioFormatCtrl.ResetContent();
|
---|
971 | }
|
---|
972 |
|
---|
973 | // locate the audio capture pin and QI for stream control
|
---|
974 | CComPtr<IAMStreamConfig> pISC = NULL;
|
---|
975 | hr = CDSUtils::FindPinInterface(m_pAudioCapture, &MEDIATYPE_Audio, PINDIR_OUTPUT, IID_IAMStreamConfig, reinterpret_cast<void**>(&pISC));
|
---|
976 | if (SUCCEEDED(hr))
|
---|
977 | {
|
---|
978 | // loop through all the capabilities (audio formats) and populate the control
|
---|
979 | int count, size;
|
---|
980 | hr = pISC->GetNumberOfCapabilities(&count, &size);
|
---|
981 | if (SUCCEEDED(hr))
|
---|
982 | {
|
---|
983 | if (sizeof(AUDIO_STREAM_CONFIG_CAPS) == size)
|
---|
984 | {
|
---|
985 | AM_MEDIA_TYPE* pmt = NULL;
|
---|
986 | AUDIO_STREAM_CONFIG_CAPS ascc;
|
---|
987 | WAVEFORMATEX* pwfex = NULL;
|
---|
988 |
|
---|
989 | for (int index=0; index<count; ++index)
|
---|
990 | {
|
---|
991 | hr = pISC->GetStreamCaps(index, &pmt, reinterpret_cast<BYTE*>(&ascc));
|
---|
992 | if (SUCCEEDED(hr))
|
---|
993 | {
|
---|
994 | char buffer[32];
|
---|
995 |
|
---|
996 | ZeroMemory(buffer, sizeof(buffer));
|
---|
997 |
|
---|
998 | pwfex = (WAVEFORMATEX*)pmt->pbFormat;
|
---|
999 |
|
---|
1000 | // provide a useful description of the formats
|
---|
1001 | if (1 == pwfex->nChannels)
|
---|
1002 | {
|
---|
1003 | StringCbPrintfA(buffer, sizeof(buffer), "%d channel, %2.1fkHz, %d-bit", (int)pwfex->nChannels, (float)pwfex->nSamplesPerSec / 1000, (int)pwfex->wBitsPerSample);
|
---|
1004 | }
|
---|
1005 | else
|
---|
1006 | {
|
---|
1007 | StringCbPrintfA(buffer, sizeof(buffer), "%d channels, %2.1fkHz, %d-bit", (int)pwfex->nChannels, (float)pwfex->nSamplesPerSec / 1000, (int)pwfex->wBitsPerSample);
|
---|
1008 | }
|
---|
1009 |
|
---|
1010 | // add the item description to combo box
|
---|
1011 | int n = m_audioFormatCtrl.AddString(buffer);
|
---|
1012 | // store media type pointer in item's data section
|
---|
1013 | m_audioFormatCtrl.SetItemData(n, (DWORD_PTR)pmt);
|
---|
1014 |
|
---|
1015 | // set default format
|
---|
1016 | if ((pwfex->wFormatTag == m_wfexDefault.wFormatTag) &&
|
---|
1017 | (pwfex->nChannels == m_wfexDefault.nChannels) &&
|
---|
1018 | (pwfex->nSamplesPerSec == m_wfexDefault.nSamplesPerSec) &&
|
---|
1019 | (pwfex->nAvgBytesPerSec == m_wfexDefault.nAvgBytesPerSec))
|
---|
1020 | {
|
---|
1021 | m_audioFormatCtrl.SetCurSel(n);
|
---|
1022 | pISC->SetFormat(pmt);
|
---|
1023 | }
|
---|
1024 | }
|
---|
1025 | }
|
---|
1026 | }
|
---|
1027 | else
|
---|
1028 | {
|
---|
1029 | m_audioFormatCtrl.AddString("ERROR: Unable to retrieve audio formats");
|
---|
1030 | }
|
---|
1031 | }
|
---|
1032 | }
|
---|
1033 | }
|
---|
1034 | else
|
---|
1035 | {
|
---|
1036 | hr = E_POINTER;
|
---|
1037 | }
|
---|
1038 |
|
---|
1039 | // initial
|
---|
1040 | m_audioFormatCtrl.SetCurSel(1);
|
---|
1041 | OnCbnSelchangeComboAudioformats();
|
---|
1042 |
|
---|
1043 | return hr;
|
---|
1044 | }
|
---|
1045 |
|
---|
1046 | //-----------------------------------------------------------------------------
|
---|
1047 | // PopulateCompressionControl
|
---|
1048 | // Fill compression control with a selection of video compressors, locate the
|
---|
1049 | // encoders and add them to the combo box if they exist.
|
---|
1050 | HRESULT CDecklinkCaptureDlg::PopulateCompressionControl()
|
---|
1051 | {
|
---|
1052 | // Pixel format
|
---|
1053 | int n = m_compressionCtrl.AddString("RGB24");
|
---|
1054 | m_compressionCtrl.SetItemData(n, (DWORD_PTR)RGB24);
|
---|
1055 |
|
---|
1056 | n = m_compressionCtrl.AddString("RGB16");
|
---|
1057 | m_compressionCtrl.SetItemData(n, (DWORD_PTR)RGB16);
|
---|
1058 |
|
---|
1059 | n = m_compressionCtrl.AddString("YUV");
|
---|
1060 | m_compressionCtrl.SetItemData(n, (DWORD_PTR)YUV);
|
---|
1061 |
|
---|
1062 | // Frame rate
|
---|
1063 | n = m_framerateCtrl.AddString("10fps");
|
---|
1064 | m_framerateCtrl.SetItemData(n, (DWORD_PTR)FR10);
|
---|
1065 | n = m_framerateCtrl.AddString("15fps");
|
---|
1066 | m_framerateCtrl.SetItemData(n, (DWORD_PTR)FR15);
|
---|
1067 | n = m_framerateCtrl.AddString("20fps");
|
---|
1068 | m_framerateCtrl.SetItemData(n, (DWORD_PTR)FR20);
|
---|
1069 | n = m_framerateCtrl.AddString("25fps");
|
---|
1070 | m_framerateCtrl.SetItemData(n, (DWORD_PTR)FR25);
|
---|
1071 | n = m_framerateCtrl.AddString("30fps");
|
---|
1072 | m_framerateCtrl.SetItemData(n, (DWORD_PTR)FR30);
|
---|
1073 |
|
---|
1074 | /*
|
---|
1075 | // search for the DV encoder, MPEG encoder and WM encoder
|
---|
1076 | IBaseFilter* pFilter = NULL;
|
---|
1077 | HRESULT hr = CoCreateInstance(CLSID_DVVideoEnc, 0, CLSCTX_INPROC_SERVER, IID_IBaseFilter, reinterpret_cast<void**>(&pFilter));
|
---|
1078 | if (SUCCEEDED(hr))
|
---|
1079 | {
|
---|
1080 | n = m_compressionCtrl.SetCurSel(m_compressionCtrl.AddString("DV Video Encoder"));
|
---|
1081 | m_compressionCtrl.SetItemData(n, (DWORD_PTR)ENC_DV);
|
---|
1082 | SAFE_RELEASE(pFilter);
|
---|
1083 | }
|
---|
1084 |
|
---|
1085 | hr = CoCreateInstance(CLSID_WMAsfWriter, 0, CLSCTX_INPROC_SERVER, IID_IBaseFilter, reinterpret_cast<void**>(&pFilter));
|
---|
1086 | if (SUCCEEDED(hr))
|
---|
1087 | {
|
---|
1088 | n = m_compressionCtrl.SetCurSel(m_compressionCtrl.AddString("Windows Media Encoder"));
|
---|
1089 | m_compressionCtrl.SetItemData(n, (DWORD_PTR)ENC_WM);
|
---|
1090 | SAFE_RELEASE(pFilter);
|
---|
1091 | }
|
---|
1092 | */
|
---|
1093 | // initial
|
---|
1094 | m_compressor =2;
|
---|
1095 | m_compressionCtrl.SetCurSel(m_compressor);
|
---|
1096 | //OnCbnSelchangeComboCompression();
|
---|
1097 |
|
---|
1098 | m_framerate = 2; // 20fps
|
---|
1099 | m_framerateCtrl.SetCurSel(m_framerate);
|
---|
1100 | FrameRate = FR20;
|
---|
1101 |
|
---|
1102 | return S_OK;
|
---|
1103 | }
|
---|
1104 |
|
---|
1105 | //-----------------------------------------------------------------------------
|
---|
1106 | // OnCbnSelchangeComboVideodevice
|
---|
1107 | // Rebuild graph with selected capture device
|
---|
1108 | void CDecklinkCaptureDlg::OnCbnSelchangeComboVideodevice()
|
---|
1109 | {
|
---|
1110 | SAFE_RELEASE(m_pVideoCapture); // release our outstanding reference
|
---|
1111 | // remove intermediate filters, since the device selection has changed the capture device will also be removed
|
---|
1112 | HRESULT hr = DestroyGraph();
|
---|
1113 | if (SUCCEEDED(hr))
|
---|
1114 | {
|
---|
1115 | // rebuild graph with new capture device selection
|
---|
1116 | PWSTR pName = (PWSTR)m_videoDeviceCtrl.GetItemData(m_videoDeviceCtrl.GetCurSel());
|
---|
1117 | if (pName)
|
---|
1118 | {
|
---|
1119 | hr = CDSUtils::AddFilter2(m_pGraph, CLSID_VideoInputDeviceCategory, pName, &m_pVideoCapture);
|
---|
1120 | if (SUCCEEDED(hr))
|
---|
1121 | {
|
---|
1122 | // as the device has changed get the current operating format so that the control
|
---|
1123 | // and display this as the current selection
|
---|
1124 | CComPtr<IAMStreamConfig> pISC = NULL;
|
---|
1125 | hr = CDSUtils::FindPinInterface(m_pVideoCapture, &MEDIATYPE_Video, PINDIR_OUTPUT, IID_IAMStreamConfig, reinterpret_cast<void**>(&pISC));
|
---|
1126 | if (SUCCEEDED(hr))
|
---|
1127 | {
|
---|
1128 | // get the current format of the device to set the current selection of the control
|
---|
1129 | AM_MEDIA_TYPE* pamt = NULL;
|
---|
1130 | hr = pISC->GetFormat(&pamt);
|
---|
1131 | if (SUCCEEDED(hr))
|
---|
1132 | {
|
---|
1133 | if (pamt->pbFormat)
|
---|
1134 | {
|
---|
1135 | m_vihDefault = *(VIDEOINFOHEADER*)pamt->pbFormat;
|
---|
1136 | }
|
---|
1137 | DeleteMediaType(pamt);
|
---|
1138 | }
|
---|
1139 | }
|
---|
1140 |
|
---|
1141 | hr = PopulateVideoControl(); // repopulate the control with formats from the new device
|
---|
1142 | if (SUCCEEDED(hr))
|
---|
1143 | {
|
---|
1144 | hr = CreatePreviewGraph(); // rebuild the graph with the new device
|
---|
1145 | }
|
---|
1146 | }
|
---|
1147 | }
|
---|
1148 | else
|
---|
1149 | {
|
---|
1150 | hr = E_POINTER;
|
---|
1151 | }
|
---|
1152 | }
|
---|
1153 | }
|
---|
1154 |
|
---|
1155 | //-----------------------------------------------------------------------------
|
---|
1156 | // OnCbnSelchangeComboAudiodevice
|
---|
1157 | // Rebuild graph with selected capture device
|
---|
1158 | void CDecklinkCaptureDlg::OnCbnSelchangeComboAudiodevice()
|
---|
1159 | {
|
---|
1160 | SAFE_RELEASE(m_pAudioCapture); // release our outstanding reference
|
---|
1161 | // remove intermediate filters, since the device selection has changed the capture device will also be removed
|
---|
1162 | HRESULT hr = DestroyGraph();
|
---|
1163 | if (SUCCEEDED(hr))
|
---|
1164 | {
|
---|
1165 | PWSTR pName = (PWSTR)m_audioDeviceCtrl.GetItemData(m_audioDeviceCtrl.GetCurSel());
|
---|
1166 | if (pName)
|
---|
1167 | {
|
---|
1168 | hr = CDSUtils::AddFilter2(m_pGraph, CLSID_AudioInputDeviceCategory, pName, &m_pAudioCapture);
|
---|
1169 | if (SUCCEEDED(hr))
|
---|
1170 | {
|
---|
1171 | // as the device has changed get the current operating format so that the control
|
---|
1172 | // and display this as the current selection
|
---|
1173 | CComPtr<IAMStreamConfig> pISC = NULL;
|
---|
1174 | hr = CDSUtils::FindPinInterface(m_pAudioCapture, &MEDIATYPE_Audio, PINDIR_OUTPUT, IID_IAMStreamConfig, reinterpret_cast<void**>(&pISC));
|
---|
1175 | if (SUCCEEDED(hr))
|
---|
1176 | {
|
---|
1177 | // get the current format of the device to set the current selection of the control
|
---|
1178 | AM_MEDIA_TYPE* pamt = NULL;
|
---|
1179 | hr = pISC->GetFormat(&pamt);
|
---|
1180 | if (SUCCEEDED(hr))
|
---|
1181 | {
|
---|
1182 | if (pamt->pbFormat)
|
---|
1183 | {
|
---|
1184 | m_wfexDefault = *(WAVEFORMATEX*)pamt->pbFormat;
|
---|
1185 | }
|
---|
1186 | DeleteMediaType(pamt);
|
---|
1187 | }
|
---|
1188 | }
|
---|
1189 |
|
---|
1190 | hr = PopulateAudioControl(); // repopulate the control with formats from the new device
|
---|
1191 | if (SUCCEEDED(hr))
|
---|
1192 | {
|
---|
1193 | hr = CreatePreviewGraph(); // rebuild the graph with the new device
|
---|
1194 | }
|
---|
1195 | }
|
---|
1196 | }
|
---|
1197 | else
|
---|
1198 | {
|
---|
1199 | hr = E_POINTER;
|
---|
1200 | }
|
---|
1201 | }
|
---|
1202 | }
|
---|
1203 |
|
---|
1204 | //-----------------------------------------------------------------------------
|
---|
1205 | // OnCbnSelchangeComboVideoformats
|
---|
1206 | // Rebuild preview graph if format selection changed
|
---|
1207 | void CDecklinkCaptureDlg::OnCbnSelchangeComboVideoformats()
|
---|
1208 | {
|
---|
1209 | HRESULT hr = DestroyGraph();
|
---|
1210 |
|
---|
1211 | if (SUCCEEDED(hr))
|
---|
1212 | {
|
---|
1213 | // locate the video capture pin and QI for stream control
|
---|
1214 | CComPtr<IAMStreamConfig> pISC = NULL;
|
---|
1215 | hr = CDSUtils::FindPinInterface(m_pVideoCapture, &MEDIATYPE_Video, PINDIR_OUTPUT, IID_IAMStreamConfig, reinterpret_cast<void**>(&pISC));
|
---|
1216 | if (SUCCEEDED(hr))
|
---|
1217 | {
|
---|
1218 | // set the new media format
|
---|
1219 | AM_MEDIA_TYPE* pmt = (AM_MEDIA_TYPE*)m_videoFormatCtrl.GetItemData(m_videoFormatCtrl.GetCurSel());
|
---|
1220 | m_vihDefault = *(VIDEOINFOHEADER*)pmt->pbFormat;
|
---|
1221 | ASSERT(sizeof(VIDEOINFOHEADER) <= pmt->cbFormat);
|
---|
1222 | hr = pISC->SetFormat(pmt);
|
---|
1223 | if (SUCCEEDED(hr))
|
---|
1224 | {
|
---|
1225 | // save the new format
|
---|
1226 | //ASSERT(ERROR_SUCCESS == m_regUtils.SetBinary("VideoFormat", reinterpret_cast<const BYTE*>(&m_vihDefault), sizeof(m_vihDefault)));
|
---|
1227 | m_regUtils.SetBinary("VideoFormat", reinterpret_cast<const BYTE*>(&m_vihDefault), sizeof(m_vihDefault));
|
---|
1228 |
|
---|
1229 |
|
---|
1230 | // update compression control, we don't have an HD compression filter so disable compression for HD formats
|
---|
1231 | if (576 < m_vihDefault.bmiHeader.biHeight)
|
---|
1232 | {
|
---|
1233 | m_compressor = 2;
|
---|
1234 | m_compressionCtrl.SetCurSel(m_compressor);
|
---|
1235 | // save the new state
|
---|
1236 | //ASSERT(ERROR_SUCCESS == m_regUtils.SetBinary("VideoDecoder", reinterpret_cast<const BYTE*>(&m_compressor), sizeof(m_compressor)));
|
---|
1237 | m_regUtils.SetBinary("VideoDecoder", reinterpret_cast<const BYTE*>(&m_compressor), sizeof(m_compressor));
|
---|
1238 |
|
---|
1239 | m_bEnableCompressionCtrl = FALSE;
|
---|
1240 | }
|
---|
1241 | else
|
---|
1242 | {
|
---|
1243 | m_bEnableCompressionCtrl = TRUE;
|
---|
1244 | }
|
---|
1245 | EnableControls();
|
---|
1246 |
|
---|
1247 | // rebuild the graph
|
---|
1248 | hr = CreatePreviewGraph();
|
---|
1249 | }
|
---|
1250 | }
|
---|
1251 | }
|
---|
1252 | }
|
---|
1253 |
|
---|
1254 | //-----------------------------------------------------------------------------
|
---|
1255 | // OnCbnSelchangeComboAudioformats
|
---|
1256 | // Rebuild preview graph if format selection changed
|
---|
1257 | void CDecklinkCaptureDlg::OnCbnSelchangeComboAudioformats()
|
---|
1258 | {
|
---|
1259 | HRESULT hr = DestroyGraph();
|
---|
1260 | if (SUCCEEDED(hr))
|
---|
1261 | {
|
---|
1262 | // locate the audio capture pin and QI for stream control
|
---|
1263 | CComPtr<IAMStreamConfig> pISC = NULL;
|
---|
1264 | hr = CDSUtils::FindPinInterface(m_pAudioCapture, &MEDIATYPE_Audio, PINDIR_OUTPUT, IID_IAMStreamConfig, reinterpret_cast<void**>(&pISC));
|
---|
1265 | if (SUCCEEDED(hr))
|
---|
1266 | {
|
---|
1267 | // set the new media format
|
---|
1268 | AM_MEDIA_TYPE* pmt = (AM_MEDIA_TYPE*)m_audioFormatCtrl.GetItemData(m_audioFormatCtrl.GetCurSel());
|
---|
1269 | m_wfexDefault = *(WAVEFORMATEX*)pmt->pbFormat;
|
---|
1270 | ASSERT(sizeof(WAVEFORMATEX) == pmt->cbFormat);
|
---|
1271 | hr = pISC->SetFormat(pmt);
|
---|
1272 | if (SUCCEEDED(hr))
|
---|
1273 | {
|
---|
1274 | // save the new format
|
---|
1275 | //EXECUTE_ASSERT(ERROR_SUCCESS == m_regUtils.SetBinary("AudioFormat", reinterpret_cast<const BYTE*>(&m_wfexDefault), sizeof(m_wfexDefault)));
|
---|
1276 | m_regUtils.SetBinary("AudioFormat", reinterpret_cast<const BYTE*>(&m_wfexDefault), sizeof(m_wfexDefault));
|
---|
1277 |
|
---|
1278 | // rebuild the graph
|
---|
1279 | hr = CreatePreviewGraph();
|
---|
1280 | }
|
---|
1281 | }
|
---|
1282 | }
|
---|
1283 | }
|
---|
1284 |
|
---|
1285 | //-----------------------------------------------------------------------------
|
---|
1286 | // OnCbnSelchangeComboCompression
|
---|
1287 | // Rebuild preview graph if compression selection changed
|
---|
1288 | void CDecklinkCaptureDlg::OnCbnSelchangeComboCompression()
|
---|
1289 | {
|
---|
1290 | /*
|
---|
1291 | HRESULT hr = DestroyGraph();
|
---|
1292 | if (SUCCEEDED(hr))
|
---|
1293 | {
|
---|
1294 | // save the new state
|
---|
1295 | m_compressor = m_compressionCtrl.GetCurSel();
|
---|
1296 | EXECUTE_ASSERT(ERROR_SUCCESS == m_regUtils.SetBinary("VideoDecoder", reinterpret_cast<const BYTE*>(&m_compressor), sizeof(m_compressor)));
|
---|
1297 | // rebuild the graph
|
---|
1298 | hr = CreatePreviewGraph();
|
---|
1299 | }
|
---|
1300 | */
|
---|
1301 | }
|
---|
1302 |
|
---|
1303 | //-----------------------------------------------------------------------------
|
---|
1304 | // OnBnClickedCheckAudiomute
|
---|
1305 | // Rebuild the capture graph to reflect the new audio setting
|
---|
1306 | void CDecklinkCaptureDlg::OnBnClickedCheckAudiomute()
|
---|
1307 | {
|
---|
1308 | CButton* pCheck = (CButton*)GetDlgItem(IDC_CHECK_AUDIOMUTE);
|
---|
1309 | if (pCheck)
|
---|
1310 | {
|
---|
1311 | m_bAudioMute = pCheck->GetState() & 0x0003;
|
---|
1312 | // hjhur
|
---|
1313 | grabberCallback.m_GrabProc.SetAudioOn(!m_bAudioMute);
|
---|
1314 |
|
---|
1315 | HRESULT hr = DestroyGraph();
|
---|
1316 | if (SUCCEEDED(hr))
|
---|
1317 | {
|
---|
1318 | // save the new state
|
---|
1319 | //EXECUTE_ASSERT(ERROR_SUCCESS == m_regUtils.SetBinary("AudioMute", reinterpret_cast<const BYTE*>(&m_bAudioMute), sizeof(m_bAudioMute)));
|
---|
1320 |
|
---|
1321 | m_regUtils.SetBinary("AudioMute", reinterpret_cast<const BYTE*>(&m_bAudioMute), sizeof(m_bAudioMute));
|
---|
1322 |
|
---|
1323 | // rebuild the graph which reflects the new audio setting
|
---|
1324 | hr = CreatePreviewGraph();
|
---|
1325 | }
|
---|
1326 | }
|
---|
1327 | }
|
---|
1328 |
|
---|
1329 | //-----------------------------------------------------------------------------
|
---|
1330 | // OnBnClickedButtonBrowse
|
---|
1331 | // Create a file open dialog to browse for a file location
|
---|
1332 | void CDecklinkCaptureDlg::OnBnClickedButtonBrowse()
|
---|
1333 | {
|
---|
1334 | char BASED_CODE szFilters[] = "Windows Media Files|*.avi;*.asf;*.wmv|All Files (*.*)|*.*||";
|
---|
1335 |
|
---|
1336 | char* pExt[] = {"*.avi", "*.avi", "*.asf;*.wmv"};
|
---|
1337 |
|
---|
1338 | CFileDialog FileDlg(TRUE, "Windows Media Files", pExt[m_compressor], 0, szFilters, this);
|
---|
1339 |
|
---|
1340 | if (FileDlg.DoModal() == IDOK)
|
---|
1341 | {
|
---|
1342 | m_captureFile = FileDlg.GetPathName();
|
---|
1343 | m_captureFileCtrl.SetWindowText(m_captureFile);
|
---|
1344 | }
|
---|
1345 | }
|
---|
1346 |
|
---|
1347 | //-----------------------------------------------------------------------------
|
---|
1348 | // OnBnClickedButtonCapture
|
---|
1349 | // Create a capture graph a start capture
|
---|
1350 | void CDecklinkCaptureDlg::OnBnClickedButtonCapture()
|
---|
1351 | {
|
---|
1352 | DisableControls();
|
---|
1353 |
|
---|
1354 | HRESULT hr = DestroyGraph();
|
---|
1355 | if (SUCCEEDED(hr))
|
---|
1356 | {
|
---|
1357 | hr = CreateCaptureGraph();
|
---|
1358 | if (SUCCEEDED(hr))
|
---|
1359 | {
|
---|
1360 | DisableControls();
|
---|
1361 | return;
|
---|
1362 | }
|
---|
1363 | }
|
---|
1364 |
|
---|
1365 | hr = DestroyGraph();
|
---|
1366 | if (SUCCEEDED(hr))
|
---|
1367 | {
|
---|
1368 | hr = CreatePreviewGraph();
|
---|
1369 | if (SUCCEEDED(hr))
|
---|
1370 | {
|
---|
1371 | EnableControls();
|
---|
1372 | }
|
---|
1373 | }
|
---|
1374 |
|
---|
1375 | exit(1);
|
---|
1376 | return;
|
---|
1377 | }
|
---|
1378 |
|
---|
1379 | //-----------------------------------------------------------------------------
|
---|
1380 | // OnBnClickedButtonStop
|
---|
1381 | // Stop capture and revert to preview
|
---|
1382 | void CDecklinkCaptureDlg::OnBnClickedButtonStop()
|
---|
1383 | {
|
---|
1384 | HRESULT hr = DestroyGraph();
|
---|
1385 | grabberCallback.m_GrabProc.Shutdown();
|
---|
1386 | if (SUCCEEDED(hr))
|
---|
1387 | {
|
---|
1388 | hr = CreatePreviewGraph();
|
---|
1389 | if (SUCCEEDED(hr))
|
---|
1390 | {
|
---|
1391 | EnableControls();
|
---|
1392 | }
|
---|
1393 | }
|
---|
1394 | }
|
---|
1395 |
|
---|
1396 | //-----------------------------------------------------------------------------
|
---|
1397 | // EnableControls
|
---|
1398 | //
|
---|
1399 | void CDecklinkCaptureDlg::EnableControls(void)
|
---|
1400 | {
|
---|
1401 | CWnd* pWnd = GetDlgItem(IDC_COMBO_VIDEODEVICE);
|
---|
1402 | pWnd->EnableWindow(TRUE);
|
---|
1403 | pWnd = GetDlgItem(IDC_COMBO_VIDEOFORMATS);
|
---|
1404 | pWnd->EnableWindow(TRUE);
|
---|
1405 | pWnd = GetDlgItem(IDC_COMBO_AUDIODEVICE);
|
---|
1406 | pWnd->EnableWindow(TRUE);
|
---|
1407 | pWnd = GetDlgItem(IDC_COMBO_AUDIOFORMATS);
|
---|
1408 | //pWnd->EnableWindow(TRUE);
|
---|
1409 | pWnd->EnableWindow(FALSE);
|
---|
1410 |
|
---|
1411 | pWnd = GetDlgItem(IDC_CHECK_AUDIOMUTE);
|
---|
1412 | pWnd->EnableWindow(TRUE);
|
---|
1413 | pWnd = GetDlgItem(IDC_COMBO_COMPRESSION);
|
---|
1414 | pWnd->EnableWindow(TRUE);
|
---|
1415 | // m_bEnableCompressionCtrl = (576 < m_vihDefault.bmiHeader.biHeight) ? FALSE : TRUE; // don't have an HDV codec do disable compression control for HD formats
|
---|
1416 | // pWnd->EnableWindow(m_bEnableCompressionCtrl);
|
---|
1417 | pWnd = GetDlgItem(IDC_EDIT_CAPTUREFILE);
|
---|
1418 | pWnd->EnableWindow(FALSE);
|
---|
1419 | pWnd = GetDlgItem(IDC_BUTTON_BROWSE);
|
---|
1420 | pWnd->EnableWindow(FALSE);
|
---|
1421 | pWnd = GetDlgItem(IDC_BUTTON_CAPTURE);
|
---|
1422 | pWnd->EnableWindow(FALSE);
|
---|
1423 | pWnd = GetDlgItem(IDC_BUTTON_STOP);
|
---|
1424 | pWnd->EnableWindow(FALSE);
|
---|
1425 | pWnd = GetDlgItem(IDC_SAGE_ADDR);
|
---|
1426 | pWnd->EnableWindow(TRUE);
|
---|
1427 | pWnd = GetDlgItem(IDC_SAGE_START);
|
---|
1428 | pWnd->EnableWindow(TRUE);
|
---|
1429 | pWnd = GetDlgItem(IDC_SAGE_STOP);
|
---|
1430 | pWnd->EnableWindow(FALSE);
|
---|
1431 | }
|
---|
1432 |
|
---|
1433 | //-----------------------------------------------------------------------------
|
---|
1434 | // DisableControls
|
---|
1435 | //
|
---|
1436 | void CDecklinkCaptureDlg::DisableControls(void)
|
---|
1437 | {
|
---|
1438 | CWnd* pWnd = GetDlgItem(IDC_COMBO_VIDEODEVICE);
|
---|
1439 | pWnd->EnableWindow(FALSE);
|
---|
1440 | pWnd = GetDlgItem(IDC_COMBO_VIDEOFORMATS);
|
---|
1441 | pWnd->EnableWindow(FALSE);
|
---|
1442 | pWnd = GetDlgItem(IDC_COMBO_AUDIODEVICE);
|
---|
1443 | pWnd->EnableWindow(FALSE);
|
---|
1444 | pWnd = GetDlgItem(IDC_COMBO_AUDIOFORMATS);
|
---|
1445 | pWnd->EnableWindow(FALSE);
|
---|
1446 | pWnd = GetDlgItem(IDC_CHECK_AUDIOMUTE);
|
---|
1447 | pWnd->EnableWindow(FALSE);
|
---|
1448 | pWnd = GetDlgItem(IDC_COMBO_COMPRESSION);
|
---|
1449 | pWnd->EnableWindow(FALSE);
|
---|
1450 | pWnd = GetDlgItem(IDC_EDIT_CAPTUREFILE);
|
---|
1451 | pWnd->EnableWindow(FALSE);
|
---|
1452 | pWnd = GetDlgItem(IDC_BUTTON_BROWSE);
|
---|
1453 | pWnd->EnableWindow(FALSE);
|
---|
1454 | pWnd = GetDlgItem(IDC_BUTTON_CAPTURE);
|
---|
1455 | pWnd->EnableWindow(FALSE);
|
---|
1456 | pWnd = GetDlgItem(IDC_BUTTON_STOP);
|
---|
1457 | pWnd->EnableWindow(FALSE);
|
---|
1458 | pWnd = GetDlgItem(IDC_SAGE_ADDR);
|
---|
1459 | pWnd->EnableWindow(FALSE);
|
---|
1460 | pWnd = GetDlgItem(IDC_SAGE_START);
|
---|
1461 | pWnd->EnableWindow(FALSE);
|
---|
1462 | pWnd = GetDlgItem(IDC_SAGE_STOP);
|
---|
1463 | pWnd->EnableWindow(TRUE);
|
---|
1464 | }
|
---|
1465 |
|
---|
1466 | //-----------------------------------------------------------------------------
|
---|
1467 | // QueryRegistry
|
---|
1468 | // retrieve previous media formats from registry
|
---|
1469 | void CDecklinkCaptureDlg::QueryRegistry(void)
|
---|
1470 | {
|
---|
1471 | if (ERROR_SUCCESS == m_regUtils.Open("DecklinkCaptureSample"))
|
---|
1472 | {
|
---|
1473 | EXECUTE_ASSERT(ERROR_SUCCESS == m_regUtils.GetBinary("VideoFormat", reinterpret_cast<LPBYTE>(&m_vihDefault), sizeof(m_vihDefault)));
|
---|
1474 | EXECUTE_ASSERT(ERROR_SUCCESS == m_regUtils.GetBinary("AudioFormat", reinterpret_cast<LPBYTE>(&m_wfexDefault), sizeof(m_wfexDefault)));
|
---|
1475 | EXECUTE_ASSERT(ERROR_SUCCESS == m_regUtils.GetBinary("AudioMute", reinterpret_cast<LPBYTE>(&m_bAudioMute), sizeof(m_bAudioMute)));
|
---|
1476 | EXECUTE_ASSERT(ERROR_SUCCESS == m_regUtils.GetBinary("VideoDecoder", reinterpret_cast<LPBYTE>(&m_compressor), sizeof(m_compressor)));
|
---|
1477 |
|
---|
1478 | WCHAR captureFile[MAX_PATH];
|
---|
1479 | ZeroMemory(captureFile, sizeof(captureFile));
|
---|
1480 | // EXECUTE_ASSERT(ERROR_SUCCESS == m_regUtils.GetString("CaptureFile", reinterpret_cast<LPBYTE>(captureFile), sizeof(captureFile)));
|
---|
1481 | m_captureFile = captureFile;
|
---|
1482 | }
|
---|
1483 | else
|
---|
1484 | {
|
---|
1485 | // create the key and registry values
|
---|
1486 | if (ERROR_SUCCESS == m_regUtils.Create("DecklinkCaptureSample"))
|
---|
1487 | {
|
---|
1488 | EXECUTE_ASSERT(ERROR_SUCCESS == m_regUtils.SetBinary("VideoFormat", reinterpret_cast<const BYTE*>(&m_vihDefault), sizeof(m_vihDefault)));
|
---|
1489 | EXECUTE_ASSERT(ERROR_SUCCESS == m_regUtils.SetBinary("AudioFormat", reinterpret_cast<const BYTE*>(&m_wfexDefault), sizeof(m_wfexDefault)));
|
---|
1490 | EXECUTE_ASSERT(ERROR_SUCCESS == m_regUtils.SetBinary("AudioMute", reinterpret_cast<const BYTE*>(&m_bAudioMute), sizeof(m_bAudioMute)));
|
---|
1491 | EXECUTE_ASSERT(ERROR_SUCCESS == m_regUtils.SetBinary("VideoDecoder", reinterpret_cast<const BYTE*>(&m_compressor), sizeof(m_compressor)));
|
---|
1492 | }
|
---|
1493 | }
|
---|
1494 |
|
---|
1495 | // update mute audio check box control
|
---|
1496 | CButton* pButton = (CButton*)GetDlgItem(IDC_CHECK_AUDIOMUTE);
|
---|
1497 | pButton->SetCheck(m_bAudioMute);
|
---|
1498 | }
|
---|
1499 |
|
---|
1500 |
|
---|
1501 | void CDecklinkCaptureDlg::OnBnClickedButtonCodecproperties()
|
---|
1502 | {
|
---|
1503 | // TODO: Add your control notification handler code here
|
---|
1504 | }
|
---|
1505 |
|
---|
1506 | void CDecklinkCaptureDlg::OnBnClickedSageStart()
|
---|
1507 | {
|
---|
1508 | int index = m_sageStreamCtrl.GetCurSel();
|
---|
1509 | //ChangeMasterIP(index);
|
---|
1510 | CString iptxt;
|
---|
1511 | m_sageIPCtrl.GetWindowTextA(iptxt);
|
---|
1512 | grabberCallback.m_GrabProc.setIP(iptxt);
|
---|
1513 |
|
---|
1514 | // TODO: Add your control notification handler code here
|
---|
1515 | DisableControls();
|
---|
1516 |
|
---|
1517 | HRESULT hr = DestroyGraph();
|
---|
1518 | if (SUCCEEDED(hr))
|
---|
1519 | {
|
---|
1520 | hr = CreateCaptureGraph();
|
---|
1521 | if (SUCCEEDED(hr))
|
---|
1522 | {
|
---|
1523 | DisableControls();
|
---|
1524 | return;
|
---|
1525 | }
|
---|
1526 | }
|
---|
1527 |
|
---|
1528 | hr = DestroyGraph();
|
---|
1529 | if (SUCCEEDED(hr))
|
---|
1530 | {
|
---|
1531 | hr = CreatePreviewGraph();
|
---|
1532 | if (SUCCEEDED(hr))
|
---|
1533 | {
|
---|
1534 | EnableControls();
|
---|
1535 | }
|
---|
1536 | }
|
---|
1537 |
|
---|
1538 | exit(1);
|
---|
1539 | return;
|
---|
1540 | }
|
---|
1541 |
|
---|
1542 | void CDecklinkCaptureDlg::OnBnClickedSageStop()
|
---|
1543 | {
|
---|
1544 | // TODO: Add your control notification handler code here
|
---|
1545 | HRESULT hr = DestroyGraph();
|
---|
1546 | if (SUCCEEDED(hr))
|
---|
1547 | {
|
---|
1548 | hr = CreatePreviewGraph();
|
---|
1549 | if (SUCCEEDED(hr))
|
---|
1550 | {
|
---|
1551 | EnableControls();
|
---|
1552 | }
|
---|
1553 | }
|
---|
1554 | grabberCallback.m_GrabProc.Shutdown();
|
---|
1555 |
|
---|
1556 | exit(1);
|
---|
1557 | }
|
---|
1558 |
|
---|
1559 | void CDecklinkCaptureDlg::OnCbnSelchangeSageAddr()
|
---|
1560 | {
|
---|
1561 | // TODO: Add your control notification handler code here
|
---|
1562 | int index = m_sageStreamCtrl.GetCurSel();
|
---|
1563 |
|
---|
1564 | CString iptxt;
|
---|
1565 | m_sageStreamCtrl.GetWindowTextA(iptxt);
|
---|
1566 |
|
---|
1567 | m_sageIPCtrl.SetWindowText(iptxt);
|
---|
1568 |
|
---|
1569 | /*
|
---|
1570 | switch (index)
|
---|
1571 | {
|
---|
1572 | case 0:
|
---|
1573 | m_sageIPCtrl.SetWindowText("67.58.62.100");
|
---|
1574 | break;
|
---|
1575 | case 1:
|
---|
1576 | m_sageIPCtrl.SetWindowText("67.58.62.21");
|
---|
1577 | break;
|
---|
1578 | case 2:
|
---|
1579 | m_sageIPCtrl.SetWindowText("67.58.62.23");
|
---|
1580 | break;
|
---|
1581 | }
|
---|
1582 | */
|
---|
1583 | //ChangeMasterIP(index);
|
---|
1584 | }
|
---|
1585 |
|
---|
1586 | void CDecklinkCaptureDlg::OnEnChangeSageEditIp()
|
---|
1587 | {
|
---|
1588 | // TODO: If this is a RICHEDIT control, the control will not
|
---|
1589 | // send this notification unless you override the CDialog::OnInitDialog()
|
---|
1590 | // function and call CRichEditCtrl().SetEventMask()
|
---|
1591 | // with the ENM_CHANGE flag ORed into the mask.
|
---|
1592 |
|
---|
1593 | // TODO: Add your control notification handler code here
|
---|
1594 | }
|
---|
1595 |
|
---|
1596 | int CDecklinkCaptureDlg::ChangeMasterIP(int index)
|
---|
1597 | {
|
---|
1598 | char fileName[100];
|
---|
1599 | memset(fileName,' ', 100);
|
---|
1600 | char *sageDir = getenv("SAGE_DIRECTORY");
|
---|
1601 | sprintf(fileName, "%s/bin/fsManager.conf", sageDir);
|
---|
1602 |
|
---|
1603 | FILE *fp = fopen(fileName, "w+");
|
---|
1604 |
|
---|
1605 | if (!fp) {
|
---|
1606 | printf("fail to open fsClient config file\n");
|
---|
1607 | return -1;
|
---|
1608 | }
|
---|
1609 |
|
---|
1610 | CString iptxt;
|
---|
1611 | m_sageIPCtrl.GetWindowTextA(iptxt);
|
---|
1612 | if(iptxt == "") return -1;
|
---|
1613 | char sip[25];
|
---|
1614 | sprintf(sip, "%s", iptxt.GetBuffer());
|
---|
1615 |
|
---|
1616 | switch (index)
|
---|
1617 | {
|
---|
1618 | case 0:
|
---|
1619 | fprintf(fp, "fsManager global %s\nsystemPort 20002\n",sip);
|
---|
1620 | break;
|
---|
1621 | case 1:
|
---|
1622 | fprintf(fp, "fsManager global %s\nsystemPort 20002\n",sip);
|
---|
1623 | break;
|
---|
1624 | case 2:
|
---|
1625 | fprintf(fp, "fsManager global %s\nsystemPort 20002\n",sip);
|
---|
1626 | break;
|
---|
1627 | }
|
---|
1628 |
|
---|
1629 | fprintf(fp, "receiverBufSize 100\n");
|
---|
1630 | fprintf(fp, "rcvNwBufSize 16777216\n");
|
---|
1631 | fprintf(fp, "sendNwBufSize 1048576\n");
|
---|
1632 | fprintf(fp, "MTU 8800\n");
|
---|
1633 |
|
---|
1634 | fclose(fp);
|
---|
1635 | return 1;
|
---|
1636 | }
|
---|
1637 | void CDecklinkCaptureDlg::OnBnClickedSageReg()
|
---|
1638 | {
|
---|
1639 | char fileName[100];
|
---|
1640 | memset(fileName,' ', 100);
|
---|
1641 | char *sageDir = getenv("SAGE_DIRECTORY");
|
---|
1642 | sprintf(fileName, "%s/bin/ips.sys", sageDir);
|
---|
1643 |
|
---|
1644 | FILE *fp = fopen(fileName, "a+");
|
---|
1645 |
|
---|
1646 | if (!fp) {
|
---|
1647 | printf("fail to open fsClient config file\n");
|
---|
1648 | return;
|
---|
1649 | }
|
---|
1650 |
|
---|
1651 | fseek(fp, 0, SEEK_END);
|
---|
1652 | CString ip;
|
---|
1653 | m_sageIPCtrl.GetWindowTextA(ip);
|
---|
1654 |
|
---|
1655 | fprintf(fp, "\n%s",ip.GetBuffer());
|
---|
1656 |
|
---|
1657 | char sip[255];
|
---|
1658 | memset(sip, 0, 255);
|
---|
1659 | sprintf(sip, "%s", ip.GetBuffer());
|
---|
1660 |
|
---|
1661 | int index = m_sageStreamCtrl.GetCount();
|
---|
1662 | m_sageStreamCtrl.InsertString(index, sip);
|
---|
1663 |
|
---|
1664 | fclose(fp);
|
---|
1665 | }
|
---|
1666 |
|
---|
1667 |
|
---|
1668 | int CDecklinkCaptureDlg::LoadIpInfo()
|
---|
1669 | {
|
---|
1670 | char fileName[100];
|
---|
1671 | memset(fileName,' ', 100);
|
---|
1672 | char *sageDir = getenv("SAGE_DIRECTORY");
|
---|
1673 | sprintf(fileName, "%s/bin/ips.sys", sageDir);
|
---|
1674 | //sprintf(fileName, "C:/sources/bijeong/bin/ips.sys");
|
---|
1675 |
|
---|
1676 | FILE *fp = fopen(fileName, "r+");
|
---|
1677 |
|
---|
1678 | if (!fp) {
|
---|
1679 | printf("fail to open fsClient config file\n");
|
---|
1680 | return -1;
|
---|
1681 | }
|
---|
1682 |
|
---|
1683 | char sip[255];
|
---|
1684 | memset(sip, 0, 255);
|
---|
1685 | int index =0;
|
---|
1686 |
|
---|
1687 | do {
|
---|
1688 | fscanf(fp, "%s", &sip);
|
---|
1689 | if(strlen(sip) > 0) {
|
---|
1690 | if(index == 0) {
|
---|
1691 | m_sageIPCtrl.SetWindowText(sip);
|
---|
1692 | }
|
---|
1693 | m_sageStreamCtrl.InsertString(index, sip);
|
---|
1694 | index++;
|
---|
1695 | }
|
---|
1696 | } while (!feof(fp));
|
---|
1697 |
|
---|
1698 | fclose(fp);
|
---|
1699 |
|
---|
1700 | m_sageStreamCtrl.SetCurSel(0);
|
---|
1701 | return 1;
|
---|
1702 | }
|
---|
1703 | void CDecklinkCaptureDlg::OnCbnSelchangeComboCompression2()
|
---|
1704 | {
|
---|
1705 | // Framerate changed
|
---|
1706 | DWORD_PTR val = (DWORD_PTR)m_framerateCtrl.GetItemData(m_framerateCtrl.GetCurSel());
|
---|
1707 | FrameRate = val;
|
---|
1708 | }
|
---|