[VCM] use adaptive jpg quality instead of 0.9 and report more video format info

This commit is contained in:
yuyoyuppe
2021-04-09 16:05:21 +03:00
parent 81e2bbd80e
commit 8318c20eb0
5 changed files with 135 additions and 54 deletions

View File

@@ -22,6 +22,10 @@ unique_media_type_ptr CopyMediaType(const AM_MEDIA_TYPE* source)
wil::com_ptr_nothrow<IMemAllocator> GetPinAllocator(wil::com_ptr_nothrow<IPin>& inputPin)
{
if (!inputPin)
{
return nullptr;
}
wil::com_ptr_nothrow<IMemAllocator> allocator;
if (auto memInput = inputPin.try_query<IMemInputPin>(); memInput)
{

View File

@@ -21,31 +21,12 @@
#include <mfidl.h>
#include <mftransform.h>
#include <dshow.h>
#include <Wincodecsdk.h>
#include <shlwapi.h>
#include "Logging.h"
bool failed(HRESULT hr)
{
return hr != S_OK;
}
bool failed(bool val)
{
return val == false;
}
template<typename T>
bool failed(wil::com_ptr_nothrow<T>& ptr)
{
return ptr == nullptr;
}
#define OK_OR_BAIL(expr) \
if (failed(expr)) \
return {};
IWICImagingFactory* _GetWIC() noexcept
{
static IWICImagingFactory* s_Factory = nullptr;
@@ -110,7 +91,8 @@ wil::com_ptr_nothrow<IStream> EncodeBitmapToContainer(IWICImagingFactory* pWIC,
wil::com_ptr_nothrow<IWICBitmapSource> bitmap,
const GUID& containerGUID,
const UINT width,
const UINT height)
const UINT height,
const float quality)
{
wil::com_ptr_nothrow<IWICBitmapEncoder> encoder;
pWIC->CreateEncoder(containerGUID, nullptr, &encoder);
@@ -125,8 +107,31 @@ wil::com_ptr_nothrow<IStream> EncodeBitmapToContainer(IWICImagingFactory* pWIC,
OK_OR_BAIL(CreateStreamOnHGlobal(nullptr, true, &encodedBitmap));
encoder->Initialize(encodedBitmap.get(), WICBitmapEncoderNoCache);
wil::com_ptr_nothrow<IWICBitmapFrameEncode> encodedFrame;
OK_OR_BAIL(encoder->CreateNewFrame(&encodedFrame, nullptr));
OK_OR_BAIL(encodedFrame->Initialize(nullptr));
wil::com_ptr_nothrow<IPropertyBag2> encoderOptions;
OK_OR_BAIL(encoder->CreateNewFrame(&encodedFrame, &encoderOptions));
ULONG nProperties = 0;
OK_OR_BAIL(encoderOptions->CountProperties(&nProperties));
for (ULONG propIdx = 0; propIdx < nProperties; ++propIdx)
{
PROPBAG2 propBag{};
ULONG _;
OK_OR_BAIL(encoderOptions->GetPropertyInfo(propIdx, 1, &propBag, &_));
if (propBag.pstrName == std::wstring_view{ L"ImageQuality" })
{
wil::unique_variant variant;
variant.vt = VT_R4;
variant.fltVal = quality;
OK_OR_BAIL(encoderOptions->Write(1, &propBag, &variant));
LOG("Successfully set jpg compression quality");
// skip the rest of the properties
propIdx = nProperties;
}
CoTaskMemFree(propBag.pstrName);
}
OK_OR_BAIL(encodedFrame->Initialize(encoderOptions.get()));
WICPixelFormatGUID intermediateFormat = GUID_WICPixelFormat24bppRGB;
OK_OR_BAIL(encodedFrame->SetPixelFormat(&intermediateFormat));
@@ -245,7 +250,8 @@ IMFSample* ConvertIMFVideoSample(const MFT_REGISTER_TYPE_INFO& inputType,
}
wil::com_ptr_nothrow<IMFSample> LoadImageAsSample(wil::com_ptr_nothrow<IStream> imageStream,
IMFMediaType* sampleMediaType) noexcept
IMFMediaType* sampleMediaType,
const float quality) noexcept
{
UINT targetWidth = 0;
UINT targetHeight = 0;
@@ -302,7 +308,7 @@ wil::com_ptr_nothrow<IMFSample> LoadImageAsSample(wil::com_ptr_nothrow<IStream>
{
// Use an intermediate jpg container sample which will be transcoded to the target format
wil::com_ptr_nothrow<IStream> jpgStream =
EncodeBitmapToContainer(pWIC, srcImageBitmap, GUID_ContainerFormatJpeg, targetWidth, targetHeight);
EncodeBitmapToContainer(pWIC, srcImageBitmap, GUID_ContainerFormatJpeg, targetWidth, targetHeight, quality);
// Obtain stream size and lock its memory pointer
STATSTG intermediateStreamStat{};

View File

@@ -35,7 +35,8 @@ wil::com_ptr_nothrow<IMemAllocator> VideoCaptureProxyPin::FindAllocator()
}
wil::com_ptr_nothrow<IMFSample> LoadImageAsSample(wil::com_ptr_nothrow<IStream> imageStream,
IMFMediaType* sampleMediaType) noexcept;
IMFMediaType* sampleMediaType,
const float quality) noexcept;
HRESULT VideoCaptureProxyPin::Connect(IPin* pReceivePin, const AM_MEDIA_TYPE*)
{
@@ -69,6 +70,7 @@ HRESULT VideoCaptureProxyPin::Connect(IPin* pReceivePin, const AM_MEDIA_TYPE*)
}
auto allocator = FindAllocator();
memInput->NotifyAllocator(allocator.get(), false);
return S_OK;
@@ -349,6 +351,20 @@ HRESULT VideoCaptureProxyPin::QuerySupported(REFGUID guidPropSet, DWORD dwPropID
return S_OK;
}
long GetImageSize(wil::com_ptr_nothrow<IMFSample>& image)
{
if (!image)
{
return 0;
}
DWORD imageSize = 0;
wil::com_ptr_nothrow<IMFMediaBuffer> imageBuf;
OK_OR_BAIL(image->GetBufferByIndex(0, &imageBuf));
OK_OR_BAIL(imageBuf->GetCurrentLength(&imageSize));
return imageSize;
}
void OverwriteFrame(IMediaSample* frame, wil::com_ptr_nothrow<IMFSample>& image)
{
if (!image)
@@ -365,17 +381,23 @@ void OverwriteFrame(IMediaSample* frame, wil::com_ptr_nothrow<IMFSample>& image)
}
wil::com_ptr_nothrow<IMFMediaBuffer> imageBuf;
const long nBytes = frame->GetSize();
const DWORD frameSize = frame->GetSize();
image->GetBufferByIndex(0, &imageBuf);
BYTE* imageData = nullptr;
DWORD maxLength = 0, curLength = 0;
imageBuf->Lock(&imageData, &maxLength, &curLength);
std::copy(imageData, imageData + curLength, frameData);
imageBuf->Unlock();
frame->SetActualDataLength(curLength);
}
DWORD _ = 0, imageSize = 0;
imageBuf->Lock(&imageData, &_, &imageSize);
if (imageSize > frameSize)
{
LOG("Error: overlay image size is larger than a frame size - truncated.");
imageSize = frameSize;
}
std::copy(imageData, imageData + imageSize, frameData);
imageBuf->Unlock();
frame->SetActualDataLength(imageSize);
}
VideoCaptureProxyFilter::VideoCaptureProxyFilter() :
_worker_thread{ std::thread{ [this]() {
@@ -566,12 +588,18 @@ HRESULT VideoCaptureProxyFilter::EnumPins(IEnumPins** ppEnum)
}
auto& webcam = webcams[*selectedCamIdx];
auto pin = winrt::make_self<VideoCaptureProxyPin>();
pin->_mediaFormat = CopyMediaType(webcam.bestFormat.mediaType);
pin->_owningFilter = this;
_outPin.attach(pin.detach());
auto frameCallback = [this](IMediaSample* sample) {
std::unique_lock<std::mutex> lock{ _worker_mutex };
sample->AddRef();
_pending_frame = sample;
_worker_cv.notify_one();
};
wil::com_ptr_nothrow<IMFMediaType> targetMediaType;
MFCreateMediaType(&targetMediaType);
targetMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
@@ -582,28 +610,37 @@ HRESULT VideoCaptureProxyFilter::EnumPins(IEnumPins** ppEnum)
targetMediaType.get(), MF_MT_FRAME_SIZE, webcam.bestFormat.width, webcam.bestFormat.height);
MFSetAttributeRatio(targetMediaType.get(), MF_MT_PIXEL_ASPECT_RATIO, 1, 1);
if (!_blankImage)
{
wil::com_ptr_nothrow<IStream> blackBMPImage = SHCreateMemStream(bmpPixelData, sizeof(bmpPixelData));
_blankImage = LoadImageAsSample(blackBMPImage, targetMediaType.get());
}
if (newSettings.overlayImage && !_overlayImage)
{
_overlayImage = LoadImageAsSample(newSettings.overlayImage, targetMediaType.get());
}
LOG("Loaded images");
auto frameCallback = [this](IMediaSample* sample) {
std::unique_lock<std::mutex> lock{ _worker_mutex };
sample->AddRef();
_pending_frame = sample;
_worker_cv.notify_one();
};
_captureDevice = VideoCaptureDevice::Create(std::move(webcam), std::move(frameCallback));
if (_captureDevice)
{
if (!_blankImage)
{
wil::com_ptr_nothrow<IStream> blackBMPImage = SHCreateMemStream(bmpPixelData, sizeof(bmpPixelData));
_blankImage = LoadImageAsSample(blackBMPImage, targetMediaType.get(), false);
}
if (newSettings.overlayImage && !_overlayImage)
{
long maxFrameSize = 0;
ALLOCATOR_PROPERTIES allocatorProperties{};
if (!failed(_captureDevice->_allocator->GetProperties(&allocatorProperties)))
{
maxFrameSize = allocatorProperties.cbBuffer;
}
size_t selectedModeIdx = 0;
constexpr std::array<float, 3> jpgQualityModes = { 0.5f, 0.25f, 0.1f };
_overlayImage = LoadImageAsSample(newSettings.overlayImage, targetMediaType.get(), jpgQualityModes[selectedModeIdx]);
long imageSize = GetImageSize(_overlayImage);
while (maxFrameSize && maxFrameSize < imageSize && selectedModeIdx < size(jpgQualityModes))
{
_overlayImage = LoadImageAsSample(newSettings.overlayImage, targetMediaType.get(), jpgQualityModes[++selectedModeIdx]);
imageSize = GetImageSize(_overlayImage);
}
}
LOG("Loaded images");
LOG("Capture device created successfully");
}
else

View File

@@ -4,6 +4,9 @@
#include <guiddef.h>
#include <system_error>
#include <wil/com.h>
#include <Windows.h>
void LogToFile(std::string what, const bool verbose = false);
void LogToFile(std::wstring what, const bool verbose = false);
std::string toMediaTypeString(GUID subtype);
@@ -38,3 +41,22 @@ std::string toMediaTypeString(GUID subtype);
#define LOG(str) LogToFile(str, false);
#endif
inline bool failed(HRESULT hr)
{
return hr != S_OK;
}
inline bool failed(bool val)
{
return val == false;
}
template<typename T>
inline bool failed(wil::com_ptr_nothrow<T>& ptr)
{
return ptr == nullptr;
}
#define OK_OR_BAIL(expr) \
if (failed(expr)) \
return {};

View File

@@ -10,6 +10,7 @@
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <Shlobj.h>
#include <Shlobj_core.h>
@@ -90,6 +91,15 @@ std::string GetMediaSubTypeString(const GUID& guid)
}
}
std::string asString(RECT r)
{
std::ostringstream s;
if (r.left == r.right && r.bottom == r.right && r.top == r.right && r.right == 0)
return "[ZEROES]";
s << '(' << r.left << ", " << r.top << ") - (" << r.right << ", " << r.bottom << ")";
return s.str();
}
void LogMediaTypes(wil::com_ptr_nothrow<IPin>& pin)
{
wil::com_ptr_nothrow<IEnumMediaTypes> mediaTypeEnum;
@@ -113,7 +123,9 @@ void LogMediaTypes(wil::com_ptr_nothrow<IPin>& pin)
}
const auto formatAvgFPS = 10000000LL / format->AvgTimePerFrame;
log() << GetMediaSubTypeString(mt->subtype) << '\t' << format->bmiHeader.biWidth << "x"
<< format->bmiHeader.biHeight << " - " << formatAvgFPS << "fps\n";
<< format->bmiHeader.biHeight << " - " << formatAvgFPS << "fps src rect: " << asString(format->rcSource)
<< " dst rect: " << asString(format->rcSource)
<< " flipped: " << std::boolalpha << (format->bmiHeader.biHeight < 0) << '\n';
}
log() << '\n';
}