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

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. 数组char a[4] 保存了一个数据,怎么转换为unsigned int呢 ?

    [待解决问题] 浏览: 701次 注意char并不表示字符的 a[0]=0; a[1]=0; a[2]=3; a[3]=0; 那么我要的unsigned int b应该等于: b= 0x0000030 ...

  2. Java中的==和equals的区别详解

    1.基础知识 (1)String x = "hello"; (2)String x = new String ("hello"); 第1种方式的工作机制是,首先 ...

  3. python处理excel总结

    工作中,大家经常会使用excel去处理数据以及展示,但是对于部分工作我们可以借助程序帮忙实现,达到高效解决问题的效果,比如将接口返回的json解析并保存结果到excel中,按一定规律处理excel中的 ...

  4. 树莓派开发板入门学习笔记2:[转]树莓派系统在VM中能做什么

    问"树莓派系统在VM中能做什么"不如问"树莓派能做什么":(参考:树莓派实验室) 普通难度的DIY 较高难度的DIY 用树莓派打造一个家庭影院 给树莓派安装摄像 ...

  5. leetcode-10-basic

    35. Search Insert Position Given a sorted array and a target value, return the index if the target i ...

  6. UVa - 1593 Unix ls(STL)

    给你一堆文件名,排序后按列优先的方式左对齐输出. 假设最长文件名长度是M,那么每一列都要有M+2字符,最后一列有M字符. inmanip真NB..orz #include <iostream&g ...

  7. Java技术——Java反射机制分析

    )生成动态代理. 2. Java反射API 反射API用来生成在当前Java虚拟机中的类.接口或者对象的信息. Class类:反射的核心类,可以获取类的属性,方法等内容信息. Field类:Java. ...

  8. Android开发——减小APK大小

    0. 前言 APK的大小对APP的加载速度,使用内存大小和消耗功率多少有一定影响.如何减小APK的大小对于Android开发者是一个永恒的话题. 查阅了很多相关资料,并将其做了删减以及总结.本文原创, ...

  9. <原创>在PE最后一节中插入补丁程序(附代码)

    完整文件  http://files.cnblogs.com/Files/Gotogoo/在PE最后一节中插入补丁程序.zip 在PE文件最后一节中插入补丁程序,是最简单也是最有效的一种,因为PE最后 ...

  10. mysql查询的语法

    单表查询语法 SELECT DISTINCT 字段1,字段2... FROM 表名 WHERE 条件 GROUP BY field HAVING 筛选 ORDER BY field LIMIT 限制条 ...