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