Skip to content

Commit 196d50f

Browse files
janherlingmetafacebook-github-bot
authored andcommitted
Added possibility to control the exposure of webcams in Media::MediaFoundation
Summary: This feature was simply missing. Reviewed By: enpe Differential Revision: D79011737 Privacy Context Container: L1241821 fbshipit-source-id: 2d54a1b4d1000b969b4b8d59e1e118cf2393c940
1 parent ff82dfd commit 196d50f

5 files changed

Lines changed: 330 additions & 2 deletions

File tree

impl/ocean/media/mediafoundation/MFLiveVideo.cpp

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
#include "ocean/media/mediafoundation/MFLibrary.h"
1010
#include "ocean/media/mediafoundation/Utilities.h"
1111

12+
#include <ks.h>
13+
#include <ksmedia.h>
14+
#include <ksproxy.h>
15+
1216
namespace Ocean
1317
{
1418

@@ -173,6 +177,78 @@ MFLiveVideo::StreamConfigurations MFLiveVideo::supportedStreamConfigurations(con
173177
return streamConfigurations;
174178
}
175179

180+
double MFLiveVideo::exposureDuration(double* minDuration, double* maxDuration, ControlMode* exposureMode) const
181+
{
182+
const ScopedLock scopedLock(lock_);
183+
184+
ScopedIKsControl iKsControl;
185+
if (S_OK != mediaSource_->QueryInterface(IID_IKsControl, (void**)(&iKsControl.resetObject())))
186+
{
187+
return false;
188+
}
189+
190+
if (minDuration != nullptr || maxDuration != nullptr)
191+
{
192+
double minExposure = NumericD::minValue();
193+
double maxExposure = NumericD::minValue();
194+
195+
const bool result = exposureRange(*iKsControl, minExposure, maxExposure);
196+
ocean_assert_and_suppress_unused(result, result);
197+
198+
if (minDuration != nullptr)
199+
{
200+
*minDuration = minExposure;
201+
}
202+
203+
if (maxDuration != nullptr)
204+
{
205+
*maxDuration = maxExposure;
206+
}
207+
}
208+
209+
double duration = Numeric::minValue();
210+
211+
ControlMode controlMode = CM_INVALID;
212+
if (!exposure(*iKsControl, duration, controlMode))
213+
{
214+
return -1.0;
215+
}
216+
217+
if (exposureMode != nullptr)
218+
{
219+
*exposureMode = controlMode;
220+
}
221+
222+
return duration;
223+
}
224+
225+
bool MFLiveVideo::setExposureDuration(const double duration, const bool /*allowShorterExposure*/)
226+
{
227+
if (duration < 0.0)
228+
{
229+
Log::warning() << "MFLiveVideo::setExposureDuration() does not support one-time auto exposure";
230+
231+
return false;
232+
}
233+
234+
const ScopedLock scopedLock(lock_);
235+
236+
if (!sesssionStarted_)
237+
{
238+
delayedExposureDuration_ = duration;
239+
240+
return true;
241+
}
242+
243+
ScopedIKsControl iKsControl;
244+
if (S_OK != mediaSource_->QueryInterface(IID_IKsControl, (void**)(&iKsControl.resetObject())))
245+
{
246+
return false;
247+
}
248+
249+
return setExposure(*iKsControl, duration);
250+
}
251+
176252
bool MFLiveVideo::enumerateVideoDevices(Library::Definitions& definitions)
177253
{
178254
definitions.clear();
@@ -294,6 +370,178 @@ void MFLiveVideo::releaseTopology()
294370
MFMedium::releaseTopology();
295371
}
296372

373+
void MFLiveVideo::onSessionStarted()
374+
{
375+
const ScopedLock scopedLock(lock_);
376+
377+
MFFrameMedium::onSessionStarted();
378+
379+
sesssionStarted_ = true;
380+
381+
if (delayedExposureDuration_ != NumericD::minValue())
382+
{
383+
setExposureDuration(delayedExposureDuration_);
384+
385+
delayedExposureDuration_ = NumericD::minValue();
386+
}
387+
}
388+
389+
void MFLiveVideo::onSessionStopped()
390+
{
391+
const ScopedLock scopedLock(lock_);
392+
393+
MFFrameMedium::onSessionStopped();
394+
395+
sesssionStarted_ = false;
396+
}
397+
398+
bool MFLiveVideo::exposureRange(IKsControl* iKsControl, double& minExposure, double& maxExposure)
399+
{
400+
// User-mode clients use the KSPROPERTY_CAMERACONTROL_EXPOSURE property to get or set a digital camera's exposure time. This property is optional.
401+
402+
KSPROPERTY ksProperty = {};
403+
ksProperty.Set = PROPSETID_VIDCAP_CAMERACONTROL;
404+
ksProperty.Id = KSPROPERTY_CAMERACONTROL_EXPOSURE;
405+
ksProperty.Flags = KSPROPERTY_TYPE_BASICSUPPORT;
406+
407+
KSPROPERTY_DESCRIPTION ksPropertyDescription = {};
408+
ULONG bytesReturned = 0;
409+
if (S_OK != iKsControl->KsProperty(&ksProperty, sizeof(ksProperty), &ksPropertyDescription, sizeof(ksPropertyDescription), &bytesReturned))
410+
{
411+
return false;
412+
}
413+
414+
if (bytesReturned < sizeof(KSPROPERTY_DESCRIPTION))
415+
{
416+
return false;
417+
}
418+
419+
constexpr size_t expectedSize = sizeof(KSPROPERTY_DESCRIPTION) + sizeof(KSPROPERTY_MEMBERSHEADER) + sizeof(KSPROPERTY_STEPPING_LONG);
420+
421+
if (ksPropertyDescription.DescriptionSize < expectedSize)
422+
{
423+
return false;
424+
}
425+
426+
std::vector<uint8_t> buffer(ksPropertyDescription.DescriptionSize);
427+
428+
bytesReturned = 0;
429+
if (S_OK != iKsControl->KsProperty(&ksProperty, sizeof(ksProperty), buffer.data(), ULONG(buffer.size()), &bytesReturned))
430+
{
431+
return false;
432+
}
433+
434+
const KSPROPERTY_MEMBERSHEADER* propertyMember = (const KSPROPERTY_MEMBERSHEADER*)(buffer.data() + sizeof(KSPROPERTY_DESCRIPTION));
435+
436+
if (propertyMember->MembersFlags & KSPROPERTY_MEMBER_RANGES)
437+
{
438+
const KSPROPERTY_STEPPING_LONG* range = (const KSPROPERTY_STEPPING_LONG*)(buffer.data() + sizeof(KSPROPERTY_DESCRIPTION) + sizeof(KSPROPERTY_MEMBERSHEADER));
439+
440+
const LONG minLogBase2 = range->Bounds.SignedMinimum;
441+
const LONG maxLogBase2 = range->Bounds.SignedMaximum;
442+
443+
minExposure = translateExposure(minLogBase2);
444+
maxExposure = translateExposure(maxLogBase2);
445+
446+
return true;
447+
}
448+
449+
return false;
450+
}
451+
452+
bool MFLiveVideo::setExposure(IKsControl* iKsControl, const double exposure)
453+
{
454+
ocean_assert(iKsControl != nullptr);
455+
456+
LONG exposureValue = 0;
457+
458+
if (exposure > 0.0)
459+
{
460+
exposureValue = translateExposure(exposure);
461+
}
462+
463+
KSPROPERTY_CAMERACONTROL_S cameraControl = {};
464+
465+
cameraControl.Property.Set = PROPSETID_VIDCAP_CAMERACONTROL;
466+
cameraControl.Property.Id = KSPROPERTY_CAMERACONTROL_EXPOSURE;
467+
cameraControl.Property.Flags = KSPROPERTY_TYPE_SET;
468+
cameraControl.Value = exposureValue;
469+
cameraControl.Flags = exposure > 0.0 ? KSPROPERTY_CAMERACONTROL_FLAGS_MANUAL : KSPROPERTY_CAMERACONTROL_FLAGS_AUTO;
470+
cameraControl.Capabilities = KSPROPERTY_CAMERACONTROL_FLAGS_ABSOLUTE;
471+
472+
ULONG bytesReturned = 0;
473+
474+
const HRESULT result = iKsControl->KsProperty(&cameraControl.Property, sizeof(cameraControl), &cameraControl, sizeof(cameraControl), &bytesReturned);
475+
476+
return result == S_OK;
477+
}
478+
479+
bool MFLiveVideo::exposure(IKsControl* iKsControl, double& exposure, ControlMode& controlMode)
480+
{
481+
KSPROPERTY_CAMERACONTROL_S cameraControl = {};
482+
483+
cameraControl.Property.Set = PROPSETID_VIDCAP_CAMERACONTROL;
484+
cameraControl.Property.Id = KSPROPERTY_CAMERACONTROL_EXPOSURE;
485+
cameraControl.Property.Flags = KSPROPERTY_TYPE_GET;
486+
cameraControl.Value = 0;
487+
cameraControl.Flags = 0;
488+
cameraControl.Capabilities = 0;
489+
490+
ULONG bytesReturned = 0;
491+
492+
if (S_OK != iKsControl->KsProperty(&cameraControl.Property, sizeof(cameraControl), &cameraControl, sizeof(cameraControl), &bytesReturned))
493+
{
494+
return false;
495+
}
496+
497+
exposure = translateExposure(cameraControl.Value);
498+
499+
if (cameraControl.Flags & KSPROPERTY_CAMERACONTROL_FLAGS_AUTO)
500+
{
501+
controlMode = CM_DYNAMIC;
502+
}
503+
else if (cameraControl.Flags & KSPROPERTY_CAMERACONTROL_FLAGS_MANUAL)
504+
{
505+
controlMode = CM_FIXED;
506+
}
507+
else
508+
{
509+
controlMode = CM_INVALID;
510+
}
511+
512+
return true;
513+
}
514+
515+
double MFLiveVideo::translateExposure(const LONG logBase2)
516+
{
517+
if (logBase2 >= 0)
518+
{
519+
return double(logBase2) * 2.0;
520+
}
521+
else
522+
{
523+
return 1.0 / NumericD::pow(2.0, double(-logBase2));
524+
}
525+
}
526+
527+
LONG MFLiveVideo::translateExposure(const double exposure)
528+
{
529+
ocean_assert(exposure > 0.0);
530+
531+
if (exposure >= 1.0)
532+
{
533+
const double exposureValueD = exposure / 2.0;
534+
535+
return NumericD::round32(exposureValueD);
536+
}
537+
else
538+
{
539+
const double exposureValueD = NumericD::log2(exposure);
540+
541+
return NumericD::round32(exposureValueD);
542+
}
543+
}
544+
297545
}
298546

299547
}

impl/ocean/media/mediafoundation/MFLiveVideo.h

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ class OCEAN_MEDIA_MF_EXPORT MFLiveVideo :
5454
*/
5555
StreamConfigurations supportedStreamConfigurations(const StreamType streamType) const override;
5656

57+
/**
58+
* Returns the current exposure duration of this device.
59+
* @see LiveVideo::exposureDuration().
60+
*/
61+
double exposureDuration(double* minDuration = nullptr, double* maxDuration = nullptr, ControlMode* exposureMode = nullptr) const override;
62+
63+
/**
64+
* Sets the exposure duration of this device.
65+
* @see LiveVideo::setExposureDuration().
66+
*/
67+
bool setExposureDuration(const double duration, const bool allowShorterExposure = false) override;
68+
5769
/**
5870
* Enumerates all currently available video devices.
5971
* @param definitions The resulting video devices
@@ -92,10 +104,68 @@ class OCEAN_MEDIA_MF_EXPORT MFLiveVideo :
92104
*/
93105
void releaseTopology() override;
94106

107+
/**
108+
* Session started event function.
109+
* @see MFMedium::onSessionStarted().
110+
*/
111+
void onSessionStarted() override;
112+
113+
/**
114+
* Session stopped event function.
115+
* @see MFMedium::onSessionStopped().
116+
*/
117+
void onSessionStopped() override;
118+
119+
/**
120+
* Determines the exposure range of the camera.
121+
* @param iKsControl The IKsControl interface of the camera, must be valid
122+
* @param minExposure The resulting minimal exposure value, in seconds
123+
* @param maxExposure The resulting maximal exposure value, in seconds
124+
* @return True, if succeeded
125+
*/
126+
static bool exposureRange(IKsControl* iKsControl, double& minExposure, double& maxExposure);
127+
128+
/**
129+
* Sets the exposure of the camera.
130+
* @param iKsControl The IKsControl interface of the camera, must be valid
131+
* @param exposure The exposure to be set, in seconds, with range (0, infinity), 0 for auto exposure
132+
* @return True, if succeeded
133+
*/
134+
static bool setExposure(IKsControl* iKsControl, const double exposure);
135+
136+
/**
137+
* Returns the exposure of the camera.
138+
* @param iKsControl The IKsControl interface of the camera, must be valid
139+
* @param exposure The resulting exposure value, in seconds
140+
* @param controlMode The resulting control mode
141+
* @return True, if succeeded
142+
*/
143+
static bool exposure(IKsControl* iKsControl, double& exposure, ControlMode& controlMode);
144+
145+
/**
146+
* Translates the exposure value from log base 2 to a linear exposure in seconds.
147+
* @param logBase2 The exposure value in log base 2, with range (-infinity, infinity)
148+
* @return The resulting exposure value in seconds, with range [0, infinity)
149+
*/
150+
static double translateExposure(const LONG logBase2);
151+
152+
/**
153+
* Translates the exposure value from a linear exposure in seconds to log base 2.
154+
* @param exposure The exposure value in seconds, with range [0, infinity)
155+
* @return The resulting exposure value in log base 2, with range (-infinity, infinity)
156+
*/
157+
static LONG translateExposure(const double exposure);
158+
95159
protected:
96160

97161
/// The symbolic link of the device.
98162
std::string symbolicLink_;
163+
164+
/// True, if the session has been started.
165+
bool sesssionStarted_ = false;
166+
167+
/// The exposure duration of the camera which will be set once the session has been started.
168+
double delayedExposureDuration_ = NumericD::minValue();
99169
};
100170

101171
}

impl/ocean/media/mediafoundation/MediaFoundation.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
#endif
2929

3030
DISABLE_WARNINGS_BEGIN
31+
#include <devicetopology.h>
32+
#include <ks.h>
33+
#include <ksproxy.h>
3134
#include <mfapi.h>
3235
#include <mfidl.h>
3336
DISABLE_WARNINGS_END
@@ -106,6 +109,13 @@ template <class T> void release(T *object);
106109
template <typename T>
107110
using ScopedMediaFoundationObject = ScopedObjectCompileTimeVoidT<T*, release>;
108111

112+
/**
113+
* Definition of a scoped object holding a IKsControl object.
114+
* The wrapped IKsControl object will be released automatically once the scoped object does not exist anymore.
115+
* @ingroup mediamf
116+
*/
117+
using ScopedIKsControl = ScopedMediaFoundationObject<IKsControl>;
118+
109119
/**
110120
* Definition of a scoped object holding a IMFActivate object.
111121
* The wrapped IMFActivate object will be released automatically once the scoped object does not exist anymore.

impl/ocean/media/mediafoundation/SampleGrabber.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ STDMETHODIMP SampleGrabber::OnSetPresentationClock(IMFPresentationClock* /*pCloc
9797
return S_OK;
9898
}
9999

100-
STDMETHODIMP SampleGrabber::OnProcessSample(REFGUID guidMajorMediaType, DWORD dwSampleFlags, LONGLONG llSampleTime, LONGLONG llSampleDuration, const BYTE * pSampleBuffer, DWORD dwSampleSize)
100+
STDMETHODIMP SampleGrabber::OnProcessSample(REFGUID guidMajorMediaType, DWORD dwSampleFlags, LONGLONG llSampleTime, LONGLONG llSampleDuration, const BYTE* pSampleBuffer, DWORD dwSampleSize)
101101
{
102102
if (active_)
103103
{

impl/ocean/media/mediafoundation/SampleGrabber.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ class SampleGrabber final : public IMFSampleGrabberSinkCallback
135135
* @param pSampleBuffer A pointer to a buffer that contains the sample data
136136
* @param dwSampleSize Size of the pSampleBuffer buffer, in bytes
137137
*/
138-
STDMETHODIMP OnProcessSample(REFGUID guidMajorMediaType, DWORD dwSampleFlags, LONGLONG llSampleTime, LONGLONG llSampleDuration, const BYTE * pSampleBuffer, DWORD dwSampleSize) override;
138+
STDMETHODIMP OnProcessSample(REFGUID guidMajorMediaType, DWORD dwSampleFlags, LONGLONG llSampleTime, LONGLONG llSampleDuration, const BYTE* pSampleBuffer, DWORD dwSampleSize) override;
139139

140140
/**
141141
* Called when the sample-grabber sink is shut down.

0 commit comments

Comments
 (0)