经过几天折腾终于把视频通信搞定了,中间走了很多弯路,其实很简单,原本一天就可以搞定的。
这篇主要记录中间遇到的坑,全是精华。
我主要是使用C++接口,调用Native API,没有使用Peerconnection功能,我所调用的接口,都在src/webrtc/video_engine目录下。
首先在上一篇编译完成以后,把库和头文件都加进来,头文件需要什么就加什么
我主要用到了以下接口:
#import "vie_base.h"
#import "vie_capture.h"
#import "vie_codec.h"
#import "vie_errors.h"
#import "vie_network.h"
#import "vie_render.h"
#import "vie_rtp_rtcp.h"
#import "vie_channel.h"
主要的代码部分如下:
- (void)startVideoEngine
{
_vieEngine = webrtc::VideoEngine::Create();
_vieBase = webrtc::ViEBase::GetInterface(_vieEngine);
_vieCapture = webrtc::ViECapture::GetInterface(_vieEngine);
_vieRender = webrtc::ViERender::GetInterface(_vieEngine);
_vieCodec = webrtc::ViECodec::GetInterface(_vieEngine);
_vieNetwork = webrtc::ViENetwork::GetInterface(_vieEngine);
_vieRtpRtcp = webrtc::ViERTP_RTCP::GetInterface(_vieEngine);
_vieEngine->SetTraceFilter(webrtc::kTraceAll);
_vieEngine->SetTraceCallback(new myTraceCallback);
if (_vieBase->Init() != 0) {
NSLog(@"vieBase Init failed \n");
return;
}
int channel;
if(_vieBase->CreateChannel(channel) != 0)
{
NSLog(@"vieBase CreateChannel failed\n");
return;
}
_vieChannel = _vieBase->GetChannel(channel);
_vieEncoder = _vieBase->GetEncoder(channel);
_vieChannel->SetRTCPMode(webrtc::RTCPMethod::kRtcpCompound);
_vieRtpRtcp->SetKeyFrameRequestMethod(channel, webrtc::kViEKeyFrameRequestPliRtcp);
// _vieRtpRtcp->SetTMMBRStatus(channel, true);
// _vieRtpRtcp->SetTransmissionSmoothingStatus(channel, true);
NSString *deviceID = nil;
int list_count = _vieCapture->NumberOfCaptureDevices();
if (list_count > 0) {
int list_number = 0;
if (list_count > 1) {
list_number = 1;
}
char device_name[KMaxDeviceNameLength];
char uniqueID[KMaxUniqueIDLength];
memset(uniqueID, 0, KMaxUniqueIDLength);
_vieCapture->GetCaptureDevice(list_number, device_name, KMaxDeviceNameLength, uniqueID, KMaxUniqueIDLength);
deviceID = [NSString stringWithFormat:@"%s", uniqueID];
}
int captureID;
_vieCapture->AllocateCaptureDevice([deviceID UTF8String], deviceID.length, captureID);
_vieCapture->ConnectCaptureDevice(captureID, channel);
webrtc::CaptureCapability captureCapability;
captureCapability.width = 352;
captureCapability.height = 288;
captureCapability.codecType = webrtc::kVideoCodecVP8;
captureCapability.maxFPS = DEFAULT_VIDEO_CODEC_MAX_FRAMERATE;
webrtc::VideoCodec videoCodec;
int numberOfCodecs = _vieCodec->NumberOfCodecs();
for (int i = 0; i < numberOfCodecs; i++) {
if (_vieCodec->GetCodec(i, videoCodec) != -1) {
if (videoCodec.codecType == webrtc::kVideoCodecVP8) {
break;
}
}
}
_vieCodec->SetSendCodec(channel, videoCodec);
_vieCodec->SetReceiveCodec(channel, videoCodec);
my_transportation *my_transport = new my_transportation(_vieNetwork, destinationIP);
_vieNetwork->RegisterSendTransport(channel, *my_transport);
_vieCapture->StartCapture(captureID, captureCapability);
//0,1,1,0,0 ->allright
//0,0,0,1,1 ->upsidedown
_vieRender->AddRenderer(captureID, [self localRenderView], 0, 1.0, 1.0, 0.0, 0.0);
_vieRender->StartRender(captureID);
_vieRender->AddRenderer(channel, [self remoteRenderView], 0, 0.0, 0.0, 1.0, 1.0);
_vieRender->StartRender(channel);
_vieBase->StartSend(channel);
_vieBase->StartReceive(channel);
}
这里面的第一个坑,也让我搞了大半天,就是如何显示的问题。
_vieRender->AddRenderer(captureID, [self localRenderView], 0, 1.0, 1.0, 0.0, 0.0);
这里面的几个参数如何设置的问题。特别是第二个参数,应该传入一个显示窗口,但是这个窗口不是简单的UIView,而是符合一些特定协议的UIView。
事实上这个View webrtc代码里已经写好了,就在src/webrtc/modules/video_render/ios/目录下,
VideoRenderIosView这个类。
只需要将这个头文件拿出来,直接就可以用来显示。一定要记得将view加到主View上。另外最好不要用ARC。
另外简单介绍一下,这里面的第一个参数,传入CaptureID表示要显示本地摄像头数据,如果传入ChannelID,表示显示通道解码数据。
后面的几个参数,最终会转换为OpenGLES的坐标系,在iOS设备上,就写为0,1,1,0,0是正常的。写为0,0,0,1,1则图像是上下颠倒的。
具体的含义我还没有搞懂。
第二个坑,是不能自环通话
webrtc视频不能在同一个设备上自收自发,否则会一直解码失败:
(generic_decoder.cc:164): Failed to decode frame with timestamp 1477586615, error code: -1
解码失败的原因是VP8只在刚开始发送一个关键帧,之后每隔3000帧才会发送一个关键帧,除非接收端主动请求。而如果自发自收,接收端即发送端,会出现SSRC冲突,此时发送方会重新生成一个SSRC,而接受方检测到SSRC变化会重置解码器,
解码器重置后,正常解码第一帧需要一个关键帧,而发送方已经发送过了不再发送,就会导致解码器没有关键帧而解码失败。
另外还有一些小问题,比如
1,只有先启动接收端,再启动发送端,才能正常解码,否则会解码失败。
这个跟第二个坑类似,也是关键帧丢失的问题,设置如下接口即可解决:
_vieRtpRtcp->SetKeyFrameRequestMethod(channel, webrtc::kViEKeyFrameRequestPliRtcp);
这个是设置接收端解码器出问题的时候,比如关键帧丢失或损坏,如何重新请求关键帧的方式。
2,外部传输接口中,怎么传入接收时间。
struct timeval tv;
gettimeofday(&tv, NULL);
result.ticks_ = 1000000LL * static_cast(tv.tv_sec) +
static_cast(tv.tv_usec);
这个说明地址不可达,我是使用两个iPod设备连入Mac电脑创建的共享网络,事实上这个共享网络的设备是不能直接用ip地址通信的。
解决方法就是重新连到同一个路由器上。