WebRTC源码之摄像头视频数据采集源码分析

WebRTC、Video、Capture

Posted by chensong on 2022-09-03 23::50::33

WebRTC源码之摄像头视频数据采集源码分析

@TOC

</font>

<hr style=” border:solid; width:100px; height:1px;” color=#000000 size=1”>

WebRTC专题开嗨鸭 !!!

一、 WebRTC 线程模型

1、WebRTC中线程模型和常见线程模型介绍

2、WebRTC网络PhysicalSocketServer之WSAEventselect模型使用

二、 WebRTC媒体协商

1、WebRTC媒体协商之SDP中JsepSessionDescription类结构分析

2、WebRTC媒体协商之CreatePeerConnectionFactory、CreatePeerConnection、CreateOffer

三、 WebRTC 音频数据采集

1、WebRTC源码之音频设备播放流程源码分析

2、WebRTC源码之音频设备的录制流程源码分析

四、 WebRTC 音频引擎(编解码和3A算法)

五、 WebRTC 视频数据采集

1、WebRTC源码之摄像头视频数据采集源码分析

六、 WebRTC 视频引擎( 编解码)

七、 WebRTC 网络传输

1、WebRTC的ICE之STUN协议

2、WebRTC的ICE之Dtls/SSL/TLSv1.x协议详解

八、 WebRTC服务质量(Qos)

1、WebRTC中RTCP协议详解

2、WebRTC中RTP协议详解

3、WebRTC之NACK、RTX 在什么时机判断丢包发送NACK请求和RTX丢包重传

4、WebRTC源码之视频质量统计数据的数据结构分析

九、 NetEQ

十、 Simulcast与SVC

前言

WebRTC视频采集流程图

在这里插入图片描述

一、 VideoCapture

在这里插入图片描述

IPin::QueryDirection方法

HRESULT QueryDirection([out] PIN_DIRECTION* pPinDir);

参数1 获取Pin的方向   PINDIR_INPUT/PINDIR_OUT

1、 IKsPropertypSet

  1. 提供了访问驱动程序底层扩展属性的能力
  2. 它提供了Get/Set方法获取或设置扩展属性
  3. 通过Pin的QueryInterface可以获得该接口

WebRTC中使用 IKsPropertySet::Get方法

HRESULT Get([in] REFGUID rguidPropSet/*要访问的属性集*/,
        [in] ULONG ulId /*要访问的属性集中的某一项*/, 
        [out] LPVOID pInstanceData/*实例数据*/,
        [out] ULONG ulInstanceLength/*实例数据长度*/,
        [out] LPVOID pPropertyData/*读到的属性数据*/,
        [out] ULONG ulDataLength/*存放属性数据的长度*/,
        [out] PULONG pulBytesReturened/*真正读到的属性数据长度*/)

1、 ComRefCount

①、模板

// Provides a reference count implementation for COM (IUnknown derived) classes.
// The implementation uses atomics for managing the ref count.
template <class T>
class ComRefCount : public T {
 public:
  ComRefCount() {}

  template <class P0>
  explicit ComRefCount(P0&& p0) : T(std::forward<P0>(p0)) {}

  STDMETHOD_(ULONG, AddRef)() override {
    ref_count_.IncRef();
    return 1;
  }

  STDMETHOD_(ULONG, Release)() override {
    const auto status = ref_count_.DecRef();
    if (status == rtc::RefCountReleaseStatus::kDroppedLastRef) {
      delete this;
      return 0;
    }
    return 1;
  }

 protected:
  ~ComRefCount() {}

 private:
  webrtc::webrtc_impl::RefCounter ref_count_{0};
};

②、使用模板

// Create the sink filte used for receiving Captured frames.
  sink_filter_ = new ComRefCount<CaptureSinkFilter>(this);

input_pin_ = (new ComRefCount<CaptureInputPin>(this))

③、模板展开


// 1、是一个Filter
class ComRefCount : public CaptureSinkFilter {
 public:
  ComRefCount() {}
 
  explicit ComRefCount(VideoCaptureDS&& p0) : CaptureSinkFilter(std::forward<VideoCaptureDS>(p0)) {}

  STDMETHOD_(ULONG, AddRef)() override {
    ref_count_.IncRef();
    return 1;
  }

  STDMETHOD_(ULONG, Release)() override {
    const auto status = ref_count_.DecRef();
    if (status == rtc::RefCountReleaseStatus::kDroppedLastRef) {
      delete this;
      return 0;
    }
    return 1;
  }

 protected:
  ~ComRefCount() {}

 private:
  webrtc::webrtc_impl::RefCounter ref_count_{0};
};

/////////////////////
// 2. 是一个Pin
class ComRefCount : public CaptureInputPin {
 public:
  ComRefCount() {}
 
  explicit ComRefCount(CaptureSinkFilter&& p0) : CaptureInputPin(std::forward<CaptureSinkFilter>(p0)) {}

  STDMETHOD_(ULONG, AddRef)() override {
    ref_count_.IncRef();
    return 1;
  }

  STDMETHOD_(ULONG, Release)() override {
    const auto status = ref_count_.DecRef();
    if (status == rtc::RefCountReleaseStatus::kDroppedLastRef) {
      delete this;
      return 0;
    }
    return 1;
  }

 protected:
  ~ComRefCount() {}

 private:
  webrtc::webrtc_impl::RefCounter ref_count_{0};
};


3、PIN_INFO


typedef struct _PinInfo
    {
    IBaseFilter *pFilter; // Filter指针
    PIN_DIRECTION dir; // Pin的方向
    WCHAR achName[ 128 ]; // Pin的名子
    } 	PIN_INFO;

4、Filter与FilterGraph 关系图

Filter与FilterGraph 关系图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4i0NW6DK-1662190677029)(./img/filter_and_filtergraph.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kJEEYCVw-1662190677030)(./img/filter_connect.jpg)]

***WebRTC中底层CapturedFilter拿到数据, 然后应用层从SinkFilter获取数据***

6、如何将两个Filter连接在一起的api

①、IFilterGraph::ConnectDirect方法介绍


 IFilterGraph : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE AddFilter( 
            /* [in] */ IBaseFilter *pFilter,
            /* [string][in] */ LPCWSTR pName) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE RemoveFilter( 
            /* [in] */ IBaseFilter *pFilter) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE EnumFilters( 
            /* [annotation][out] */ 
            _Out_  IEnumFilters **ppEnum) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE FindFilterByName( 
            /* [string][in] */ LPCWSTR pName,
            /* [annotation][out] */ 
            _Out_  IBaseFilter **ppFilter) = 0;
        // 两个Filter连接在一起的api
         // TODO@chensong 20220828  将CapturePin和 sinkPin连接起来的api
	  // param1 : 前一个Filter的输出Pin
	  // param2 : 后一个Filter的输入Pin
	  // param3 : AM_MEDIA_TYPE等于DMO_MEDIA_TYPE
        virtual HRESULT STDMETHODCALLTYPE ConnectDirect( 
            /* [in] */ IPin *ppinOut,
            /* [in] */ IPin *ppinIn,
            /* [annotation][unique][in] */ 
            _In_opt_  const AM_MEDIA_TYPE *pmt) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE Reconnect( 
            /* [in] */ IPin *ppin) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE Disconnect( 
            /* [in] */ IPin *ppin) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE SetDefaultSyncSource( void) = 0;
        
    };

②、AM_MEDIA_TYPE类型解读

typedef struct _AMMediaType
    {
    GUID majortype;          // 流的主类型GUID
    GUID subtype;            // 流的子类型GUID
    BOOL bFixedSizeSamples;     // 采样算法是固定大小,音频为TRUE
    BOOL bTemporalCompression;   // 时域压缩
    ULONG lSampleSize;        // 以字节为单位的采样大小
    GUID formattype;         // 数据格式类型、音频为WAVEFORMATEX
    IUnknown *pUnk;          // 未使用
    ULONG cbFormat;           // 不同媒体类型格式块的大小
    /* [size_is] */ BYTE *pbFormat; // 根据cbFormat决定该字段
    } 	AM_MEDIA_TYPE;

3、 注意事项

  1. 只有在同一个FilterGraph中Filter才能进行连接
  2. Filter间的连接不能直接用Pin或Filter的相关方法
  3. 需要使用IFilterGraph的ConnectDirect方法

4、Filter的连接过程

FilterGraph调用输出Pin的Connect方法、连接输入Pin

之后调用输入Pin的ReceivConnection方法, 是否允许双方的连接

如果ReceivConnection接受连接,则两个Pin连接成功

5、 Pin协商媒体的类型

Complete Type : 指定了具体的媒体类型 : 举例子: I420格式, rgba格式 等等

Parial Media Type : 部分类型为GUID_NULL、表示任意类型

No Media Type: 传入NULL, 表示两个Pin可以接受任意媒体类型

6、协商分配器

两个Filter交换媒体数据的机制称为transport

通常两个Filter之间通过本地内存来交换数据

有两种本地内存交换数据的方式: push和pull

7、 push与pull

push模式: 源Filter使用IMeminputPin, push数据到下游Filter

pull模式: 下游Filter向源Filter请求,通过IAsyncReader获取数据

WebRTC使用的是push模式

8、allocator

负责分配内存Buffer的对象称为allocator

allocator支持IMemAllocator接口

两个Pin共享同一个allocator

无论谁提供allocator,最终由输出Pin决定使用那个allocator

输出Pin还决定allocator的属性

  1. allocator需要创建多少个buffer
  2. 每个buffer的大小
  3. 内存是否对齐

9、allocator协商的过程

输出Pin调用输入Pin的GetAllocatorRequirements获取buffer大小

输出Pin调用输入Pin的GetAllocator获取allocator

输入Pin调用NotifyAllocator通知输入Pin所做的选择

当流开始或停止时,输出Pin负责提交或者撤销allocator

FilterGraph中的数据流

有两种数据流: 媒体数据和控制数据

媒体数据向下游传播,控制数据向上游传播

音频、视频等都属于媒体数据

flush、流结束通知等都属于控制数据

投递采样

输出Pin调用输入Pin的Receive和ReciveMultiple投递采样

如果Pin可以阻塞, ReceivecanBlock能返回S_OK

但通信情况下不应该产生阻塞

SetCameraOutput流程图

六大步骤

在这里插入图片描述

IAMStreamConfig

该接口用于报告/设置设备支持的格式和能力

CaptureFilter的CapturePin和PreviewPin支持该接口

GetNumberOfCapabilities获取Pin支持的Capa数量

GetStreamCaps获取某个具体Capability

SetFormat用于设置硬件设备的输出格式

 MIDL_INTERFACE("C6E13340-30AC-11d0-A18C-00A0C9118956")
    IAMStreamConfig : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE SetFormat( 
            /* [in] */ AM_MEDIA_TYPE *pmt) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetFormat( 
            /* [annotation][out] */ 
            _Out_  AM_MEDIA_TYPE **ppmt) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetNumberOfCapabilities( 
            /* [annotation][out] */ 
            _Out_  int *piCount/*支持哪些Capabilities*/,
            /* [annotation][out] */ 
            _Out_  int *piSize/*每个Capabilities的大小*/) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetStreamCaps( 
            /* [in] */ int iIndex/*指定获取某个Index的Capability, 从0开始*/,
            /* [annotation][out] */ 
            _Out_  AM_MEDIA_TYPE **ppmt/*该方法分配且用媒体类型填充的*/,
            /* [annotation][out] */ 
            _Out_  BYTE *pSCC/*调用者分配 VIDEO_STREAM_CONFIG_CAPS*/) = 0;
        
    };

IAMStreamControl 控制播放

该接口用于控制视频采集操作, 如枚举帧率于图像方向

GetFrameRateList用于获取采集帧率列表

MIDL_INTERFACE("6a2e0670-28e4-11d0-a18c-00a0c9118956")
    IAMVideoControl : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE GetCaps( 
            /* [in] */ IPin *pPin,
            /* [annotation][out] */ 
            _Out_  long *pCapsFlags) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE SetMode( 
            /* [in] */ IPin *pPin,
            /* [in] */ long Mode) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetMode( 
            /* [in] */ IPin *pPin,
            /* [annotation][out] */ 
            _Out_  long *Mode) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetCurrentActualFrameRate( 
            /* [in] */ IPin *pPin,
            /* [annotation][out] */ 
            _Out_  LONGLONG *ActualFrameRate) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetMaxAvailableFrameRate( 
            /* [in] */ IPin *pPin,
            /* [in] */ long iIndex,
            /* [in] */ SIZE Dimensions,
            /* [annotation][out] */ 
            _Out_  LONGLONG *MaxAvailableFrameRate) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetFrameRateList( 
            /* [in] */ IPin *pPin /*查询帧率的Pin*/,
            /* [in] */ long iIndex /*查询帧率格式的索引值*/,
            /* [in] */ SIZE Dimensions /*以像素为单位图像的大小*/,
            /* [annotation][out] */ 
            _Out_  long *ListSize/*用于存放帧率列表元素个数*/,
            /* [annotation][out] */ 
            _Out_  LONGLONG **FrameRates/*存放帧率数组的地址指针*/) = 0;
        
    };

总结:

WebRTC源码分析地址:https://github.com/chensongpoixs/cwebrtc