1 // This is core/vidl/vidl_dshow_live_istream.cxx
2 //=========================================================================
3 #include <cstring>
4 #include "vidl_dshow_live_istream.h"
5 //:
6 // \file
7 // \brief  DirectShow live video input stream support.
8 //
9 // See vidl_dshow_live_istream.h for details.
10 //
11 //=========================================================================
12 
13 #include <vidl/vidl_config.h>
14 #include "vidl/vidl_dshow.h"
15 #include <cassert>
16 #ifdef _MSC_VER
17 #  include "vcl_msvc_warnings.h"
18 #endif
19 
20 //-------------------------------------------------------------------------
21 //-------------------------------------------------------------------------
sample_grabber_cb(void)22 sample_grabber_cb::sample_grabber_cb(void)
23   : busy_index_(-1)
24   , curr_index_(-1)
25   , next_index_(0)
26 {
27   mutex_ = CreateMutex(0, false, 0);
28 }
29 
30 STDMETHODIMP
QueryInterface(REFIID riid,void ** target)31 sample_grabber_cb::QueryInterface(REFIID riid, void ** target)
32 {
33   if (target == 0)
34     return E_POINTER;
35   if (riid == __uuidof(IUnknown))
36   {
37     *target = static_cast<IUnknown *>(this);
38     return S_OK;
39   }
40   if (riid == __uuidof(ISampleGrabberCB))
41   {
42     *target = static_cast<ISampleGrabberCB *>(this);
43     return S_OK;
44   }
45   return E_NOTIMPL;
46 }
47 
48 //: Retrieves the original media sample.
49 STDMETHODIMP
SampleCB(double time,IMediaSample * sample)50 sample_grabber_cb::SampleCB(double time, IMediaSample * sample)
51 {
52   assert(sample);
53 
54   BYTE * buffer;
55   DSHOW_ERROR_IF_FAILED(sample->GetPointer(&buffer));
56 
57   return BufferCB(time, buffer, sample->GetSize());
58 }
59 
60 //: Retrieves a copy of the media sample (requires SetBufferSamples(true)).
61 STDMETHODIMP
BufferCB(double time,BYTE * buffer,long buffer_size)62 sample_grabber_cb::BufferCB(double time, BYTE * buffer, long buffer_size)
63 {
64   assert(buffer);
65 
66   // allocate space for the buffer, if necessary
67   const unsigned int i = next_index_;
68   buffer_[i].resize(buffer_size);
69   buffer_time_[i] = time;
70 
71   // copy buffer
72   std::memcpy(&buffer_[i][0], buffer, buffer_size);
73 
74   // reset flags to reflect new state
75   WaitForSingleObject(mutex_, INFINITE);
76   curr_index_ = next_index_;
77   next_index_ = (next_index_ + 1) % 3;
78   if (next_index_ == busy_index_)
79   {
80     next_index_ = (next_index_ + 1) % 3;
81   }
82   ReleaseMutex(mutex_);
83 
84   return S_FALSE;
85 }
86 
87 void
advance(void)88 sample_grabber_cb::advance(void)
89 {
90   busy_index_ = -1;
91   while (busy_index_ == -1)
92   {
93     WaitForSingleObject(mutex_, INFINITE);
94     if (curr_index_ != -1)
95     {
96       busy_index_ = curr_index_;
97       curr_index_ = -1;
98     }
99     ReleaseMutex(mutex_);
100     Sleep(0);
101   }
102 }
103 
104 vidl_frame_sptr
current_frame(void)105 sample_grabber_cb::current_frame(void)
106 {
107   return new vidl_shared_frame(&buffer_[busy_index_][0], buffer_[busy_index_].size(), 1, VIDL_PIXEL_FORMAT_UNKNOWN);
108 }
109 
110 //-------------------------------------------------------------------------
111 // vidl_dshow_live_istream implementation - construction & destruction
112 //-------------------------------------------------------------------------
113 //: Constructor - default
114 template <class ParamsObject>
vidl_dshow_live_istream(void)115 vidl_dshow_live_istream<ParamsObject>::vidl_dshow_live_istream(void)
116   : params_(ParamsObject())
117   , register_(0)
118 {
119   // connect to the first available device
120   std::vector<std::string> names = vidl_dshow::get_capture_device_names();
121   if (names.size() > 0)
122   {
123     params_.set_device_name(names[0]);
124     connect();
125   }
126   else
127   {
128     vidl_exception_error(vidl_dshow_exception("No capture devices found."));
129   }
130 }
131 
132 //: Constructor - from a string containing a device name.
133 template <class ParamsObject>
vidl_dshow_live_istream(const std::string & device_name)134 vidl_dshow_live_istream<ParamsObject>::vidl_dshow_live_istream(const std::string & device_name)
135   : params_(ParamsObject().set_device_name(device_name))
136   , register_(0)
137 {
138   connect();
139 }
140 
141 //: Constructor - from a parameter object.
142 template <class ParamsObject>
vidl_dshow_live_istream(const ParamsObject & params)143 vidl_dshow_live_istream<ParamsObject>::vidl_dshow_live_istream(const ParamsObject & params)
144   : params_(params) // ***** dynamic_cast<const ParamsObject&>
145   , register_(0)
146 {
147   connect();
148 }
149 
150 // *****
151 ////: Destructor.
152 // template <class ParamsObject>
153 // vidl_dshow_live_istream<ParamsObject>::~vidl_dshow_live_istream(void)
154 //{
155 //  close();
156 //}
157 
158 //: Connect to the device specified in params object.
159 template <class ParamsObject>
160 void
connect(void)161 vidl_dshow_live_istream<ParamsObject>::connect(void)
162 {
163   // ***** no re-connection allowed, yet...
164   // close();
165 
166   // connect to the device...
167   moniker_ = vidl_dshow::get_capture_device_moniker(params_.device_name());
168   if (!moniker_)
169   {
170     vidl_exception_error(vidl_exception("Requested device not found."));
171   }
172 
173   // ***** start: build the filter graph here *****
174   // create the filter graph manager
175   DSHOW_ERROR_IF_FAILED(filter_graph_.CoCreateInstance(CLSID_FilterGraph));
176 
177   // create the capture graph builder
178   CComPtr<ICaptureGraphBuilder2> graph_builder;
179   DSHOW_ERROR_IF_FAILED(graph_builder.CoCreateInstance(CLSID_CaptureGraphBuilder2));
180 
181   // initialize the capture graph builder
182   graph_builder->SetFiltergraph(filter_graph_);
183 
184   // add the selected source filter to filter graph
185   CComPtr<IBaseFilter> source_filter;
186   DSHOW_ERROR_IF_FAILED(filter_graph_->AddSourceFilterForMoniker(moniker_, 0, L"Source", &source_filter));
187 
188   // configure filter based on params structure
189   params_.configure_filter(source_filter);
190   // ***** for debugging only
191   // params_.print_parameter_help(source_filter);
192 
193   // create sample grabber
194   CComPtr<ISampleGrabber> sample_grabber;
195   DSHOW_ERROR_IF_FAILED(sample_grabber.CoCreateInstance(CLSID_SampleGrabber));
196   sample_grabber->SetBufferSamples(false);
197   sample_grabber->SetOneShot(false);
198   sample_grabber->SetCallback(&sample_grabber_callback_, 0);
199 
200   // set target output format type
201   if (params_.target_output_format() != GUID_NULL)
202   {
203     AM_MEDIA_TYPE media_type;
204     ZeroMemory(&media_type, sizeof(AM_MEDIA_TYPE));
205     media_type.majortype = MEDIATYPE_Video;
206     media_type.subtype = params_.target_output_format();
207     sample_grabber->SetMediaType(&media_type);
208   }
209 
210   // add sample grabber to the filter graph
211   CComQIPtr<IBaseFilter> sample_grabber_filter(sample_grabber);
212   DSHOW_ERROR_IF_FAILED(filter_graph_->AddFilter(sample_grabber_filter, L"Sample Grabber"));
213 
214   // create a null renderer or a file writing section
215   CComPtr<IBaseFilter> filter;
216   if (params_.output_filename() == "")
217   {
218     DSHOW_ERROR_IF_FAILED(filter.CoCreateInstance(CLSID_NullRenderer));
219     DSHOW_ERROR_IF_FAILED(filter_graph_->AddFilter(filter, L"Null Renderer"));
220   }
221   else
222   {
223     DSHOW_ERROR_IF_FAILED(
224       graph_builder->SetOutputFileName(&MEDIASUBTYPE_Avi, CA2W(params_.output_filename().c_str()), &filter, 0));
225   }
226 
227   // CComPtr<IBaseFilter> vmr;
228   // DSHOW_ERROR_IF_FAILED(vmr.CoCreateInstance(CLSID_VideoMixingRenderer));
229   // DSHOW_ERROR_IF_FAILED(filter_graph_->AddFilter(vmr, L"Video Mixing Renderer"));
230 
231   // connect the filters
232   DSHOW_ERROR_IF_FAILED(
233     graph_builder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, source_filter, sample_grabber_filter, filter));
234 
235   // DSHOW_ERROR_IF_FAILED(graph_builder->RenderStream(&PIN_CATEGORY_PREVIEW,
236   //                                                  &MEDIATYPE_Video,
237   //                                                  source_filter,
238   //                                                  0, 0));
239   // ***** end: build the filter graph here *****
240 
241   // **** testing renderers *****
242   // CComPtr<IBaseFilter> vmr;
243   // DSHOW_ERROR_IF_FAILED(vmr.CoCreateInstance(CLSID_VideoMixingRenderer));
244   // DSHOW_ERROR_IF_FAILED(filter_graph_->AddFilter(vmr, L"Video Mixing Renderer"));
245   ////CComQIPtr<IVMRFilterConfig> vmr_config(vmr);
246   ////vmr_config->SetRenderingMode(VMRMode_Windowless);
247   ////vmr_config->SetNumberOfStreams(1);
248   ////CComQIPtr<IVideoWindow> video_window(vmr);
249   ////video_window->put_AutoShow(OATRUE);
250   ////video_window->put_Visible(OATRUE);
251   ////video_window->put_FullScreenMode(OATRUE);
252   // DSHOW_ERROR_IF_FAILED(graph_builder->RenderStream(&PIN_CATEGORY_PREVIEW,
253   //                                                  &MEDIATYPE_Video,
254   //                                                  source_filter,
255   //                                                  0,
256   //                                                  vmr));
257   // **** testing renderers *****
258 
259   // ***** should I provide access to this through the public interface???
260   // vidl_dshow::load_graph_from_file(filter_graph_, L"testing.grf");
261 
262   // ***** should I provide access to this through the public interface???
263   if (params_.save_graph_to() != "")
264   {
265     vidl_dshow::save_graph_to_file(filter_graph_, params_.save_graph_to());
266   }
267 
268   // get frame format information
269   AM_MEDIA_TYPE media_type;
270   DSHOW_ERROR_IF_FAILED(sample_grabber->GetConnectedMediaType(&media_type));
271   vidl_dshow::get_media_info(media_type, buffer_width_, buffer_height_, buffer_pixel_format_);
272   vidl_dshow::delete_media_type(media_type);
273 
274   // ***** MSDN docs suggest turning the graph clock off (if not needed)
275   //       for running the graph faster. Check this out.
276   // *****
277   // if (params_.turn_clock_off())
278   //{
279   //  CComQIPtr<IMediaFilter> media_filter(filter_graph_);
280   //  media_filter->SetSyncSource(0);
281   //}
282 
283   if (params_.register_in_rot())
284   {
285     vidl_dshow::register_in_rot(filter_graph_, register_);
286   }
287 
288   filter_graph_->QueryInterface(IID_IMediaControl, reinterpret_cast<void **>(&media_control_));
289 
290   if (params_.run_when_ready())
291   {
292     run();
293   }
294   else
295   {
296     pause();
297   }
298 }
299 
300 template <class ParamsObject>
301 inline void
close(void)302 vidl_dshow_live_istream<ParamsObject>::close(void)
303 {
304   stop();
305 
306   if (register_ != 0)
307   {
308     vidl_dshow::remove_from_rot(register_);
309   }
310 
311   media_control_.Release();
312   moniker_.Release();
313   filter_graph_.Release();
314 }
315 
316 //-------------------------------------------------------------------------
317 // vidl_dshow_live_istream implementation
318 //-------------------------------------------------------------------------
319 //: Initiate advance and wait for completion; synchronous advance.
320 template <class ParamsObject>
321 inline bool
advance_wait(void)322 vidl_dshow_live_istream<ParamsObject>::advance_wait(void)
323 {
324   if (!advance_start())
325   {
326     return false;
327   }
328   while (!is_frame_available())
329   {
330     Sleep(0);
331   }
332   return true;
333 }
334 
335 //: Initiate advance and return immediately; asynchronous advance.
336 template <class ParamsObject>
337 inline bool
advance_start(void)338 vidl_dshow_live_istream<ParamsObject>::advance_start(void)
339 {
340   sample_grabber_callback_.advance();
341   return true;
342 }
343 
344 //: Advance to the next frame (but don't acquire an image).
345 template <class ParamsObject>
346 inline bool
is_frame_available(void) const347 vidl_dshow_live_istream<ParamsObject>::is_frame_available(void) const
348 {
349   return true;
350 }
351 
352 //: Read the next frame from the stream (advance and acquire).
353 template <class ParamsObject>
354 inline vidl_frame_sptr
read_frame(void)355 vidl_dshow_live_istream<ParamsObject>::read_frame(void)
356 {
357   if (!advance_wait())
358   {
359     return 0;
360   }
361   return current_frame();
362 }
363 
364 //: Return the current frame in the stream
365 template <class ParamsObject>
366 inline vidl_frame_sptr
current_frame(void)367 vidl_dshow_live_istream<ParamsObject>::current_frame(void)
368 {
369   if (buffer_pixel_format_ == VIDL_PIXEL_FORMAT_UNKNOWN)
370   {
371     return sample_grabber_callback_.current_frame();
372   }
373   else
374   {
375     return new vidl_shared_frame(
376       sample_grabber_callback_.current_frame()->data(), buffer_width_, buffer_height_, buffer_pixel_format_);
377   }
378 }
379 
380 template <class ParamsObject>
381 inline void
wait_for_state_change(HRESULT hr)382 vidl_dshow_live_istream<ParamsObject>::wait_for_state_change(HRESULT hr)
383 {
384   if (hr == S_FALSE)
385   {
386     OAFilterState state;
387     DSHOW_ERROR_IF_FAILED(media_control_->GetState(INFINITE, &state));
388   }
389 }
390 
391 template <class ParamsObject>
392 inline void
run(void)393 vidl_dshow_live_istream<ParamsObject>::run(void)
394 {
395   wait_for_state_change(media_control_->Run());
396 }
397 
398 template <class ParamsObject>
399 inline void
pause(void)400 vidl_dshow_live_istream<ParamsObject>::pause(void)
401 {
402   wait_for_state_change(media_control_->Pause());
403 }
404 
405 template <class ParamsObject>
406 inline void
stop(void)407 vidl_dshow_live_istream<ParamsObject>::stop(void)
408 {
409   wait_for_state_change(media_control_->Stop());
410 }
411 
412 // ***** make these with the usual *.hxx macros? *****
413 // verify these steps with vil library before attempting
414 // 1. put this file into vidl_dshow_istream.hxx
415 // 2. put the macros in vidl_dshow_istream.h
416 // 3. put the templates in:
417 //      templates/vidl_dshow_istream+vidl_dshow_istream_params-.cxx
418 //      templates/vidl_dshow_istream+vidl_dshow_istream_params_esf-.cxx
419 #include "vidl/vidl_dshow_istream_params.h"
420 template class vidl_dshow_live_istream<vidl_dshow_istream_params>;
421 
422 #if VIDL_HAS_DSHOW_ESF
423 #  include "vidl/vidl_dshow_istream_params_esf.h"
424 template class vidl_dshow_live_istream<vidl_dshow_istream_params_esf>;
425 #endif // HAS_EURESYS_ESF
426