本系列目前共三篇文章,后续还会更新

WebRTC VideoEngine综合应用示例(一)——视频通话的基本流程

WebRTC VideoEngine综合应用示例(二)——集成OPENH264编解码器

WebRTC VideoEngine综合应用示例(三)——集成X264编码和ffmpeg解码

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实例,如下

webrtc::VideoEngine* ptrViE = NULL;
ptrViE = webrtc::VideoEngine::Create();
if (ptrViE == NULL)
{
printf("ERROR in VideoEngine::Create\n");
return -;
}

然后初始化VideoEngine并创建一个Channel

webrtc::ViEBase* ptrViEBase = webrtc::ViEBase::GetInterface(ptrViE);
if (ptrViEBase == NULL)
{
printf("ERROR in ViEBase::GetInterface\n");
return -;
} error = ptrViEBase->Init();//这里的Init其实是针对VideoEngine的初始化
if (error == -)
{
printf("ERROR in ViEBase::Init\n");
return -;
} webrtc::ViERTP_RTCP* ptrViERtpRtcp = webrtc::ViERTP_RTCP::GetInterface(ptrViE);
if (ptrViERtpRtcp == NULL)
{
printf("ERROR in ViERTP_RTCP::GetInterface\n");
return -;
} int videoChannel = -;
error = ptrViEBase->CreateChannel(videoChannel);
if (error == -)
{
printf("ERROR in ViEBase::CreateChannel\n");
return -;
}

列出可用的capture devices等待用户进行选择, 然后进行allocate和connect,最后start选中的capture device

webrtc::ViECapture* ptrViECapture = webrtc::ViECapture::GetInterface(ptrViE);
if (ptrViEBase == NULL)
{
printf("ERROR in ViECapture::GetInterface\n");
return -;
} const unsigned int KMaxDeviceNameLength = ;
const unsigned int KMaxUniqueIdLength = ;
char deviceName[KMaxDeviceNameLength];
memset(deviceName, , KMaxDeviceNameLength);
char uniqueId[KMaxUniqueIdLength];
memset(uniqueId, , KMaxUniqueIdLength); printf("Available capture devices:\n");
int captureIdx = ;
for (captureIdx = ;
captureIdx < ptrViECapture->NumberOfCaptureDevices();
captureIdx++)
{
memset(deviceName, , KMaxDeviceNameLength);
memset(uniqueId, , KMaxUniqueIdLength); error = ptrViECapture->GetCaptureDevice(captureIdx, deviceName, KMaxDeviceNameLength, uniqueId, KMaxUniqueIdLength);
if (error == -)
{
printf("ERROR in ViECapture::GetCaptureDevice\n");
return -;
}
printf("\t %d. %s\n", captureIdx + , deviceName);
}
printf("\nChoose capture device: "); if (scanf("%d", &captureIdx) != )
{
printf("Error in scanf()\n");
return -;
}
getchar();
captureIdx = captureIdx - ; // Compensate for idx start at 1. error = ptrViECapture->GetCaptureDevice(captureIdx, deviceName, KMaxDeviceNameLength, uniqueId, KMaxUniqueIdLength);
if (error == -)
{
printf("ERROR in ViECapture::GetCaptureDevice\n");
return -;
} int captureId = ;
error = ptrViECapture->AllocateCaptureDevice(uniqueId, KMaxUniqueIdLength, captureId);
if (error == -)
{
printf("ERROR in ViECapture::AllocateCaptureDevice\n");
return -;
} error = ptrViECapture->ConnectCaptureDevice(captureId, videoChannel);
if (error == -)
{
printf("ERROR in ViECapture::ConnectCaptureDevice\n");
return -;
} error = ptrViECapture->StartCapture(captureId);
if (error == -)
{
printf("ERROR in ViECapture::StartCapture\n");
return -;
}

设置RTP/RTCP所采用的模式

error = ptrViERtpRtcp->SetRTCPStatus(videoChannel, webrtc::kRtcpCompound_RFC4585);
if (error == -)
{
printf("ERROR in ViERTP_RTCP::SetRTCPStatus\n");
return -;
}

设置接收端解码器出问题的时候,比如关键帧丢失或损坏,如何重新请求关键帧的方式

error = ptrViERtpRtcp->SetKeyFrameRequestMethod(videoChannel, webrtc::kViEKeyFrameRequestPliRtcp);
if (error == -)
{
printf("ERROR in ViERTP_RTCP::SetKeyFrameRequestMethod\n");
return -;
}

设置是否为当前channel使用REMB(Receiver Estimated Max Bitrate)包,发送端可以用它表明正在编码当前channel

接收端用它来记录当前channel的估计码率

error = ptrViERtpRtcp->SetRembStatus(videoChannel, true, true);
if (error == -)
{
printf("ERROR in ViERTP_RTCP::SetTMMBRStatus\n");
return -;
}

设置rendering用于显示

webrtc::ViERender* ptrViERender = webrtc::ViERender::GetInterface(ptrViE);
if (ptrViERender == NULL)
{
printf("ERROR in ViERender::GetInterface\n");
return -;
}

显示本地摄像头数据,这里的window1和下面的window2都是显示窗口,更详细的内容后面再说

error = ptrViERender->AddRenderer(captureId, window1, , 0.0, 0.0, 1.0, 1.0);
if (error == -)
{
printf("ERROR in ViERender::AddRenderer\n");
return -;
} error = ptrViERender->StartRender(captureId);
if (error == -)
{
printf("ERROR in ViERender::StartRender\n");
return -;
}

显示接收端收到的解码数据

error = ptrViERender->AddRenderer(videoChannel, window2, , 0.0, 0.0, 1.0, 1.0);
if (error == -)
{
printf("ERROR in ViERender::AddRenderer\n");
return -;
} error = ptrViERender->StartRender(videoChannel);
if (error == -)
{
printf("ERROR in ViERender::StartRender\n");
return -;
}

设置编解码器

webrtc::ViECodec* ptrViECodec = webrtc::ViECodec::GetInterface(ptrViE);
if (ptrViECodec == NULL)
{
printf("ERROR in ViECodec::GetInterface\n");
return -;
} VideoCodec videoCodec;
int numOfVeCodecs = ptrViECodec->NumberOfCodecs();
for (int i = ; i<numOfVeCodecs; ++i)
{
if (ptrViECodec->GetCodec(i, videoCodec) != -)
{
if (videoCodec.codecType == kVideoCodecVP8)
{
break;
}
}
} videoCodec.targetBitrate = ;
videoCodec.minBitrate = ;
videoCodec.maxBitrate = ;
videoCodec.maxFramerate = ; error = ptrViECodec->SetSendCodec(videoChannel, videoCodec);
assert(error != -); error = ptrViECodec->SetReceiveCodec(videoChannel, videoCodec);
assert(error != -);

设置接收和发送地址,然后开始发送和接收

webrtc::ViENetwork* ptrViENetwork = webrtc::ViENetwork::GetInterface(ptrViE);
if (ptrViENetwork == NULL)
{
printf("ERROR in ViENetwork::GetInterface\n");
return -;
}
//VideoChannelTransport是由我们自己定义的类,后面将会详细介绍
VideoChannelTransport* video_channel_transport = NULL; video_channel_transport = new VideoChannelTransport(ptrViENetwork, videoChannel); const char* ipAddress = "127.0.0.1";
const unsigned short rtpPort = ;
std::cout << std::endl;
std::cout << "Using rtp port: " << rtpPort << std::endl;
std::cout << std::endl; error = video_channel_transport->SetLocalReceiver(rtpPort);
if (error == -)
{
printf("ERROR in SetLocalReceiver\n");
return -;
}
error = video_channel_transport->SetSendDestination(ipAddress, rtpPort);
if (error == -)
{
printf("ERROR in SetSendDestination\n");
return -;
} error = ptrViEBase->StartReceive(videoChannel);
if (error == -)
{
printf("ERROR in ViENetwork::StartReceive\n");
return -;
} error = ptrViEBase->StartSend(videoChannel);
if (error == -)
{
printf("ERROR in ViENetwork::StartSend\n");
return -;
}

设置按下回车键即停止通话

printf("\n call started\n\n");
printf("Press enter to stop...");
while ((getchar()) != '\n')
{ }

停止通话后的各种stop

error = ptrViEBase->StopReceive(videoChannel);
if (error == -)
{
printf("ERROR in ViEBase::StopReceive\n");
return -;
} error = ptrViEBase->StopSend(videoChannel);
if (error == -)
{
printf("ERROR in ViEBase::StopSend\n");
return -;
} error = ptrViERender->StopRender(captureId);
if (error == -)
{
printf("ERROR in ViERender::StopRender\n");
return -;
} error = ptrViERender->RemoveRenderer(captureId);
if (error == -)
{
printf("ERROR in ViERender::RemoveRenderer\n");
return -;
} error = ptrViERender->StopRender(videoChannel);
if (error == -)
{
printf("ERROR in ViERender::StopRender\n");
return -;
} error = ptrViERender->RemoveRenderer(videoChannel);
if (error == -)
{
printf("ERROR in ViERender::RemoveRenderer\n");
return -;
}
error = ptrViECapture->StopCapture(captureId);
if (error == -)
{
printf("ERROR in ViECapture::StopCapture\n");
return -;
} error = ptrViECapture->DisconnectCaptureDevice(videoChannel);
if (error == -)
{
printf("ERROR in ViECapture::DisconnectCaptureDevice\n");
return -;
} error = ptrViECapture->ReleaseCaptureDevice(captureId);
if (error == -)
{
printf("ERROR in ViECapture::ReleaseCaptureDevice\n");
return -;
} error = ptrViEBase->DeleteChannel(videoChannel);
if (error == -)
{
printf("ERROR in ViEBase::DeleteChannel\n");
return -;
} delete video_channel_transport; int remainingInterfaces = ;
remainingInterfaces = ptrViECodec->Release();
remainingInterfaces += ptrViECapture->Release();
remainingInterfaces += ptrViERtpRtcp->Release();
remainingInterfaces += ptrViERender->Release();
remainingInterfaces += ptrViENetwork->Release();
remainingInterfaces += ptrViEBase->Release();
if (remainingInterfaces > )
{
printf("ERROR: Could not release all interfaces\n");
return -;
} bool deleted = webrtc::VideoEngine::Delete(ptrViE);
if (deleted == false)
{
printf("ERROR in VideoEngine::Delete\n");
return -;
} return ;

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

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

ViEWindowCreator windowCreator;
ViEAutoTestWindowManagerInterface* windowManager = windowCreator.CreateTwoWindows();
VideoEngineSample(windowManager->GetWindow1(), windowManager->GetWindow2());

这里的VideoEngineSample就是我们在前面所写的包含全部流程的示例程序,它以两个窗口的指针作为参数。

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

class VideoChannelTransport : public webrtc::test::UdpTransportData
{
public:
VideoChannelTransport(ViENetwork* vie_network, int channel); virtual ~VideoChannelTransport(); // Start implementation of UdpTransportData.
virtual void IncomingRTPPacket(const int8_t* incoming_rtp_packet,
const int32_t packet_length,
const char* /*from_ip*/,
const uint16_t /*from_port*/) OVERRIDE; virtual void IncomingRTCPPacket(const int8_t* incoming_rtcp_packet,
const int32_t packet_length,
const char* /*from_ip*/,
const uint16_t /*from_port*/) OVERRIDE;
// End implementation of UdpTransportData. // Specifies the ports to receive RTP packets on.
int SetLocalReceiver(uint16_t rtp_port); // Specifies the destination port and IP address for a specified channel.
int SetSendDestination(const char* ip_address, uint16_t rtp_port); private:
int channel_;
ViENetwork* vie_network_;
webrtc::test::UdpTransport* socket_transport_;
}; VideoChannelTransport::VideoChannelTransport(ViENetwork* vie_network,
int channel)
: channel_(channel),
vie_network_(vie_network)
{
uint8_t socket_threads = ;
socket_transport_ = webrtc::test::UdpTransport::Create(channel, socket_threads);
int registered = vie_network_->RegisterSendTransport(channel,
*socket_transport_);
} VideoChannelTransport::~VideoChannelTransport()
{
vie_network_->DeregisterSendTransport(channel_);
webrtc::test::UdpTransport::Destroy(socket_transport_);
} void VideoChannelTransport::IncomingRTPPacket(
const int8_t* incoming_rtp_packet,
const int32_t packet_length,
const char* /*from_ip*/,
const uint16_t /*from_port*/)
{
vie_network_->ReceivedRTPPacket(
channel_, incoming_rtp_packet, packet_length, PacketTime());
} void VideoChannelTransport::IncomingRTCPPacket(
const int8_t* incoming_rtcp_packet,
const int32_t packet_length,
const char* /*from_ip*/,
const uint16_t /*from_port*/)
{
vie_network_->ReceivedRTCPPacket(channel_, incoming_rtcp_packet, packet_length);
} int VideoChannelTransport::SetLocalReceiver(uint16_t rtp_port)
{
int return_value = socket_transport_->InitializeReceiveSockets(this, rtp_port);
if (return_value == )
{
return socket_transport_->StartReceiving();
}
return return_value;
} int VideoChannelTransport::SetSendDestination(const char* ip_address, uint16_t rtp_port)
{
return socket_transport_->InitializeSendSockets(ip_address, rtp_port);
}

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

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

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

原文转自 http://blog.csdn.net/nonmarking/article/details/47375849#

WebRTC VideoEngine综合应用示例(一)——视频通话的基本流程(转)的更多相关文章

  1. WebRTC VoiceEngine综合应用示例(二)——音频通话的基本流程(转)

    下面将以实现一个音频通话功能为示例详细介绍VoiceEngine的使用,在文末将附上相应源码的下载地址.这里参考的是voiceengine\voe_cmd_test. 第一步是创建VoiceEngin ...

  2. WebRTC VoiceEngine综合应用示例(一)——基本结构分析(转)

    把自己这两天学习VoiceEngine的成果分享出来,供大家参考,有什么问题也欢迎大家指出,一起学习一起进步. 本文将对VoiceEngine的基本结构做一个分析,分析的方法是自底向上的:看一个音频编 ...

  3. WebRTC VideoEngine超详细教程(三)——集成X264编码和ffmpeg解码

    转自:http://blog.csdn.net/nonmarking/article/details/47958395 本系列目前共三篇文章,后续还会更新 WebRTC VideoEngine超详细教 ...

  4. 全互联结构DVPN综合配置示例

    以下内容摘自正在全面热销的最新网络设备图书“豪华四件套”之一<H3C路由器配置与管理完全手册>(第二版)(其余三本分别是:<Cisco交换机配置与管理完全手册>(第二版).&l ...

  5. PIE SDK组件式开发综合运用示例

    1. 功能概述 关于PIE SDK的功能开发,在我们的博客上已经分门别类的进行了展示,点击PIESat博客就可以访问,为了初学者入门,本章节将对从PIE SDK组件式二次开发如何搭建界面.如何综合开发 ...

  6. Django笔记&教程 5-3 综合使用示例

    Django 自学笔记兼学习教程第5章第3节--综合使用示例 点击查看教程总目录 1 - 生成学号场景 场景描述: 教务管理系统中,学生注册账号,学生选择年级后,生成唯一学号. 细节分析: 学生学号由 ...

  7. zigbee学习:示例程序SampleApp中通讯流程

    zigbee学习:示例程序SampleApp中通讯流程 本文博客链接:http://blog.csdn.net/jdh99,作者:jdh,转载请注明. 参考链接: http://wjf88223.bl ...

  8. 结合WebSocket编写WebGL综合场景示例

    在WebGL场景中导入多个Babylon骨骼模型,在局域网用WebSocket实现多用户交互控制. 首先是场景截图: 上图在场景中导入一个Babylon骨骼模型,使用asdw.空格.鼠标控制加速度移动 ...

  9. 一文带你了解webrtc基本原理(动手实现1v1视频通话)

    webrtc (Web Real-Time Communications) 是一个实时通讯技术,也是实时音视频技术的标准和框架. 大白话讲,webrtc是一个集大成的实时音视频技术集,包含了各种客户端 ...

随机推荐

  1. 为什么要在函数内部声明 var that = this 呢

    看一个例子 $('#conten').click(function(){ //this是被点击的#conten var that =this; $('.conten').each(function() ...

  2. 【状压dp】cf906C. Party

    需要稍加分析结论:还有一些小细节 Arseny likes to organize parties and invite people to it. However, not only friends ...

  3. 基于Inception搭建MySQL SQL审核平台Yearing

    基于Inception搭建MySQL SQL审核平台Yearing Inception 1. Inceptionj简介 2. Inception安装 2.1 下载和编译 2.2 启动配置 Yearni ...

  4. Robot Framework user guide

    http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html

  5. Python中变量的作用域

    一.变量作用域的含义 变量的作用域说白了就是变量的值从哪里获取,或者说变量取值的地方 我们在写代码过程中会用到很多变量,这些变量会出现在各种代码块中,有的出现在函数块里,有的在函数块外,例如: def ...

  6. hdu-1338 game predictions(贪心题)

    Suppose there are M people, including you, playing a special card game. At the beginning, each playe ...

  7. linux学习-主机的细部权限规划:ACL 的使用

    传统的权限仅有三种身份 (owner, group, others) 搭配三种权限 (r,w,x) 而已,并没有办法单纯的针对某一个使用者或某一个群 组来设定特定的权限需求,此时就得要使用 ACL 这 ...

  8. c++ dll 创建

    建立一个C++的Win32DLL,这里要注意选择"Export symbols"导出符号.点击完成. 如下图所示:   由于项目的名称是"TestCPPDLL" ...

  9. acm之图论基础

    1.图的定义 图 是一个顶点集合V和一个顶点间关系的集合E组成,记G=(V,E) V:顶点的有限非空集合. E:顶点间关系的有限集合(边集). 存在一个结点v,可能含有多个前驱节点和后继结点. 1顶点 ...

  10. Hadoop全分布式模式安装

    一.准备 1.准备至少三台linux服务器,并安装JDK 关闭防火墙如下 systemctl stop firewalld.service systemctl disable firewalld.se ...