From ce673bf3305ba54f8c09c6ad18a4e9124332507b Mon Sep 17 00:00:00 2001 From: luocai Date: Thu, 21 Nov 2024 19:34:13 +0800 Subject: [PATCH] add dshow api. --- Analyser/Application.cpp | 11 +- Analyser/Application.h | 2 +- Analyser/VideoPlayer.cpp | 5 +- Analyser/qml/ConnectionItem.qml | 4 +- Peripheral/CMakeLists.txt | 2 +- Peripheral/DeviceDiscovery.cpp | 210 +++++++++++++++++++++++++++++++- Peripheral/DeviceDiscovery.h | 20 ++- 7 files changed, 239 insertions(+), 15 deletions(-) diff --git a/Analyser/Application.cpp b/Analyser/Application.cpp index 56dbfc6..1d31c25 100644 --- a/Analyser/Application.cpp +++ b/Analyser/Application.cpp @@ -119,12 +119,15 @@ QStringList Application::availableSerialPorts() const { return ret; } -QStringList Application::availableUsbVideoCameras() const { - QStringList ret; +QVariantList Application::availableUsbVideoCameras() const { + QVariantList ret; DeviceDiscovery d; - auto devices = d.devices(); + auto devices = d.getDevices(); for (auto &device : devices) { - ret << QString::fromStdString(device); + QVariantMap item; + item.insert("name", QString::fromStdString(device.friendlyName)); + item.insert("path", QString::fromStdString(device.alternativeName)); + ret << item; } return ret; } diff --git a/Analyser/Application.h b/Analyser/Application.h index fea8438..73e217c 100644 --- a/Analyser/Application.h +++ b/Analyser/Application.h @@ -43,7 +43,7 @@ public: int exec(); static Application *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine); Q_INVOKABLE QStringList availableSerialPorts() const; - Q_INVOKABLE QStringList availableUsbVideoCameras() const; + Q_INVOKABLE QVariantList availableUsbVideoCameras() const; Q_INVOKABLE bool open(const QString &portName, int baudRate); Q_INVOKABLE bool openUVC(const QString &deviceName); Q_INVOKABLE void close(); diff --git a/Analyser/VideoPlayer.cpp b/Analyser/VideoPlayer.cpp index 53be363..e30d51b 100644 --- a/Analyser/VideoPlayer.cpp +++ b/Analyser/VideoPlayer.cpp @@ -32,7 +32,7 @@ bool VideoPlayer::open(const std::string &deviceName) { #ifdef WIN32 constexpr auto format = "dshow"; std::ostringstream oss; - oss << "video=" << deviceName; + oss << "video=@device_pnp_" << deviceName; auto device = oss.str(); #else constexpr auto format = "v4l2"; @@ -41,7 +41,10 @@ bool VideoPlayer::open(const std::string &deviceName) { auto inputFormat = av_find_input_format(format); AVDictionary *dictionary = nullptr; + // clang-format off + // ffplay -f dshow -i video="@device_pnp_\\?\usb#vid_3346&pid_0001&mi_00#6&6ff22b9&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global" // ffmpeg -f dshow -list_options true -i video="UVC Camera" + // clang-format on av_dict_set(&dictionary, "video_size", "800*600", 0); int status = avformat_open_input(&m_formatContext, device.c_str(), inputFormat, &dictionary); diff --git a/Analyser/qml/ConnectionItem.qml b/Analyser/qml/ConnectionItem.qml index a411266..171b4e6 100644 --- a/Analyser/qml/ConnectionItem.qml +++ b/Analyser/qml/ConnectionItem.qml @@ -57,6 +57,7 @@ RowLayout { } ComboBox { id: uvcs + textRole: "name" enabled: !App.uvcOpened implicitWidth: 150 } @@ -67,8 +68,7 @@ RowLayout { } Button { text: App.uvcOpened ? "关闭" : "连接" - onClicked: App.uvcOpened ? App.closeUVC() : App.openUVC( - uvcs.currentText) + onClicked: App.uvcOpened ? App.closeUVC() : App.openUVC(uvcs.currentValue.path) } } } diff --git a/Peripheral/CMakeLists.txt b/Peripheral/CMakeLists.txt index 93164ea..bfdb05d 100644 --- a/Peripheral/CMakeLists.txt +++ b/Peripheral/CMakeLists.txt @@ -15,6 +15,6 @@ target_include_directories(Peripheral target_link_libraries(Peripheral PUBLIC Universal PRIVATE Encrypt - $<$:Mfreadwrite Mf mfplat mfuuid> + $<$:Mfreadwrite Mf mfplat mfuuid strmiids comsuppw> PRIVATE Qt${QT_VERSION_MAJOR}::SerialPort ) diff --git a/Peripheral/DeviceDiscovery.cpp b/Peripheral/DeviceDiscovery.cpp index 8770ab2..b169ec7 100644 --- a/Peripheral/DeviceDiscovery.cpp +++ b/Peripheral/DeviceDiscovery.cpp @@ -1,13 +1,15 @@ #include "DeviceDiscovery.h" #include "BoostLog.h" -#include "StringUtility.h" #include #include #include #include #ifdef WIN32 +#include +#include #include #include +#include #else #include #include @@ -27,6 +29,23 @@ DeviceDiscovery::DeviceDiscovery() { } #ifdef WIN32 + +void DeleteMediaType(AM_MEDIA_TYPE *pmt) { + if (pmt != nullptr) { + if (pmt->cbFormat != 0) { + CoTaskMemFree((PVOID)pmt->pbFormat); + pmt->cbFormat = 0; + pmt->pbFormat = nullptr; + } + if (pmt->pUnk != nullptr) { + // Unecessary because pUnk should not be used, but safest. + pmt->pUnk->Release(); + pmt->pUnk = nullptr; + } + CoTaskMemFree((PVOID)pmt); + } +} + static std::string deviceName(IMFActivate *device) { std::string ret; WCHAR *friendlyName = nullptr; @@ -34,7 +53,7 @@ static std::string deviceName(IMFActivate *device) { auto result = device->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &friendlyName, &nameLength); if (SUCCEEDED(result)) { - ret = Amass::StringUtility::wstringToString(std::wstring(friendlyName, nameLength)); + ret = _com_util::ConvertBSTRToString(friendlyName); } if (friendlyName != nullptr) { CoTaskMemFree(friendlyName); @@ -160,6 +179,110 @@ std::vector DeviceDiscovery::devices() { return ret; } +std::vector DeviceDiscovery::getDevices() { + std::vector devices; + ICreateDevEnum *pDevEnum = nullptr; + IEnumMoniker *pEnum = nullptr; + + HRESULT hr = + CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pDevEnum); + + if (FAILED(hr)) { + return devices; + } + + hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0); + if (hr != S_OK) { + pDevEnum->Release(); + return devices; + } + + IMoniker *pMoniker = nullptr; + while (pEnum->Next(1, &pMoniker, nullptr) == S_OK) { + Device info; + GetDeviceNames(pMoniker, info); + + IBaseFilter *pFilter; + hr = pMoniker->BindToObject(nullptr, nullptr, IID_IBaseFilter, (void **)&pFilter); + if (SUCCEEDED(hr)) { + IEnumPins *pEnumPins; + hr = pFilter->EnumPins(&pEnumPins); + if (SUCCEEDED(hr)) { + IPin *pPin; + while (pEnumPins->Next(1, &pPin, nullptr) == S_OK) { + PIN_DIRECTION pinDir; + pPin->QueryDirection(&pinDir); + if (pinDir == PINDIR_OUTPUT) { + GetSupportedResolutions(pPin, info.resolutions); + } + pPin->Release(); + } + pEnumPins->Release(); + } + pFilter->Release(); + } + devices.push_back(info); + pMoniker->Release(); + } + pEnum->Release(); + pDevEnum->Release(); + return devices; +} + +bool DeviceDiscovery::SetResolution(const std::string &deviceName, int width, int height) { + ICreateDevEnum *pDevEnum = nullptr; + IEnumMoniker *pEnum = nullptr; + + HRESULT hr = + CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pDevEnum); + + if (FAILED(hr)) { + return false; + } + + hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0); + if (hr != S_OK) { + pDevEnum->Release(); + return false; + } + + IMoniker *pMoniker = nullptr; + while (pEnum->Next(1, &pMoniker, nullptr) == S_OK) { + IPropertyBag *pPropBag; + hr = pMoniker->BindToStorage(nullptr, nullptr, IID_IPropertyBag, (void **)&pPropBag); + if (SUCCEEDED(hr)) { + VARIANT varName; + VariantInit(&varName); + hr = pPropBag->Read(L"FriendlyName", &varName, nullptr); + if (SUCCEEDED(hr)) { + std::string currentName = _com_util::ConvertBSTRToString(varName.bstrVal); + if (currentName == deviceName) { + IBaseFilter *pFilter; + hr = pMoniker->BindToObject(nullptr, nullptr, IID_IBaseFilter, (void **)&pFilter); + if (SUCCEEDED(hr)) { + if (SetPinResolution(pFilter, width, height)) { + pFilter->Release(); + VariantClear(&varName); + pPropBag->Release(); + pMoniker->Release(); + pEnum->Release(); + pDevEnum->Release(); + return true; + } + pFilter->Release(); + } + } + } + VariantClear(&varName); + pPropBag->Release(); + } + pMoniker->Release(); + } + pEnum->Release(); + pDevEnum->Release(); + return false; +} + DeviceDiscovery::Resolutions DeviceDiscovery::deviceResolutions(const std::shared_ptr &source) { DeviceDiscovery::Resolutions ret; HRESULT result = S_OK; @@ -182,6 +305,89 @@ DeviceDiscovery::Resolutions DeviceDiscovery::deviceResolutions(const std::share return ret; } +void DeviceDiscovery::GetDeviceNames(IMoniker *pMoniker, Device &info) { + IPropertyBag *pPropBag; + HRESULT hr = pMoniker->BindToStorage(nullptr, nullptr, IID_IPropertyBag, (void **)&pPropBag); + if (SUCCEEDED(hr)) { + VARIANT varName; + VariantInit(&varName); + hr = pPropBag->Read(L"FriendlyName", &varName, nullptr); + if (SUCCEEDED(hr)) { + + info.friendlyName = _com_util::ConvertBSTRToString(varName.bstrVal); + } + VariantClear(&varName); + + hr = pPropBag->Read(L"DevicePath", &varName, nullptr); + if (SUCCEEDED(hr)) { + info.alternativeName = _com_util::ConvertBSTRToString(varName.bstrVal); + } + VariantClear(&varName); + + pPropBag->Release(); + } +} + +void DeviceDiscovery::GetSupportedResolutions(IPin *pPin, std::vector> &resolutions) { + IEnumMediaTypes *pEnumMediaTypes = nullptr; + HRESULT hr = pPin->EnumMediaTypes(&pEnumMediaTypes); + if (FAILED(hr)) { + return; + } + + AM_MEDIA_TYPE *pMediaType = nullptr; + while (pEnumMediaTypes->Next(1, &pMediaType, nullptr) == S_OK) { + if (pMediaType->formattype == FORMAT_VideoInfo) { + VIDEOINFOHEADER *pVih = reinterpret_cast(pMediaType->pbFormat); + int width = pVih->bmiHeader.biWidth; + int height = pVih->bmiHeader.biHeight; + resolutions.emplace_back(width, height); + } + DeleteMediaType(pMediaType); + } + pEnumMediaTypes->Release(); +} + +bool DeviceDiscovery::SetPinResolution(IBaseFilter *pFilter, int width, int height) { + IEnumPins *pEnumPins; + HRESULT hr = pFilter->EnumPins(&pEnumPins); + if (FAILED(hr)) { + return false; + } + + IPin *pPin; + bool success = false; + while (pEnumPins->Next(1, &pPin, nullptr) == S_OK) { + PIN_DIRECTION pinDir; + pPin->QueryDirection(&pinDir); + if (pinDir == PINDIR_OUTPUT) { + IEnumMediaTypes *pEnumMediaTypes = nullptr; + hr = pPin->EnumMediaTypes(&pEnumMediaTypes); + if (SUCCEEDED(hr)) { + AM_MEDIA_TYPE *pMediaType = nullptr; + while (pEnumMediaTypes->Next(1, &pMediaType, nullptr) == S_OK) { + if (pMediaType->formattype == FORMAT_VideoInfo) { + VIDEOINFOHEADER *pVih = reinterpret_cast(pMediaType->pbFormat); + if (pVih->bmiHeader.biWidth == width && pVih->bmiHeader.biHeight == height) { + hr = pPin->QueryAccept(pMediaType); + if (hr == S_OK) { + success = true; + } + } + } + DeleteMediaType(pMediaType); + if (success) break; + } + pEnumMediaTypes->Release(); + } + } + pPin->Release(); + if (success) break; + } + pEnumPins->Release(); + return success; +} + DeviceDiscovery::Device::Device(IMFMediaSource *source) : source(source) { source->AddRef(); auto result = MFCreateSourceReaderFromMediaSource(source, nullptr, &reader); diff --git a/Peripheral/DeviceDiscovery.h b/Peripheral/DeviceDiscovery.h index 33996d4..fde874a 100644 --- a/Peripheral/DeviceDiscovery.h +++ b/Peripheral/DeviceDiscovery.h @@ -8,6 +8,9 @@ #ifdef WIN32 #include #include + +class IPin; +class IBaseFilter; #endif class DeviceDiscovery { @@ -15,25 +18,34 @@ class DeviceDiscovery { public: constexpr static auto DeviceName = "UVC Camera"; + using Resolution = std::pair; + using Resolutions = std::vector; struct Device { + Device() = default; + std::string friendlyName; + std::string alternativeName; + Resolutions resolutions; ~Device(); #ifdef WIN32 Device(IMFMediaSource *source); IMFMediaSource *source = nullptr; IMFSourceReader *reader = nullptr; -#else - std::string name; #endif }; - using Resolution = std::pair; - using Resolutions = std::vector; DeviceDiscovery(); std::shared_ptr find(const std::string &deviceName, std::error_code &error); void enterOtaMode(const std::shared_ptr &device, std::error_code &error); std::vector devices(); + std::vector getDevices(); + bool SetResolution(const std::string &deviceName, int width, int height); protected: Resolutions deviceResolutions(const std::shared_ptr &source); +#ifdef WIN32 + void GetDeviceNames(IMoniker *pMoniker, Device &info); + void GetSupportedResolutions(IPin *pPin, std::vector> &resolutions); + bool SetPinResolution(IBaseFilter *pFilter, int width, int height); +#endif }; #endif // __DEVICEDISCOVERY_H__