webrtc (3) 使用webrtc Native API实现视频通话

经过几天折腾终于把视频通信搞定了,中间走了很多弯路,其实很简单,原本一天就可以搞定的。


这篇主要记录中间遇到的坑,全是精华。


我主要是使用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<int64_t>(tv.tv_sec) +

      static_cast<int64_t>(tv.tv_usec);

3,外部传输中,使用socket发送数据一直失败,errno提示EHOSTDOWN

这个说明地址不可达,我是使用两个iPod设备连入Mac电脑创建的共享网络,事实上这个共享网络的设备是不能直接用ip地址通信的。

解决方法就是重新连到同一个路由器上。



猜你喜欢

转载自blog.csdn.net/wangruihit/article/details/45723531