---
# System prepended metadata

title: USB Video Class

---

USB Video Class
===
UVC(USB Video Class)是給透過USB傳輸數據的一種協定，讓USB的產品不需要額外安裝driver就能直接使用，包括網路攝影機、類比影像轉換器...等。



![](https://i.imgur.com/q0QjxhI.png)


* Input Terminal：基本上是描述這個裝置的輸入端，以及它支援的功能。
* Output Terminal：基本上是描述這個裝置的輸出端，以及它支援的功能。
* Camera Terminal：基本上是描述這個裝置的相機功能，像是Focus或Zoom等等的功能。
* Select Unit：簡而言之就是MUX。
* Processing Unit：基本上是描述這個裝置的影像處理功能，像是Hue或Brightness等等的功能。
* Extension Unit：描述這個裝置支援的延伸性功能，可有可無。

工作上需求要寫一個類似 AMCap 功能的應用，也是透過 UVC 方法進行讀取資料並做影像處理功能，沒用到影像處理函式庫(OpenCV)，那就要想辦法能在 Win10 上直接 Capture 影像資訊進來，而微軟定義了設備與上層應用間的通訊驅動接口，目前想法是透過微軟寫好的 function 去跟接口獲取資訊；另外還要加上 UVC Extension command 的應用這部分架構可大的很...

當然網路上也有說到有哪些方法可行，這邊採用 DirectShow 進行操作。
1. DirectShow
2. Windows MediaFunction
3. Video for Windows

--- 
> QT 在 windows 上的多媒體功能是採用兩大 windows 內部應用 -- DirectShow、WMF (Windows Media Foundation)，其包含基本的相機功能(Capture、viewfinder)但相機相關控制就沒辦法使用。
> Qt Multimedia on Windows：https://doc.qt.io/qt-5/qtmultimedia-windows.html

由於要做的應用自己希望學到的框架能在不同系統下都能工作，那寫 MFC (聽說MFC很難學)就只能單單在 windows 上跑，就想說在 QT 上開發而自己本身也對 QT 略知一二，但為了處理較底層還是得採用 Windows 專門寫的多媒體應用(DirectShow)。

MSDN 寫了很詳細的 [DirectShow](https://docs.microsoft.com/en-us/windows/win32/directshow/directshow) 說明可以直接去參考，那這邊就簡易說明目前所了解的。

DirectShow 的基本由 [Component Object Model (COM)](https://zh.wikipedia.org/wiki/%E7%BB%84%E4%BB%B6%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B)所組成，它支持類比與數位裝置的應用(Windows Driver Model (WDM) or Video for Windows)也支援很種影像格式。

下圖為 directshow 整體系統，透過 filter graph 來管理整個資訊串流，它分為三大 filter
* Source Filters：數據採集、讀取
* Transform Filters：數據格式轉換、傳輸 (Capture Filter、Grabber Filter...等)
* Redering Filters：數據輸出到外部設備

可以看到 directshow 已將底層包含進去，透過該方法可以獲取 UVC 裝置並將影像輸出。

![](https://i.imgur.com/ucra8T4.png)

**Filter Graph Manager** 是 directshow 的控制中心，主要控制 filter 狀態
1. 協調 filter graph 狀態(暫停、動作、停止)
2. 提供一套建立 filter graph 方法
3. 與應用程式進行溝通，將 filter graph 內部事件傳遞給應用程式

![](https://i.imgur.com/89Sv05k.png)

在程式的部分主要 filter graph 都是由 `CoCreateInstance` 所生成，在[Capture Filter](https://docs.microsoft.com/en-us/windows/win32/directshow/about-the-capture-graph-builder)中可以看到該範例生成了 **filter graph manager** 與 **capture graph builder**，並將 capture graph builder 串接在 filter graph manager 上。

```cpp=
// Create the Capture Graph Builder.
HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, 
    CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (void**)&pBuild );
if (SUCCEEDED(hr))
{
    // Create the Filter Graph Manager.
    hr = CoCreateInstance(CLSID_FilterGraph, 0, CLSCTX_INPROC_SERVER,
        IID_IGraphBuilder, (void**)&pGraph);
    if (SUCCEEDED(hr))
    {
        // Initialize the Capture Graph Builder.
        pBuild->SetFiltergraph(pGraph);
```

![](https://i.imgur.com/WNwdDd6.png)


直接進入要點 [Video Capture](https://docs.microsoft.com/en-us/windows/win32/directshow/video-capture) 部分，要從外接 USB 裝置取出影像就得先抓到裝置的資訊，[Selecting a Capture Device](https://docs.microsoft.com/en-us/windows/win32/directshow/selecting-a-capture-device) 這裡可以清楚知道該如何抓取裝置 ID/Description，這邊主要要提到的是在抓取裝置後會藉由 `bindToObject` 這方法來綁定窗口，後續的操作也都會用到該窗口動作(代表裝置接口)。


綁定裝置後就可以直接來看到[Previewing Video (DirectShow)](https://docs.microsoft.com/en-us/windows/win32/directshow/previewing-video)，[RenderStream](https://docs.microsoft.com/en-us/previous-versions/aa930715(v=msdn.10)?redirectedfrom=MSDN) 這方法能將 filter graph 串接起來，從裡面範例來看當我們建立好 filter graph manager 與 capture graph builder 後將裝置給綁定在 IBaseFilter 就能透過 `RenderStream` 將這些 graph 給串接起來，參數部分 MSDN 就有說明這邊就不再贅述，這邊要提到的是 `RenderStream` 的後面三個參數就是將 source filter → transform filter → render filter 給串接起來，當然有很多串接方法這是一種方便快速建置串接的方法。

```cpp=
ICaptureGraphBuilder2 *pBuild; // Capture Graph Builder
// Initialize pBuild (not shown).

IBaseFilter *pCap; // Video capture filter.

/* Initialize pCap and add it to the filter graph (not shown). */

hr = pBuild->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, 
    pCap, NULL, NULL);
```

![](https://i.imgur.com/X0t71Ua.png)


當需要顯示畫面與抓取影像時就需要將兩種不同的串流給合併在一起，這不是甚麼困難事微軟都幫我們處理好了，可以看一下[Combining Video Capture and Preview](https://docs.microsoft.com/en-us/windows/win32/directshow/combining-video-capture-and-preview)簡單透過不同`RenderStream`就能分開不同串流，裡面也提到如果裝置沒有 preview 的話也可以透過 capture pin 自動分出 preview 與 capture 效果。

```cpp=
// Render the preview stream to the video renderer.
hr = pBuild->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, pCap, 
    NULL, NULL);

// Render the capture stream to the mux.
hr = pBuild->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pCap, 
    NULL, pMux);
```

![](https://i.imgur.com/ShWmAPp.png)


串接好的 capture graph 該如何控制，Graph filter manager 提供一個方法 [IMediaControl](https://docs.microsoft.com/en-us/windows/win32/api/control/nn-control-imediacontrol)可以對整個圖流指示開始結束與暫停，而 `ControlStream` 則可以單獨控制 preview pin 或 capture pin。以下範例 `rtStart` 如設為 MAXLONGLONG 則表示永遠都不開始，設為 0 則表示當 pControl 開始時就立即開始串流，而 `rtStop` 也是相同意思。
```cpp=
// Control the video capture stream. 
REFERENCE_TIME rtStart = 0, rtStop = MAXLONGLONG;
const WORD wStartCookie = 1, wStopCookie = 2;  // Arbitrary values.
hr = pBuild->ControlStream(
    &PIN_CATEGORY_CAPTURE, // Pin category.
    &MEDIATYPE_Video,      // Media type.
    pCap,                 // Capture filter.
    &rtStart, &rtStop,     // Start and stop times.
    wStartCookie, wStopCookie  // Values for the start and stop events.
);
pControl->Run();
```


那有 graph manager filter 該怎麼把其他 graph 也加入到裡面呢? 透過他的方法`AddFilter`就能將後續建立好的 graph 都加入到 graph filter 之中，這裡有一個簡易的範例可以參考 [UVCCaptureDemo](https://github.com/liuleidong/UVCCaptureDemo/blob/master/CaptureDemo/CaptureDemo.cpp)。



[**IVideoWindow**](https://docs.microsoft.com/en-us/windows/win32/api/control/nn-control-ivideowindow) ：用来控制影像窗口，以下常見的方法
* [put_Owner](https://docs.microsoft.com/en-us/windows/win32/api/control/nf-control-ivideowindow-get_owner) 為影像窗口指定父窗口，通過這個方法，我们可以將影像窗口嵌到自己的窗口

GUI 如果由 MFC 寫就可以透過上述 `IVideoWindow` 窗口將結果 push 到寫好的畫面上，但本次要在 QT 上完成影像串流，資料傳輸部分勢必得想辦法取出，因為 DirectShow 正常 render 程序都是寫進 buffer 之間去做串流，那就要找有無 filter 能把 buffer 內的資料給拉出來。

透過 [Sample Grabber](https://docs.microsoft.com/en-us/windows/win32/directshow/using-the-sample-grabber?redirectedfrom=MSDN) 作為 transform filter 能在串流之間將 sample 給拉出，有一個方法 `GetCurrentBuffer` 能將 buffer 複製到自訂的陣列內，但發現很長一段時間會 Failed (VFW_E_WRONG_STATE)，以下網址對於這 method 的評論，他們建議不要使用 `GetCurrentBuffer` 會有執行緒問題，倒是推薦使用 Sample Grabber 內的 `SampleCB callback` 能穩定輸出資料。
>https://stackoverflow.com/questions/25141125/directshow-samplegrabber-and-null-render
>https://stackoverflow.com/questions/22229769/the-operation-could-not-be-performed-because-the-filter-is-in-the-wrong-state-ge
>https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/21091ecb-ad42-4533-b877-4467895641aa/isamplegrabbergetcurrentbuffer-returning-vfwewrongstate?forum=windowsdirectshowdevelopment

而 callbak 內的函式是 virtual fucntion 所以需要我們自己去定義 function 內容，意思是可以根據 virutal function 給的參數進行操作，以下網址為 callback function 定義，原本在 win 7 內是可以引入 `qedit.h`，它是一個可以讓用戶客製化 directshow 的 header，但在 win 10 卻被移除了，不是很清楚為何要移除它或是有其他代替方案?!

>https://social.msdn.microsoft.com/Forums/vstudio/en-US/df2dd8ab-055e-44c3-931c-0f4778a17561/why-clsidsamplegrabber-is-undefined-how-can-i-resolve-it-?forum=vcgeneral


UVC command 的 PU 作法，能將裝置給予的影像進行操作，包含影像傳輸格式、影像品質(白平衡、對比、飽和度、亮度...等)、相機控制(焦距、光圈曝光度...等)，配置這些屬性時 graph stream 是不用停止的。

> https://docs.microsoft.com/en-us/windows/win32/directshow/configure-the-video-output-format
> https://docs.microsoft.com/en-us/windows/win32/directshow/configure-the-video-quality

## 參考
>USB影片類別：https://zh.wikipedia.org/wiki/USB%E8%A6%96%E9%A0%BB%E9%A1%9E%E5%88%A5
>How do I read a video camera in a win32 C program：https://stackoverflow.com/questions/939168/how-do-i-read-a-video-camera-in-a-win32-c-program
>Take high resolution photo from USB camera in Windows (C++)：https://stackoverflow.com/questions/52796238/take-high-resolution-photo-from-usb-camera-in-windows-c
>Windows + Qt and how to capture webcam feed without OpenCV：https://stackoverflow.com/questions/15458274/windows-qt-and-how-to-capture-webcam-feed-without-opencv
>DirectShow学习笔记：https://blog.csdn.net/bins2002/article/details/1351596
>DirectShow学习笔记总结：https://blog.csdn.net/weixin_33858249/article/details/85507414
>Windows AMCap code：https://github.com/microsoft/Windows-classic-samples/tree/master/Samples/Win7Samples/multimedia/directshow/capture/amcap
>UVCCapture Code：https://gitee.com/fsfzp888/UVCCapture/tree/master
>Capture image from a streaming URL using different ISampleGrabber modes：https://www.codeproject.com/Articles/43024/Capture-image-from-a-streaming-URL-using-different
>Introduction to COM - What It Is and How to Use It.：https://www.codeproject.com/Articles/633/Introduction-to-COM-What-It-Is-and-How-to-Use-It
>UVCCaptureDemo：https://github.com/liuleidong/UVCCaptureDemo/tree/master/CaptureDemo
>DirectShow filter相關：https://www.twblogs.net/a/5b829dd92b717766a1e91709
>流媒体之DirectShow——视频采集：https://blog.csdn.net/yibu_refresh/article/details/94029181

{%hackmd BJrTq20hE %}