WebRTC技术的出现改变了传统即时通信的现状,它是一套开源的旨在建立浏览器端对端的通信标准的技术,支持浏览器平台,使用P2P架构。WebRTC所采用的技术都是当前VoIP先进的技术,如内部所采用的音频引擎是Google收购知名GIPS公司获得的核心技术:视频编解码则采用了VP8。

大家都说WebRTC好,是未来的趋势,但是不得不说这个开源项目对新手学习实在是太不友好,光是windows平台下的编译就能耗费整整一天的精力,还未必能成功,关于这个问题在我之前的文章中有所描述。编译成功之后打开一看,整个solution里面有215个项目,绝对让人当时就懵了,而且最重要的是,google方面似乎没给出什么有用的文档供人参考,网络上有关的资料也多是有关于web端开发的,和Native API开发有关的内容少之又少,于是我决定把自己这两天学习VideoEngine的成果分享出来,供大家参考,有什么问题也欢迎大家指出,一起学习一起进步。

首先需要说明的是,webrtc项目的all.sln下有一个vie_auto_test项目,里面包含了一些针对VideoEngine的测试程序,我这里的demo就是基于此修改得到的。


先来看一下VideoEngine的核心API,基本上就在以下几个头文件中了。


具体来说

ViEBase用于

- 创建和销毁 VideoEngine 实例

- 创建和销毁 channels
- 将 video channel 和相应的 voice channel 连接到一起并同步
- 发送和接收的开始与停止

ViECapture用于

- 分配capture devices.
- 将 capture device 与一个或多个 channels连接起来.
- 启动或停止 capture devices.
- 获得capture device 的可用性.

ViECodec用于

- 设置发送和接收的编解码器.

- 设置编解码器特性.

- Key frame signaling.

- Stream management settings.

ViEError即一些预定义的错误消息

ViEExternalCodec用于注册除VP8之外的其他编解码器

ViEImageProcess提供以下功能

- Effect filters
- 抗闪烁
- 色彩增强

ViENetwork用于

- 配置发送和接收地址.
- External transport support.
- 端口和地址过滤.
- Windows GQoS functions and ToS functions.
- Packet timeout notification.
- Dead‐or‐Alive connection observations.

ViERender用于

- 为输入视频流、capture device和文件指定渲染目标.
- 配置render streams.

ViERTP_RTCP用于

- Callbacks for RTP and RTCP events such as modified SSRC or CSRC.

- SSRC handling.
- Transmission of RTCP reports.
- Obtaining RTCP data from incoming RTCP sender reports.
- RTP and RTCP statistics (jitter, packet loss, RTT etc.).
- Forward Error Correction (FEC).
- Writing RTP and RTCP packets to binary files for off‐line analysis of the call quality.
- Inserting extra RTP packets into active audio stream.


下面将以实现一个视频通话功能为实例详细介绍VideoEngine的使用,在文末将附上相应源码的下载地址

第一步是创建一个VideoEngine实例,如下

[cpp]  view plain copy
  1. webrtc::VideoEngine* ptrViE = NULL;  
  2.     ptrViE = webrtc::VideoEngine::Create();  
  3.     if (ptrViE == NULL)  
  4.     {  
  5.         printf("ERROR in VideoEngine::Create\n");  
  6.         return -1;  
  7.     }  
然后初始化VideoEngine并创建一个Channel
[cpp]  view plain copy
  1. webrtc::ViEBase* ptrViEBase = webrtc::ViEBase::GetInterface(ptrViE);  
  2.     if (ptrViEBase == NULL)  
  3.     {  
  4.         printf("ERROR in ViEBase::GetInterface\n");  
  5.         return -1;  
  6.     }  
  7.   
  8.     error = ptrViEBase->Init();//这里的Init其实是针对VideoEngine的初始化  
  9.     if (error == -1)  
  10.     {  
  11.         printf("ERROR in ViEBase::Init\n");  
  12.         return -1;  
  13.     }  
  14.   
  15.     webrtc::ViERTP_RTCP* ptrViERtpRtcp =  
  16.         webrtc::ViERTP_RTCP::GetInterface(ptrViE);  
  17.     if (ptrViERtpRtcp == NULL)  
  18.     {  
  19.         printf("ERROR in ViERTP_RTCP::GetInterface\n");  
  20.         return -1;  
  21.     }  
  22.   
  23.     int videoChannel = -1;  
  24.     error = ptrViEBase->CreateChannel(videoChannel);  
  25.     if (error == -1)  
  26.     {  
  27.         printf("ERROR in ViEBase::CreateChannel\n");  
  28.         return -1;  
  29.     }  
列出可用的capture devices等待用户进行选择, 然后进行allocate和connect,最后start选中的capture device
[cpp]  view plain copy
  1. webrtc::ViECapture* ptrViECapture =  
  2.         webrtc::ViECapture::GetInterface(ptrViE);  
  3.     if (ptrViEBase == NULL)  
  4.     {  
  5.         printf("ERROR in ViECapture::GetInterface\n");  
  6.         return -1;  
  7.     }  
  8.   
  9.     const unsigned int KMaxDeviceNameLength = 128;  
  10.     const unsigned int KMaxUniqueIdLength = 256;  
  11.     char deviceName[KMaxDeviceNameLength];  
  12.     memset(deviceName, 0, KMaxDeviceNameLength);  
  13.     char uniqueId[KMaxUniqueIdLength];  
  14.     memset(uniqueId, 0, KMaxUniqueIdLength);  
  15.   
  16.     printf("Available capture devices:\n");  
  17.     int captureIdx = 0;  
  18.     for (captureIdx = 0;  
  19.         captureIdx < ptrViECapture->NumberOfCaptureDevices();  
  20.         captureIdx++)  
  21.     {  
  22.         memset(deviceName, 0, KMaxDeviceNameLength);  
  23.         memset(uniqueId, 0, KMaxUniqueIdLength);  
  24.   
  25.         error = ptrViECapture->GetCaptureDevice(captureIdx, deviceName,  
  26.             KMaxDeviceNameLength, uniqueId,  
  27.             KMaxUniqueIdLength);  
  28.         if (error == -1)  
  29.         {  
  30.             printf("ERROR in ViECapture::GetCaptureDevice\n");  
  31.             return -1;  
  32.         }  
  33.         printf("\t %d. %s\n", captureIdx + 1, deviceName);  
  34.     }  
  35.     printf("\nChoose capture device: ");  
  36.   
  37.     if (scanf("%d", &captureIdx) != 1)  
  38.     {  
  39.         printf("Error in scanf()\n");  
  40.         return -1;  
  41.     }  
  42.     getchar();  
  43.     captureIdx = captureIdx - 1; // Compensate for idx start at 1.  
  44.   
  45.     error = ptrViECapture->GetCaptureDevice(captureIdx, deviceName,  
  46.         KMaxDeviceNameLength, uniqueId,  
  47.         KMaxUniqueIdLength);  
  48.     if (error == -1)  
  49.     {  
  50.         printf("ERROR in ViECapture::GetCaptureDevice\n");  
  51.         return -1;  
  52.     }  
  53.   
  54.     int captureId = 0;  
  55.     error = ptrViECapture->AllocateCaptureDevice(uniqueId, KMaxUniqueIdLength,  
  56.         captureId);  
  57.     if (error == -1)  
  58.     {  
  59.         printf("ERROR in ViECapture::AllocateCaptureDevice\n");  
  60.         return -1;  
  61.     }  
  62.   
  63.     error = ptrViECapture->ConnectCaptureDevice(captureId, videoChannel);  
  64.     if (error == -1)  
  65.     {  
  66.         printf("ERROR in ViECapture::ConnectCaptureDevice\n");  
  67.         return -1;  
  68.     }  
  69.   
  70.     error = ptrViECapture->StartCapture(captureId);  
  71.     if (error == -1)  
  72.     {  
  73.         printf("ERROR in ViECapture::StartCapture\n");  
  74.         return -1;  
  75.     }  
设置RTP/RTCP所采用的模式
[cpp]  view plain copy
  1. error = ptrViERtpRtcp->SetRTCPStatus(videoChannel,  
  2.         webrtc::kRtcpCompound_RFC4585);  
  3.     if (error == -1)  
  4.     {  
  5.         printf("ERROR in ViERTP_RTCP::SetRTCPStatus\n");  
  6.         return -1;  
  7.     }  
设置接收端解码器出问题的时候,比如关键帧丢失或损坏,如何重新请求关键帧的方式
[cpp]  view plain copy
  1. error = ptrViERtpRtcp->SetKeyFrameRequestMethod(  
  2.         videoChannel, webrtc::kViEKeyFrameRequestPliRtcp);  
  3.     if (error == -1)  
  4.     {  
  5.         printf("ERROR in ViERTP_RTCP::SetKeyFrameRequestMethod\n");  
  6.         return -1;  
  7.     }  
设置是否为当前channel使用REMB(Receiver Estimated Max Bitrate)包,发送端可以用它表明正在编码当前channel
接收端用它来记录当前channel的估计码率
[cpp]  view plain copy
  1. error = ptrViERtpRtcp->SetRembStatus(videoChannel, truetrue);  
  2.     if (error == -1)  
  3.     {  
  4.         printf("ERROR in ViERTP_RTCP::SetTMMBRStatus\n");  
  5.         return -1;  
  6.     }  
设置rendering用于显示
[cpp]  view plain copy
  1. webrtc::ViERender* ptrViERender = webrtc::ViERender::GetInterface(ptrViE);  
  2.     if (ptrViERender == NULL) {  
  3.         printf("ERROR in ViERender::GetInterface\n");  
  4.         return -1;  
  5.     }  
显示本地摄像头数据,这里的window1和下面的window2都是显示窗口,更详细的内容后面再说
[cpp]  view plain copy
  1. error  
  2.         = ptrViERender->AddRenderer(captureId, window1, 0, 0.0, 0.0, 1.0, 1.0);  
  3.     if (error == -1)  
  4.     {  
  5.         printf("ERROR in ViERender::AddRenderer\n");  
  6.         return -1;  
  7.     }  
  8.   
  9.     error = ptrViERender->StartRender(captureId);  
  10.     if (error == -1)  
  11.     {  
  12.         printf("ERROR in ViERender::StartRender\n");  
  13.         return -1;  
  14.     }  
显示接收端收到的解码数据
[cpp]  view plain copy
  1. error = ptrViERender->AddRenderer(videoChannel, window2, 1, 0.0, 0.0, 1.0,  
  2.         1.0);  
  3.     if (error == -1)  
  4.     {  
  5.         printf("ERROR in ViERender::AddRenderer\n");  
  6.         return -1;  
  7.     }  
  8.   
  9.     error = ptrViERender->StartRender(videoChannel);  
  10.     if (error == -1)  
  11.     {  
  12.         printf("ERROR in ViERender::StartRender\n");  
  13.         return -1;  
  14.     }  
设置编解码器
[cpp]  view plain copy
  1. webrtc::ViECodec* ptrViECodec = webrtc::ViECodec::GetInterface(ptrViE);  
  2.     if (ptrViECodec == NULL)  
  3.     {  
  4.         printf("ERROR in ViECodec::GetInterface\n");  
  5.         return -1;  
  6.     }  
  7.   
  8.     VideoCodec videoCodec;  
  9.   
  10.     int numOfVeCodecs = ptrViECodec->NumberOfCodecs();  
  11.     for (int i = 0; i<numOfVeCodecs; ++i)  
  12.     {  
  13.         if (ptrViECodec->GetCodec(i, videoCodec) != -1)  
  14.         {  
  15.             if (videoCodec.codecType == kVideoCodecVP8)  
  16.                 break;  
  17.         }  
  18.     }  
  19.   
  20.     videoCodec.targetBitrate = 256;  
  21.     videoCodec.minBitrate = 200;  
  22.     videoCodec.maxBitrate = 300;  
  23.     videoCodec.maxFramerate = 25;  
  24.   
  25.     error = ptrViECodec->SetSendCodec(videoChannel, videoCodec);  
  26.     assert(error != -1);  
  27.   
  28.     error = ptrViECodec->SetReceiveCodec(videoChannel, videoCodec);  
  29.     assert(error != -1);  
设置接收和发送地址,然后开始发送和接收
[cpp]  view plain copy
  1. webrtc::ViENetwork* ptrViENetwork =  
  2.         webrtc::ViENetwork::GetInterface(ptrViE);  
  3.     if (ptrViENetwork == NULL)  
  4.     {  
  5.         printf("ERROR in ViENetwork::GetInterface\n");  
  6.         return -1;  
  7.     }  
  8.     //VideoChannelTransport是由我们自己定义的类,后面将会详细介绍  
  9.     VideoChannelTransport* video_channel_transport = NULL;  
  10.   
  11.     video_channel_transport = new VideoChannelTransport(  
  12.         ptrViENetwork, videoChannel);  
  13.   
  14.     const char* ipAddress = "127.0.0.1";  
  15.     const unsigned short rtpPort = 6000;  
  16.     std::cout << std::endl;  
  17.     std::cout << "Using rtp port: " << rtpPort << std::endl;  
  18.     std::cout << std::endl;  
  19.   
  20.     error = video_channel_transport->SetLocalReceiver(rtpPort);  
  21.     if (error == -1)  
  22.     {  
  23.         printf("ERROR in SetLocalReceiver\n");  
  24.         return -1;  
  25.     }  
  26.     error = video_channel_transport->SetSendDestination(ipAddress, rtpPort);  
  27.     if (error == -1)  
  28.     {  
  29.         printf("ERROR in SetSendDestination\n");  
  30.         return -1;  
  31.     }  
  32.   
  33.     error = ptrViEBase->StartReceive(videoChannel);  
  34.     if (error == -1)  
  35.     {  
  36.         printf("ERROR in ViENetwork::StartReceive\n");  
  37.         return -1;  
  38.     }  
  39.   
  40.     error = ptrViEBase->StartSend(videoChannel);  
  41.     if (error == -1)  
  42.     {  
  43.         printf("ERROR in ViENetwork::StartSend\n");  
  44.         return -1;  
  45.     }  
设置按下回车键即停止通话
[cpp]  view plain copy
  1. printf("\n call started\n\n");  
  2.     printf("Press enter to stop...");  
  3.     while ((getchar()) != '\n')  
  4.         ;  
停止通话后的各种stop

[cpp]  view plain copy
  1. error = ptrViEBase->StopReceive(videoChannel);  
  2. if (error == -1)  
  3. {  
  4.     printf("ERROR in ViEBase::StopReceive\n");  
  5.     return -1;  
  6. }  
  7.   
  8. error = ptrViEBase->StopSend(videoChannel);  
  9. if (error == -1)  
  10. {  
  11.     printf("ERROR in ViEBase::StopSend\n");  
  12.     return -1;  
  13. }  
  14.   
  15. error = ptrViERender->StopRender(captureId);  
  16. if (error == -1)  
  17. {  
  18.     printf("ERROR in ViERender::StopRender\n");  
  19.     return -1;  
  20. }  
  21.   
  22. error = ptrViERender->RemoveRenderer(captureId);  
  23. if (error == -1)  
  24. {  
  25.     printf("ERROR in ViERender::RemoveRenderer\n");  
  26.     return -1;  
  27. }  
  28.   
  29. error = ptrViERender->StopRender(videoChannel);  
  30. if (error == -1)  
  31. {  
  32.     printf("ERROR in ViERender::StopRender\n");  
  33.     return -1;  
  34. }  
  35.   
  36. error = ptrViERender->RemoveRenderer(videoChannel);  
  37. if (error == -1)  
  38. {  
  39.     printf("ERROR in ViERender::RemoveRenderer\n");  
  40.     return -1;  
  41. }  
  42. error = ptrViECapture->StopCapture(captureId);  
  43. if (error == -1)  
  44. {  
  45.     printf("ERROR in ViECapture::StopCapture\n");  
  46.     return -1;  
  47. }  
  48.   
  49. error = ptrViECapture->DisconnectCaptureDevice(videoChannel);  
  50. if (error == -1)  
  51. {  
  52.     printf("ERROR in ViECapture::DisconnectCaptureDevice\n");  
  53.     return -1;  
  54. }  
  55.   
  56. error = ptrViECapture->ReleaseCaptureDevice(captureId);  
  57. if (error == -1)  
  58. {  
  59.     printf("ERROR in ViECapture::ReleaseCaptureDevice\n");  
  60.     return -1;  
  61. }  
  62.   
  63. error = ptrViEBase->DeleteChannel(videoChannel);  
  64. if (error == -1)  
  65. {  
  66.     printf("ERROR in ViEBase::DeleteChannel\n");  
  67.     return -1;  
  68. }  
  69.   
  70. delete video_channel_transport;  
  71.   
  72. int remainingInterfaces = 0;  
  73. remainingInterfaces = ptrViECodec->Release();  
  74. remainingInterfaces += ptrViECapture->Release();  
  75. remainingInterfaces += ptrViERtpRtcp->Release();  
  76. remainingInterfaces += ptrViERender->Release();  
  77. remainingInterfaces += ptrViENetwork->Release();  
  78. remainingInterfaces += ptrViEBase->Release();  
  79. if (remainingInterfaces > 0)  
  80. {  
  81.     printf("ERROR: Could not release all interfaces\n");  
  82.     return -1;  
  83. }  
  84.   
  85. bool deleted = webrtc::VideoEngine::Delete(ptrViE);  
  86. if (deleted == false)  
  87. {  
  88.     printf("ERROR in VideoEngine::Delete\n");  
  89.     return -1;  
  90. }  
  91.   
  92. return 0;  


以上就是VideoEngine的基本使用流程,下面说一下显示窗口如何创建

这里使用了webrtc已经为我们定义好的类ViEWindowCreator,它有一个成员函数CreateTwoWindows可以直接创建两个窗口,只需实现定义好窗口名称、窗口大小以及坐标即可,如下

[cpp]  view plain copy
  1. ViEWindowCreator windowCreator;  
  2.     ViEAutoTestWindowManagerInterface* windowManager =  
  3.         windowCreator.CreateTwoWindows();  
  4.     VideoEngineSample(windowManager->GetWindow1(),  
  5.         windowManager->GetWindow2());  
这里的VideoEngineSample就是我们在前面所写的包含全部流程的示例程序,它以两个窗口的指针作为参数

至于前面提到的VideoChannelTransport定义如下

[cpp]  view plain copy
  1. class VideoChannelTransport : public webrtc::test::UdpTransportData {  
  2. public:  
  3.     VideoChannelTransport(ViENetwork* vie_network, int channel);  
  4.   
  5.     virtual  ~VideoChannelTransport();  
  6.   
  7.     // Start implementation of UdpTransportData.  
  8.     virtual void IncomingRTPPacket(const int8_t* incoming_rtp_packet,  
  9.         const int32_t packet_length,  
  10.         const char/*from_ip*/,  
  11.         const uint16_t /*from_port*/) OVERRIDE;  
  12.   
  13.     virtual void IncomingRTCPPacket(const int8_t* incoming_rtcp_packet,  
  14.         const int32_t packet_length,  
  15.         const char/*from_ip*/,  
  16.         const uint16_t /*from_port*/) OVERRIDE;  
  17.     // End implementation of UdpTransportData.  
  18.   
  19.     // Specifies the ports to receive RTP packets on.  
  20.     int SetLocalReceiver(uint16_t rtp_port);  
  21.   
  22.     // Specifies the destination port and IP address for a specified channel.  
  23.     int SetSendDestination(const char* ip_address, uint16_t rtp_port);  
  24.   
  25. private:  
  26.     int channel_;  
  27.     ViENetwork* vie_network_;  
  28.     webrtc::test::UdpTransport* socket_transport_;  
  29. };  
  30.   
  31. VideoChannelTransport::VideoChannelTransport(ViENetwork* vie_network,  
  32.     int channel)  
  33.     : channel_(channel),  
  34.     vie_network_(vie_network) {  
  35.     uint8_t socket_threads = 1;  
  36.     socket_transport_ = webrtc::test::UdpTransport::Create(channel, socket_threads);  
  37.     int registered = vie_network_->RegisterSendTransport(channel,  
  38.         *socket_transport_);  
  39. }  
  40.   
  41. VideoChannelTransport::~VideoChannelTransport() {  
  42.     vie_network_->DeregisterSendTransport(channel_);  
  43.     webrtc::test::UdpTransport::Destroy(socket_transport_);  
  44. }  
  45.   
  46. void VideoChannelTransport::IncomingRTPPacket(  
  47.     const int8_t* incoming_rtp_packet,  
  48.     const int32_t packet_length,  
  49.     const char/*from_ip*/,  
  50.     const uint16_t /*from_port*/) {  
  51.     vie_network_->ReceivedRTPPacket(  
  52.         channel_, incoming_rtp_packet, packet_length, PacketTime());  
  53. }  
  54.   
  55. void VideoChannelTransport::IncomingRTCPPacket(  
  56.     const int8_t* incoming_rtcp_packet,  
  57.     const int32_t packet_length,  
  58.     const char/*from_ip*/,  
  59.     const uint16_t /*from_port*/) {  
  60.     vie_network_->ReceivedRTCPPacket(channel_, incoming_rtcp_packet,  
  61.         packet_length);  
  62. }  
  63.   
  64. int VideoChannelTransport::SetLocalReceiver(uint16_t rtp_port) {  
  65.     int return_value = socket_transport_->InitializeReceiveSockets(this,  
  66.         rtp_port);  
  67.     if (return_value == 0) {  
  68.         return socket_transport_->StartReceiving(500);  
  69.     }  
  70.     return return_value;  
  71. }  
  72.   
  73. int VideoChannelTransport::SetSendDestination(const char* ip_address,  
  74.     uint16_t rtp_port) {  
  75.     return socket_transport_->InitializeSendSockets(ip_address, rtp_port);  
  76. }  

继承自UdpTransportData类,主要重写了IncomingRTPPacket和IncomingRTCPPacket两个成员函数,分别调用了vie_network的ReceivedRTPPacket和ReceivedRTCPPacket方法,当需要将接收到的RTP和RTCP包传给VideoEngine时就应该使用这两个函数。

该示例程序最后效果如下,我这里是几个虚拟摄像头,然后会有两个窗口,一个是摄像头画面,一个是解码的画面。


源码地址在这里,这是一个可以脱离webrtc那个大项目而独立运行的工程。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐