实时音视频

实时音视频

实现类似微信视频聊天的功能

WebRTC赵炳东 发表了文章 • 0 个评论 • 177 次浏览 • 2021-04-15 15:15 • 来自相关话题

前言今年因为疫情的影响,很多线下的项目或者活动都放在了线上,所以集成音视频等相关的话题一直比较火热。 作为一个小码农对微信音视频通话的功能还是比较好奇的,所以空闲的时候就想着自己能不能也实现一个,给朋友一个链接地址然后就可以愉快的视频通话了(小小的满足一下虚荣... ...查看全部

前言

今年因为疫情的影响,很多线下的项目或者活动都放在了线上,所以集成音视频等相关的话题一直比较火热。 作为一个小码农对微信音视频通话的功能还是比较好奇的,所以空闲的时候就想着自己能不能也实现一个,给朋友一个链接地址然后就可以愉快的视频通话了(小小的满足一下虚荣心)

微信截图_20210415151315.png

对于集成音视频纯小白来说,前期准备工作的确不好整,因为对这块没什么概念,对后面需要做什么列不出一个清晰的步骤出来(蓝廋香菇~~~),幸运的是,在网上找相关知识时意外发现了一家做音视频 SDK 的(融云https://www.rongcloud.cn/

为什么推荐他们家呢,主要是他们的开发文档比较适用于像我这样的小白,跟着他们的快速文档无脑实现音视频通话。点这个地址,开启你的音视频之旅吧:https://docs.rongcloud.cn/v4/views/rtc/call/noui/quick/web.html

特别意外的是融云还提供了 Web 端在线集成体验示例,体验完后还能拿到 demo ,附上跳转连接:https://docs.rongcloud.cn/v4/views/rtc/call/noui/demo/web.html

音视频通话介绍 音视频通话 SDK 含呼叫流程,可以实现一对一呼叫、多人呼叫等音视频通话场景,音视频通话 SDK 区别于音视频会议 SDK,音视频通话(呼叫)SDK 不需要双方约定房间号,呼叫流程自动处理房间号,适用于类似微信音视频通话等必须含有呼叫流程的应用场景,由两部分组成 信令通知 和 音视频传输

信令通知:基于 IMLib SDK,例如 A 给 B 发起通话,A 优先会通过 IM 给 B 发送一个通话请求,B 同意后再进入音视频房间进行音视频通信 音视频传输:基于 RongRTCLib SDK,例如 A 给 B 发起通话,信令传输完成后,会适用融云提供的音视频能力进行通信

但是在集成的时候,有几点是需要吐槽的:

1、我给其他人发起通话的时候,直接就触发挂断了返回 "SummaryMessage" 类型消息,咋又看不懂在文档找了半天都没发现 SummaryMessage 的详细描述,只知道是挂断电话返回的消息,最后提交了工单才了解挂断的原因。

Web 端挂断原因 HungupMessage 消息中 reason 字段及 SummaryMessage 消息中 status 字段都为挂断原因

状态码说明状态码说明
1己方取消已发出的通话请求11对方取消已发出的通话请求
2己方拒绝收到的通话请求12对方拒绝收到的通话请求
3己方挂断13对方挂断
4己方忙碌14对方忙碌
5己方未接听15对方未接听
6己方不支持当前引擎16对方不支持当前引擎
7己方网络错误17对方网络错误
8其他设备已处理18CallLib 不可用

原来是对方忙碌导致的接听失败!!!

2、前段时间还能正常通话,升级完 chrome 后就报错了(一脸懵逼 ),报错信息如下: Failed to execute 'setLocalDescription' on 'RTCPeerConnection': Failed to parse SessionDescription. m=video 9 UDP/TLS/RTP/SAVPF 98 99 96 97 100 101 127 Invalid value:

提供单询问融云的技术人员,才知道是由于 chrome 86 升级了 webRTC 的规范,SDK 在 3.2.6 版本做了相关适配。如果 SDK 使用的不是 3.2.6 版本。需要升级至 3.2.6 版本


超大规模会议技术优化策略 轻松实现 500 人线上流畅沟通

WebRTC梅川酷子 发表了文章 • 0 个评论 • 206 次浏览 • 2021-03-01 16:44 • 来自相关话题

受疫情影响,许多公司已经形成线上办公习惯,尤其是在线音视频会议,已经成为一种常态。对于一些大型企业和组织机构来说,分支机构遍布全国各地,员工异地参会人数众多,大规模音视频会议成为刚需。而当前音视频会议主流产品中,单个会议最多支持 500 人入会进行互动。但是 ... ...查看全部
受疫情影响,许多公司已经形成线上办公习惯,尤其是在线音视频会议,已经成为一种常态。对于一些大型企业和组织机构来说,分支机构遍布全国各地,员工异地参会人数众多,大规模音视频会议成为刚需。而当前音视频会议主流产品中,单个会议最多支持 500 人入会进行互动。


但是 500 人同时线上开会,对于资源消耗比较高。而传统的 WebRTC 架构并不擅长超过 200 人以上的会议场景。在面对超大规模会议室、聊天室、直播等各种复杂场景时,对流进行按需合流,可以降低带宽占用和设备压力;对流进行有选择的订阅分发,有助于扩展各种组合场景。针对 App 具体的应用场景,可以配合订阅分发模式,组合使用 SFU 和 MCU 架构。下来我们将详细分析一下大规模会议的资源优化策略。

1.超大规模会议架构对比


WebRTC 多对多网络架构有 P2P、MCU、SFU 三种。各种网络拓扑的优缺点如下:

1.png

SFU 方式灵活,只要降低带宽就可以实现大规模会议的要求。


2.超大规模会议中存在的挑战


在超过 20 人会议场景下,SFU 及 WebRTC 兼容场景仍然无法很好的解决。如果直接选择参会人之间进行音视频互动,音视频数据完全转发对服务器资源的要求是巨大的,如果会议中有大量人员同时接入,服务端上行流量和下行流量陡增,会对服务器造成巨大压力。


这里我们来对比一下 20 人与 200 人同时参加音视频会议时,对服务端造成压力的差距:
20人
各端流量:
20*(1Mbps+32Kbps)=20.64Mbps
服务端上行流量:
20*(1Mbps+32Kbps)=20.64Mbps
服务端下行流量:
20*(20-1)*(1Mbps+32Kbps)=392.16Mbps
200人
各端流量:
200*(1Mbps+32Kbps)=206.4Mbps
服务端上行流量:
200*(1Mbps+32Kbps)=206.4Mbps
服务端下行流量:
200*(200-1)*(1Mbps+32Kbps)=41.07Gbps

从对比结果中可以看出,服务端下行流量直接上升了一个量级。如果采用视频按需订阅,音频选择出音量最大的几路可以大大降低下行流量。比如每个客户端订阅 4 路视频,服务器只需下发 4 路音量最大的音频,服务端下行流量只需要 200*4*(1Mbps+32Kbps)=800+25.6=825.6Mbps,可以极大缓解服务器压力。
 
若要解决上面的问题,建议通过按需订阅与转发、音频流量两个方面来制定策略,在保证效果的前提下,降低服务端的压力。

3.按需订阅与转发以及音频流量优化策略


3.1 按需订阅与转发


按需订阅与转发的方式有:
支持单独订阅某个人的某路视频或某路音频。
接收端仅订阅正在说话的人的视频,音频全部订阅。
融云 SDK 支持发送端视频编码支持大小流。接收端按需订阅大流或小流。大流的清晰度高,码率高;小流的清晰度低,码率低。这样当接收端想观看清晰视频的时候订阅大流;对清晰度要求不高的时候订阅小流。另外,弱网下自动切换大小流,可以保证视频的流畅性。

3.2 音频流量优化策略

针对音频全部订阅有以下几种优化音频流量的方法。

3.2.1 发送端静音时不发送数据
WebRTC 的音频 codec 如果采用 Opus,可以开启 Opus 的 DTX(Discontinuous Transmission)。SDP 对应的设置为 usedtx=1。但测试中发现流量下降不如预期,因为用户的使用环境多少有点背景音。背景音量很容易超出静音阈值。像 Android/iOS 这种定制开发端可以手动调整静音阈值,而 PC 的 Web 端因为是浏览器,则无法调整静音阈值。

3.2.2 调整音频码率
通过设置客户端上音频码率,降低客户端上行的音频码率。当音频路数跟多的时候,限定每一路的音频码率后,总的音频码率会减少很多。SDP 设置方式 b=AS:码率。下面是摘自 RFC3556 的原文:

The Session Description Protocol includes an optional bandwidth
   attribute with the following syntax:

      b=<modifier>:<bandwidth-value>

   where <modifier> is a single alphanumeric word giving the meaning of
   the bandwidth figure, and where the default units for <bandwidth-
   value> are kilobits per second.  This attribute specifies the
   proposed bandwidth to be used by the session or media.

   A typical use is with the modifier "AS" (for Application Specific
   Maximum) which may be used to specify the total bandwidth for a
   single media stream from one site (source).
3.2.3 服务器下发音量 Top N 路

客户端收到音频流,在音频解码后,默认一般仅混流播放音量最大的 3(WebRTC 中的 kMaximumAmountOfMixedAudioSources 值)路声音。所以避免不必要的音频包的转发可以减少服务流量的。步骤如下:
发送端通过 Audio Level 标识音频能量。
音频包进入 SFU 转发队列,先进入计算队列,定期弹出 Top N 的音频包。
只有有效音频包,会进入到下行分发队列。
 
下面介绍音频如何转发音量最大几路的方法实践。

4. 音频 Top N 选择


4.1 客户端处理

客户端会计算出音量大小,并把值记录在 RTP 包中。所以客户端需要开启 audio-level 的 RTP 扩展, 如下: a=extmap:1urn:ietf:params:rtp-hdrext:ssrc-audio-level 开启这个 RTP 扩展后,WebRTC 客户端机会计算 audio 包的音量大小。这个音量大小计算方法 RFC6464 有明确定义。WebRTC 中的计算方法为 modules/audio_processing/rms_level.cc 的 ComputeRms 方法:

// Calculates the normalized RMS value from a mean square value. The input
// should be the sum of squared samples divided by the number of samples. The
// value will be normalized to full range before computing the RMS, wich is
// returned as a negated dBfs. That is, 0 is full amplitude while 127 is very
// faint.
int ComputeRms(float mean_square) {
  if (mean_square <= kMinLevel * kMaxSquaredLevel) {
    // Very faint; simply return the minimum value.
    return RmsLevel::kMinLevelDb;
  }
  // Normalize by the max level.
  const float mean_square_norm = mean_square / kMaxSquaredLevel;
  RTC_DCHECK_GT(mean_square_norm, kMinLevel);
  // 20log_10(x^0.5) = 10log_10(x)
  const float rms = 10.f * log10(mean_square_norm);
  RTC_DCHECK_LE(rms, 0.f);
  RTC_DCHECK_GT(rms, -RmsLevel::kMinLevelDb);
  // Return the negated value.
  return static_cast<int>(-rms + 0.5f);
}
客户端告诉服务器音频包的音量大小。服务器收到音频包后不用做解码,就能知道从客户端上来的音频包的音量值,为后面的服务器音频包下发策略奠定了基础。


4.2 服务器处理

下面用 Publisher 表示发布者的音频流,Subscriber 表示订阅者的音频流。RtpAudioPacket 表示一个音频包。RtpAudioPacket 里有个 mute 属性,标记这个音频包时是否静音。

在没有音频根据音量大小转发的逻辑前,Publisher 和 Subscriber 的处理关系如下。


2.png

Subscriber1、Subscriber2、Subscriber3 订阅 Publisher1、Publisher2、Publisher3。Publisher 发上来的音频包都会转发给各自的订阅者。


音频根据音量大小转发的逻辑如下:
AudioLevelHandler 表示每个 Publisher 的音频处理单元。AudioLevelHandler 里有两个音频包缓冲队列,计算队列 calculate_queue 和发送队列 send_queue。Publisher 的音频包先进入计算队列 calculate_queue 中。有个定时计算任务 AudioLevelCalculator。AudioLevelCalculator 会每隔一个音频打包时间 ptime 进行一次对所有 Publisher 的计算队列里音频包的 audio_level 均值(因为均值表示这个 Publisher 收到的若干个音频包的音量)做排序计算,选出音量值最大的几路。这几路的音频包 RtpAudioPacket 的 mute 被标记为 false,而其他音频包标记为 true。
排序后,这些音频包会从计算队列里移入到发送队列 send_queue 中。
之后音频包从 send_queue 出队,转发给 Subscriber。Subscriber 中的 MuteHandler 有以下两个作用:
a. 根据 RtpAudioPacket 的 mute 属性,mute 为 true 时,这个音频包直接被吞掉,false 表示转发给订阅者。
b. 因为下发给订阅者的音频包 RTP 序号 SeqNum 不是连续的,需要做连续化处理。

下面图中 Subscriber1、Subscriber2、Subscriber3 订阅 Publisher1、Publisher2、Publisher3。假设 Publisher1 收到的当前音量最大,最终只有它的音频包会转发给 Subscriber1、Subscriber2、Subscriber3。

3.png

4.3 级联的考虑


比如下面的图中,Subscriber4 通过级联服务器连接到当前 MediaServer 上。Publisher1、Publisher2、Publisher3 的音频包都会直接转发级联服务器。由级联服务器负责计算 Top N 音频包的计算下发给 Subscriber4。

5.png

下面是这部逻辑的伪代码:
void Publisher::Process(RtpAudioPacket packet, AudioLevelHandler handler) {
    handler.calculate_queue.enqueue(packet)

    RtpAudioPacket packetSend = handler.send_queue.dequeue();
    for (对当前Publisher的所有Subscriber subscriber) {
        if (subscriber是级联服务器) {
            转发packet
        } else {
            转发packetSend
        }
    }
}
4.4 音频下发策略优化


现实中人的说话是有停顿的。比如停顿前后人声比较大,如果简单的排序下发音频包,客户端会收到连续的非静音包。经测试,这样的体验并不理想,因此需要加入平滑处理。这里 history 为过去若干次的音频是否进入 Top N。音频包是最大的几路中的,加入 history 队列尾部加入 true,转发表示此次声音大而发。否则,加入 history 队列尾部加入 false。因为本次静音,还需判断过去的静音情况,若 history 中有 true 值,转发可表示过去一小段说过话,所以需要转发。若 history 中全为 false, 不转发则表示本次声音不大,过去一小段声音也不大,所以不转。

4.5 其他相关策略

当会议中的人数相对比较的少的时候,音频包为上面所述的正常转发。而当多个 Publisher 的订阅人数超过某个阈值(比如 50),此时 MediaServer 发的音频码率很大,对应客户端也要收相应的音频流量。这时可以走超大会议音频最大几路转发逻辑。而当会议中多个 Publisher 的订阅人数下降到阈值之下,再回归正常的转发逻辑。
经过选取最大几路流的下发方式,音频流量已经大大降低了。而在此基础上实际设置的选取路数做少许冗余,可以多发一些有音量的音频包,提高接收方体验。
当参会者增加时,相应的 MediaServer 也需要动态调度。通过把参会者音视频流打到多个 MediaServer 上,通过级联的方式解决问题,保证每台 MediaServer 服务器上的 CPU、内存、带宽的正常。

5. 总结


以上是基于超大规模会议技术优化进行的策略方面的探索。其主要思想是视频按需订阅,音频降低不必要的流量。其中涉及客户端音量值的上传、服务器端音量选择、级联、优化体验、减少音频流量等多个方面。研发过程中,超大会议需要多测试,才能暴露其中的问题,从而提高最终的会议体验。


iOS 基于实时音视频 SDK 实现屏幕共享功能——2

WebRTCadmin 发表了文章 • 0 个评论 • 800 次浏览 • 2020-12-03 18:26 • 来自相关话题

iOS 基于实时音视频 SDK 实现屏幕共享功能——1iOS 基于实时音视频 SDK 实现屏幕共享功能——2iOS 基于实时音视频 SDK 实现屏幕共享功能——3iOS 基于实时音视频 SDK 实现屏幕共享功能——4这里的核心思想是拿到屏幕共享的数据之后,先进... ...查看全部

微信截图_20201203182500.png


iOS 基于实时音视频 SDK 实现屏幕共享功能——1

iOS 基于实时音视频 SDK 实现屏幕共享功能——2

iOS 基于实时音视频 SDK 实现屏幕共享功能——3

iOS 基于实时音视频 SDK 实现屏幕共享功能——4



这里的核心思想是拿到屏幕共享的数据之后,先进行压缩,当压缩完成后会通过回调上报给当前类。既而通过 clientSend 方法,发给主 App。发给主 App 的数据中自定义了一个头部,头部添加了一个前缀和一个每次发送字节的长度,当接收端收到数据包后解析即可。

- (void)clientSend:(NSData *)data {
    //data length
    NSUInteger dataLength = data.length;
    // data header struct
    DataHeader dataH;
    memset((void *)&dataH, 0, sizeof(dataH));
    // pre
    PreHeader preH;
    memset((void *)&preH, 0, sizeof(preH));
    preH.pre[0] = '&';
    preH.dataLength = dataLength;
    dataH.preH = preH;
    // buffer
    int headerlength = sizeof(dataH);
    int totalLength = dataLength + headerlength;
    // srcbuffer
    Byte *src = (Byte *)[data bytes];
    // send buffer
    char *buffer = (char *)malloc(totalLength * sizeof(char));
    memcpy(buffer, &dataH, headerlength);
    memcpy(buffer + headerlength, src, dataLength);
    // to send
    [self sendBytes:buffer length:totalLength];
    free(buffer);
}

1.4 接收屏幕共享数据

//
//  RongRTCServerSocket.m
//  SealRTC
//
//  Created by Sun on 2020/5/7.
//  Copyright © 2020 RongCloud. All rights reserved.
//
#import "RongRTCServerSocket.h"
#import <arpa/inet.h>
#import <netdb.h>
#import <sys/types.h>
#import <sys/socket.h>
#import <ifaddrs.h>
#import <UIKit/UIKit.h>
#import "RongRTCThread.h"
#import "RongRTCSocketHeader.h"
#import "RongRTCVideoDecoder.h"
@interface RongRTCServerSocket() <RongRTCCodecProtocol>
{
    pthread_mutex_t lock;
    int _frameTime;
    CMTime _lastPresentationTime;
    Float64 _currentMediaTime;
    Float64 _currentVideoTime;
    dispatch_queue_t _frameQueue;
}
@property (nonatomic, assign) int acceptSocket;
/**
 data length
 */
@property (nonatomic, assign) NSUInteger dataLength;
/**
 timeData
 */
@property (nonatomic, strong) NSData *timeData;
/**
 decoder queue
 */
@property (nonatomic, strong) dispatch_queue_t decoderQueue;
/**
 decoder
 */
@property (nonatomic, strong) RongRTCVideoDecoder *decoder;
@end
@implementation RongRTCServerSocket
- (BOOL)createServerSocket {
    if ([self createSocket] == -1) {
        return NO;
    }
    [self setReceiveBuffer];
    [self setReceiveTimeout];
    BOOL isB = [self bind];
    BOOL isL = [self listen];
    if (isB && isL) {
        _decoderQueue = dispatch_queue_create("cn.rongcloud.decoderQueue", NULL);
        _frameTime = 0;
        [self createDecoder];
        [self receive];
        return YES;
    } else {
        return NO;
    }
}
- (void)createDecoder {
    self.decoder = [[RongRTCVideoDecoder alloc] init];
    self.decoder.delegate = self;
    RongRTCVideoEncoderSettings *settings = [[RongRTCVideoEncoderSettings alloc] init];
    settings.width = 720;
    settings.height = 1280;
    settings.startBitrate = 300;
    settings.maxFramerate = 30;
    settings.minBitrate = 1000;
    [self.decoder configWithSettings:settings onQueue:_decoderQueue];
}
- (void)receiveData {
    struct sockaddr_in rest;
    socklen_t rest_size = sizeof(struct sockaddr_in);
    self.acceptSocket = accept(self.socket, (struct sockaddr *) &rest, &rest_size);
    while (self.acceptSocket != -1) {
        DataHeader dataH;
        memset(&dataH, 0, sizeof(dataH));
        if (![self receiveData:(char *)&dataH length:sizeof(dataH)]) {
            continue;
        }
        PreHeader preH = dataH.preH;
        char pre = preH.pre[0];
        if (pre == '&') {
            // rongcloud socket
            NSUInteger dataLenght = preH.dataLength;
            char *buff = (char *)malloc(sizeof(char) * dataLenght);
            if ([self receiveData:(char *)buff length:dataLenght]) {
                NSData *data = [NSData dataWithBytes:buff length:dataLenght];
                [self.decoder decode:data];
                free(buff);
            }
        } else {
            NSLog(@"pre is not &");
            return;
        }
    }
}
- (BOOL)receiveData:(char *)data length:(NSUInteger)length {
    LOCK(lock);
    int receiveLength = 0;
    while (receiveLength < length) {
        ssize_t res = recv(self.acceptSocket, data, length - receiveLength, 0);
        if (res == -1 || res == 0) {
            UNLOCK(lock);
            NSLog(@"receive data error");
            break;
        }
        receiveLength += res;
        data += res;
    }
    UNLOCK(lock);
    return YES;
}
- (void)didGetDecodeBuffer:(CVPixelBufferRef)pixelBuffer {
    _frameTime += 1000;
    CMTime pts = CMTimeMake(_frameTime, 1000);
    CMSampleBufferRef sampleBuffer = [RongRTCBufferUtil sampleBufferFromPixbuffer:pixelBuffer time:pts];
    // Check to see if there is a problem with the decoded data. If the image appears, you are right.
    UIImage *image = [RongRTCBufferUtil imageFromBuffer:sampleBuffer];
    [self.delegate didProcessSampleBuffer:sampleBuffer];
    CFRelease(sampleBuffer);
}
- (void)close {
    int res = close(self.acceptSocket);
    self.acceptSocket = -1;
    NSLog(@"shut down server: %d", res);
    [super close];
}
- (void)dealloc {
    NSLog(@"dealoc server socket");
}
@end

主 App 通过 Socket 会持续收到数据包,再将数据包进行解码,将解码后的数据通过代理 didGetDecodeBuffer 代理方法回调给 App 层。App 层就可以通过融云 RongRTCLib 的发送自定义流方法将视频数据发送到对端。



iOS 基于实时音视频 SDK 实现屏幕共享功能——1

WebRTCadmin 发表了文章 • 0 个评论 • 834 次浏览 • 2020-12-03 18:21 • 来自相关话题

iOS 基于实时音视频 SDK 实现屏幕共享功能——1iOS 基于实时音视频 SDK 实现屏幕共享功能——2iOS 基于实时音视频 SDK 实现屏幕共享功能——3iOS 基于实时音视频 SDK 实现屏幕共享功能——4Replaykit 介绍在之前的 iOS 版... ...查看全部

微信截图_20201203181458.png

iOS 基于实时音视频 SDK 实现屏幕共享功能——1

iOS 基于实时音视频 SDK 实现屏幕共享功能——2

iOS 基于实时音视频 SDK 实现屏幕共享功能——3

iOS 基于实时音视频 SDK 实现屏幕共享功能——4

Replaykit 介绍

在之前的 iOS 版本中,iOS 开发者只能拿到编码后的数据,拿不到原始的 PCM 和 YUV,到 iOS 10 之后,开发者可以拿到原始数据,但是只能录制 App 内的内容,如果切到后台,将停止录制,直到 iOS 11,苹果对屏幕共享进行了升级并开放了权限,既可以拿到原始数据,又可以录制整个系统,以下我们重点来说 iOS 11 之后的屏幕共享功能。

系统屏幕共享

- (void)initMode_1 {
    self.systemBroadcastPickerView = [[RPSystemBroadcastPickerView alloc] initWithFrame:CGRectMake(0, 64, ScreenWidth, 80)];
    self.systemBroadcastPickerView.preferredExtension = @"cn.rongcloud.replaytest.Recoder";
    self.systemBroadcastPickerView.backgroundColor = [UIColor colorWithRed:53.0/255.0 green:129.0/255.0 blue:242.0/255.0 alpha:1.0];
    self.systemBroadcastPickerView.showsMicrophoneButton = NO;
    [self.view addSubview:self.systemBroadcastPickerView];
}

在 iOS 11 创建一个 Extension 之后,调用上面的代码就可以开启屏幕共享了,然后系统会为我们生成一个 SampleHandler 的类,在这个方法中,苹果会根据 RPSampleBufferType 上报不同类型的数据。

- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType

那怎么通过融云的 RongRTCLib 将屏幕共享数据发送出去呢?

1. 基于 Socket 的逼格玩法

1.1. Replaykit 框架启动和创建 Socket

//
//  ViewController.m
//  Socket_Replykit
//
//  Created by Sun on 2020/5/19.
//  Copyright © 2020 RongCloud. All rights reserved.
//
#import "ViewController.h"
#import <ReplayKit/ReplayKit.h>
#import "RongRTCServerSocket.h"
@interface ViewController ()<RongRTCServerSocketProtocol>
@property (nonatomic, strong) RPSystemBroadcastPickerView *systemBroadcastPickerView;
/**
 server socket
 */
@property(nonatomic , strong)RongRTCServerSocket *serverSocket;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    // Do any additional setup after loading the view.
    [self.serverSocket createServerSocket];
    self.systemBroadcastPickerView = [[RPSystemBroadcastPickerView alloc] initWithFrame:CGRectMake(0, 64, [UIScreen mainScreen].bounds.size.width, 80)];
    self.systemBroadcastPickerView.preferredExtension = @"cn.rongcloud.sealrtc.RongRTCRP";
    self.systemBroadcastPickerView.backgroundColor = [UIColor colorWithRed:53.0/255.0 green:129.0/255.0 blue:242.0/255.0 alpha:1.0];
    self.systemBroadcastPickerView.showsMicrophoneButton = NO;
    [self.view addSubview:self.systemBroadcastPickerView];
}
- (RongRTCServerSocket *)serverSocket {
    if (!_serverSocket) {
        RongRTCServerSocket *socket = [[RongRTCServerSocket alloc] init];
        socket.delegate = self;
        _serverSocket = socket;
    }
    return _serverSocket;
}
- (void)didProcessSampleBuffer:(CMSampleBufferRef)sampleBuffer {
    // 这里拿到了最终的数据,比如最后可以使用融云的音视频SDK RTCLib 进行传输就可以了
}
@end

其中,包括了创建 Server Socket 的步骤,我们把主 App 当做 Server,然后屏幕共享 Extension 当做 Client ,通过 Socket 向我们的主 APP 发送数据。

在 Extension 里面,我们拿到 ReplayKit 框架上报的屏幕视频数据后:

//
//  SampleHandler.m
//  SocketReply
//
//  Created by Sun on 2020/5/19.
//  Copyright © 2020 RongCloud. All rights reserved.
//
#import "SampleHandler.h"
#import "RongRTCClientSocket.h"
@interface SampleHandler()
/**
 Client Socket
 */
@property (nonatomic, strong) RongRTCClientSocket *clientSocket;
@end
@implementation SampleHandler
- (void)broadcastStartedWithSetupInf(NSDictionary<NSString *,NSObject *> *)setupInfo {
    // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional.
    self.clientSocket = [[RongRTCClientSocket alloc] init];
    [self.clientSocket createCliectSocket];
}
- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
    switch (sampleBufferType) {
        case RPSampleBufferTypeVide
            // Handle video sample buffer
            [self sendData:sampleBuffer];
            break;
        case RPSampleBufferTypeAudioApp:
            // Handle audio sample buffer for app audio
            break;
        case RPSampleBufferTypeAudioMic:
            // Handle audio sample buffer for mic audio
            break;
        default:
            break;
    }
}
- (void)sendData:(CMSampleBufferRef)sampleBuffer {
    [self.clientSocket encodeBuffer:sampleBuffer];
}
@end

可见 ,这里我们创建了一个 Client Socket,然后拿到屏幕共享的视频 sampleBuffer 之后,通过 Socket 发给我们的主 App,这就是屏幕共享的流程。

1.2 Local Socket 的使用

//
//  RongRTCSocket.m
//  SealRTC
//
//  Created by Sun on 2020/5/7.
//  Copyright © 2020 RongCloud. All rights reserved.
//
#import "RongRTCSocket.h"
#import <arpa/inet.h>
#import <netdb.h>
#import <sys/types.h>
#import <sys/socket.h>
#import <ifaddrs.h>
#import "RongRTCThread.h"
@interface RongRTCSocket()
/**
 receive thread
 */
@property (nonatomic, strong) RongRTCThread *receiveThread;
@end
@implementation RongRTCSocket
- (int)createSocket {
    int socket = socket(AF_INET, SOCK_STREAM, 0);
    self.socket = socket;
    if (self.socket == -1) {
        close(self.socket);
        NSLog(@"socket error : %d", self.socket);
    }
    self.receiveThread = [[RongRTCThread alloc] init];
    [self.receiveThread run];
    return socket;
}
- (void)setSendBuffer {
    int optVal = 1024 * 1024 * 2;
    int optLen = sizeof(int);
    int res = setsockopt(self.socket, SOL_SOCKET, SO_SNDBUF, (char *)&optVal,optLen);
    NSLog(@"set send buffer:%d", res);
}
- (void)setReceiveBuffer {
    int optVal = 1024 * 1024 * 2;
    int optLen = sizeof(int);
    int res = setsockopt(self.socket, SOL_SOCKET, SO_RCVBUF, (char*)&optVal,optLen );
    NSLog(@"set send buffer:%d",res);
}
- (void)setSendingTimeout {
    struct timeval timeout = {10,0};
    int res = setsockopt(self.socket, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(int));
    NSLog(@"set send timeout:%d", res);
}
- (void)setReceiveTimeout {
    struct timeval timeout = {10, 0};
    int  res = setsockopt(self.socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(int));
    NSLog(@"set send timeout:%d", res);
}
- (BOOL)connect {
    NSString *serverHost = [self ip];
    struct hostent *server = gethostbyname([serverHost UTF8String]);
    if (server == NULL) {
        close(self.socket);
        NSLog(@"get host error");
        return NO;
    }
    struct in_addr *remoteAddr = (struct in_addr *)server->h_addr_list[0];
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr = *remoteAddr;
    addr.sin_port = htons(CONNECTPORT);
    int res = connect(self.socket, (struct sockaddr *) &addr, sizeof(addr));
    if (res == -1) {
        close(self.socket);
        NSLog(@"connect error");
        return NO;
    }
    NSLog(@"socket connect to server success");
    return YES;
}
- (BOOL)bind {
    struct sockaddr_in client;
    client.sin_family = AF_INET;
    NSString *ipStr = [self ip];
    if (ipStr.length <= 0) {
        return NO;
    }
    const char *ip = [ipStr cStringUsingEncoding:NSASCIIStringEncoding];
    client.sin_addr.s_addr = inet_addr(ip);
    client.sin_port = htons(CONNECTPORT);
    int bd = bind(self.socket, (struct sockaddr *) &client, sizeof(client));
    if (bd == -1) {
        close(self.socket);
        NSLog(@"bind error: %d", bd);
        return NO;
    }
    return YES;
}
- (BOOL)listen {
    int ls = listen(self.socket, 128);
    if (ls == -1) {
        close(self.socket);
        NSLog(@"listen error: %d", ls);
        return NO;
    }
     return YES;
}
- (void)receive {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self receiveData];
    });
}
- (NSString *)ip {
    NSString *ip = nil;
    struct ifaddrs *addrs = NULL;
    struct ifaddrs *tmpAddrs = NULL;
    BOOL res = getifaddrs(&addrs);
    if (res == 0) {
        tmpAddrs = addrs;
        while (tmpAddrs != NULL) {
            if (tmpAddrs->ifa_addr->sa_family == AF_INET) {
                // Check if interface is en0 which is the wifi connection on the iPhone
                NSLog(@"%@", [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)tmpAddrs->ifa_addr)->sin_addr)]);
                if ([[NSString stringWithUTF8String:tmpAddrs->ifa_name] isEqualToString:@"en0"]) {
                    // Get NSString from C String
                    ip = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)tmpAddrs->ifa_addr)->sin_addr)];
                }
            }
            tmpAddrs = tmpAddrs->ifa_next;
        }
    }
    // Free memory
    freeifaddrs(addrs);
    NSLog(@"%@",ip);
    return ip;
}
- (void)close {
    int res = close(self.socket);
    NSLog(@"shut down: %d", res);
}
- (void)receiveData {
}
- (void)dealloc {
    [self.receiveThread stop];
}

@end

首先创建了一个 Socket 的父类,然后用 Server Socket 和 Client Socket 分别继承类来实现链接、绑定等操作。可以看到有些数据可以设置,有些则不用,这里不是核心,核心是怎样收发数据。

1.3 发送屏幕共享数据

//
//  RongRTCClientSocket.m
//  SealRTC
//
//  Created by Sun on 2020/5/7.
//  Copyright © 2020 RongCloud. All rights reserved.
//
#import "RongRTCClientSocket.h"
#import <arpa/inet.h>
#import <netdb.h>
#import <sys/types.h>
#import <sys/socket.h>
#import <ifaddrs.h>
#import "RongRTCThread.h"
#import "RongRTCSocketHeader.h"
#import "RongRTCVideoEncoder.h"
@interface RongRTCClientSocket() <RongRTCCodecProtocol> {
    pthread_mutex_t lock;
}
/**
 video encoder
 */
@property (nonatomic, strong) RongRTCVideoEncoder *encoder;
/**
 encode queue
 */
@property (nonatomic, strong) dispatch_queue_t encodeQueue;
@end
@implementation RongRTCClientSocket
- (BOOL)createClientSocket {
    if ([self createSocket] == -1) {
        return NO;
    }
    BOOL isC = [self connect];
    [self setSendBuffer];
    [self setSendingTimeout];
    if (isC) {
        _encodeQueue = dispatch_queue_create("cn.rongcloud.encodequeue", NULL);
        [self createVideoEncoder];
        return YES;
    } else {
        return NO;
    }
}
- (void)createVideoEncoder {
    self.encoder = [[RongRTCVideoEncoder alloc] init];
    self.encoder.delegate = self;
    RongRTCVideoEncoderSettings *settings = [[RongRTCVideoEncoderSettings alloc] init];
    settings.width = 720;
    settings.height = 1280;
    settings.startBitrate = 300;
    settings.maxFramerate = 30;
    settings.minBitrate = 1000;
    [self.encoder configWithSettings:settings onQueue:_encodeQueue];
}
- (void)clientSend:(NSData *)data {
    //data length
    NSUInteger dataLength = data.length;
    // data header struct
    DataHeader dataH;
    memset((void *)&dataH, 0, sizeof(dataH));
    // pre
    PreHeader preH;
    memset((void *)&preH, 0, sizeof(preH));
    preH.pre[0] = '&';
    preH.dataLength = dataLength;
    dataH.preH = preH;
    // buffer
    int headerlength = sizeof(dataH);
    int totalLength = dataLength + headerlength;
    // srcbuffer
    Byte *src = (Byte *)[data bytes];
    // send buffer
    char *buffer = (char *)malloc(totalLength * sizeof(char));
    memcpy(buffer, &dataH, headerlength);
    memcpy(buffer + headerlength, src, dataLength);
    // tosend
    [self sendBytes:buffer length:totalLength];
    free(buffer);
}
- (void)encodeBuffer:(CMSampleBufferRef)sampleBuffer {
    [self.encoder encode:sampleBuffer];
}
- (void)sendBytes:(char *)bytes length:(int)length {
    LOCK(self->lock);
    int hasSendLength = 0;
    while (hasSendLength < length) {
        // connect socket success
        if (self.socket > 0) {
            // send
            int sendRes = send(self.socket, bytes, length - hasSendLength, 0);
            if (sendRes == -1 || sendRes == 0) {
                UNLOCK(self->lock);
                NSLog(@"send buffer error");
                [self close];
                break;
            }
            hasSendLength += sendRes;
            bytes += sendRes;
        } else {
            NSLog(@"client socket connect error");
            UNLOCK(self->lock);
        }
    }
    UNLOCK(self->lock); 
}
- (void)spsData:(NSData *)sps ppsData:(NSData *)pps {
    [self clientSend:sps];
    [self clientSend:pps];
}
- (void)naluData:(NSData *)naluData {
    [self clientSend:naluData];
}
- (void)deallo c{
    NSLog(@"dealoc cliect socket");
}
@end


30万+App都在用的服务商,有什么特别之处?

科技创新融云那些事 发表了文章 • 0 个评论 • 182 次浏览 • 2020-08-03 16:57 • 来自相关话题

两个月前,一款名为 Clubhouse 的 App 开始流行于美国 VC、名人圈层,并且一直热度不减。尽管它的用户只有几千人,但其估值却已超过 1 亿美元。Clubhouse 是一款语音社交软件,被誉为是“音频 Twitter”。打开 Clubhouse,选一... ...查看全部

两个月前,一款名为 Clubhouse 的 App 开始流行于美国 VC、名人圈层,并且一直热度不减。尽管它的用户只有几千人,但其估值却已超过 1 亿美元。

Clubhouse 是一款语音社交软件,被誉为是“音频 Twitter”。

讯飞1.webp.jpg

打开 Clubhouse,选一个房间进入,人们可以查看房间里都有哪些参与者,并可以听到他们的谈话,再决定是否继续听或直接加入群聊。

如果要说它最大的规则是什么,那就是:只用语音,没有文字

近年来,移动互联网飞速发展,语音功能已开始成为 App 的标配,各种属性的音频类 App 尤其是声音社交类 App 也在悄然壮大。

红杉中国发布的《创造未来——红杉 00 后泛娱乐消费研究报告》显示,社交性、潮流性和个性化是“00 后”用户最看重的三大产品特征。因此,比图片和文字有温度,比视频更含蓄的声音社交方式,开始在他们之中风靡。

而根据艾瑞咨询的数据,2019 年中国网络音频行业市场规模为 175.8 亿元,同比增长 55.1%,预计 2020 年中国网络音频行业市场规模达 272.4 亿元。越来越多的资本和资源看到了声音背后蕴藏的更多可能性。

讯飞2.png

随着 5G 网络的逐渐普及,网络速度变得更快,以音频、视频为主要交互方式的互联网产品或将大行其道。

今年年初的疫情期间,我们已经看到各种音视频软件在在线教育、在线办公中发挥着巨大的作用。但有一个不得不面对的事实是,它们都曾因短时期内使用人数过多而崩溃。

对于音视频类互联网产品来说,保证用户使用过程中不会出现卡顿、声音效果差等问题十分重要,这就需要技术上的强力支持与保证,在这一方面能够提供“实时音视频”技术的服务商做的相当专业。

目前,在我国从事“实时音视频”技术底层搭建的服务商主要分为两大阵营。其一,是只专注“实时音视频”技术的单一产业服务商;其二,是 IM 领域已经耕耘多年,现将 IM 即时通讯及实时音视频两大业务版块交融聚合的服务商。例如,已多年稳居 IM 市场占有率第一的融云。

讯飞3.webp.jpg

北京云中融信网络科技有限公司(简称融云),是一家安全、可靠的全球互联网通信云服务商,向开发者和企业提供即时通讯和实时音视频通信云服务。虽成立时间不长,但发展迅速,成立不久就获得了 1 亿元人民币 B 轮融资。

截止目前,已有中原地产、汽车之家、融创地产、丽兹行、寺库、哈啰出行、核桃编程、易车网、编程猫以及 Castbox、Opera 等超过 30 万款海内外 App 通过融云实现了全球化的互联网通信能力。

融云现已在科大讯飞旗下人工智能全产业链综合服务平台——讯飞 AI 服务市场入驻,来一起看看,它的哪些技术能让如此之多的 App “钟情”于它?

融云IM即时通讯

许多 85 后甚至 90 后的大学记忆中都有一款即时通讯工具,飞信。在那个短信 1 毛钱一条,流量 5 块钱只有 30M 的年代,飞信免流量、免费发短信的功能,很有吸引力。作为当初中国移动即时通讯的扛鼎之作,飞信注册用户最高时达到了 5 亿,高峰时拥有高达 9000 万的活跃用户,其技术水平自不必多说。

融云的技术团队源自飞信技术团队,在即时通讯领域有着丰富的经验和强大的核心技术实力。

融云即时通讯支持多聊天模式、多消息类型、自定义界面等功能,同时支持聊天记录漫游、消息回执与召回、消息内容全文搜索、消息内容审核等功能。用户可以根据自己的需求调用相关接口,大大节约在通信能力上的研发时间和成本。

讯飞7.png

基于融云私有通信协议,可以保障消息不丢不重不乱,支持无上限用户数的群组和聊天室互动,公有云平台 150 亿条日均消息量2218 亿条日消息量峰值。融云建立了全球多数据中心,服务覆盖全球所有国家及地区(共 233 个),实时监控全球网络,基于融云分布在全球的数据中心与节点建设,向客户提供链路接入方案,持续的海外链路优化让消息发送稳定可靠,畅通无阻。

讯飞8.webp.jpg

无论是私密社交、兴趣社交还是商业沟通、系统消息等场景,融云都可以轻松应对,它也是业内唯一承诺消息可靠性 100% 的厂商。

融云RTC实时音视频

为了适应移动互联网发展和市场需求,融云在即时通讯之外还发展了实时音视频业务。融云实时音视频主要包括音视频通话、低延迟直播、音视频会议、云端录制等功能。用户可以根据自己的需求调用相关接口,大大节约在通信能力上的研发时间。

讯飞9.png

融云实时音视频具备低延迟、低成本、高流畅、高品质、部署简单、扩展灵活等技术优势。在整体网络架构上,全球分布式架构的部署让扩容时间大幅缩短,能轻松应对海量流量的激增。弱网优化策略方面,增强了抗丢包及抗网络抖动能力,音频能对抗 80% 丢包,视频能对抗 40% 丢包,延时最低可达 66ms,以保证低成本输出高性能的实时音视频能力(可以提供最高 1080P 的高清画质和最高 48KHz 的音频采样率) 。经过海量客户业务验证,融云实时音视频业务在稳定性、连通性、并发/负载等方面服务可用性达到 99.9%

融云在疫情期间还免费开放了在线医疗、在线教育及协同办公场景的通信能力,并开发了 VR 看房等新的业务场景。

最新活动推荐:

 融云年中大促活动海报.png


芥末堆专访:线上教学交付能力背后,在线音视频通信技术成刚需

科技创新融云那些事 发表了文章 • 0 个评论 • 279 次浏览 • 2020-08-03 16:43 • 来自相关话题

2020 年过半,疫情加速在线教育发展已成事实。疫情防控常态化使得线下教育场景面临着长期挑战,线上教学交付能力俨然成为教培机构的标配。伴随着线下机构转型线上探索教育 OMO,线上教学背后的技术能力开始受到重视。相比线下场景天然的临场感和及时的互动性,线上化教学... ...查看全部

2020 年过半,疫情加速在线教育发展已成事实。疫情防控常态化使得线下教育场景面临着长期挑战,线上教学交付能力俨然成为教培机构的标配。

伴随着线下机构转型线上探索教育 OMO,线上教学背后的技术能力开始受到重视。相比线下场景天然的临场感和及时的互动性,线上化教学不是仅仅利用音视频工具就足够。线下场景中的信息传递是基于面对面的沟通,如何在线上场景“重现”甚至“超越”面对面沟通的信息传递方式是关键问题。

 芥末堆1_副本.jpg

无论教学形式或场景怎样变化,教育始终是效果为王。“全民网课”的时代,教学效果的保证离不开高质量的音视频信息传递、全方位的及时互动和多维度的数据分析。然而这些对于教培机构,尤其是以教学见长的线下机构,都并不熟悉。

全球通信云服务商融云 CPO 任杰表示:“在互联网时代,教育创业者应该聚焦自己的核心业务逻辑,而不是去关心通用型能力的实现。”

为此,融云专注于互联网云通信领域,为包括在线教育在内的互联网行业提供“IM 即时通讯+实时音视频+推送”的一体化解决方案,凭借技术积累实现安全、可靠、实时的即时通讯及音视频服务,满足各类教育场景的通用型和定制化的信息交互需求。

“IM 即时通讯+实时音视频+推送”,在线教育一体化解决方案

据任杰介绍,融云最初选择互联网云通信领域,就是看到互联网发展的趋势和通信需求的刚性。时至今日,伴随着移动互联网的发展,云通信已经成为标配能力。融云也从最初的“IM 即时通讯”业务,发展到“用一套 SDK,解决所有通信场景”,涵盖“IM 即时通讯+实时音视频+推送”的一体化解决方案。

任杰告诉芥末堆,融云整体的服务形式是以客户端 SDK 的方式体现,App 只要通过简单的几行代码就可以完成通信 SDK 集成,实现通信和社交能力。此外,融云还提供实时音视频相关功能和整体解决方案。

芥末堆2_副本.jpg 

去年 11 月 30 日,融云正式宣布已完成数亿元 C 轮融资

技术的发展背后是需求的演变。从图文时代到音视频时代,信息传播的媒介和方式都在发生着巨大变化。与之相应的是互联网企业对音视频通信技术的更高要求。任杰表示,如何打造一流的实时音视频服务,赋能开发者追赶音视频领域的技术红利,是融云始终关注的。

而作为互联网通信的主要应用场景之一,在线教育的内容以视频直播为主要形式。相比娱乐内容,教育内容的严肃性和互动性使得低延迟的高质量音视频传输成为刚需。任杰告诉芥末堆,以 PC 或平板为设备载体的在线教育对画质的要求,要远远高于移动端。

 芥末堆3_副本.jpg

如果将内容看作在线教育的产品核心,那么音视频质量可以看作在线教育的技术核心。相比其他内容行业,同样面对卡顿或延迟,在线教育用户的容忍度更低。任杰介绍,融云在今年 5 月份对融云实时音视频业务进行全面升级,针对开发者最关心的音视频通话质量,融云使用的是 WebRTC 技术,完全可以满足在线教育等场景中低延迟、强互动的需求。

据了解,在视频方面,融云目前能够提供最高 1080P 的分辨率,画面纤毫毕现,尤其适合特殊高清场景,如在线教育领域中的双师课堂场景的大屏直播。同时,融云还提供各种高中低分辨率供不同业务场景调用,实现画面和流量平衡,帧率最高支持 30FPS,可以匹配不同设备端的教学需求。

在音频方面,融云采用最高音频采样率 48KHz,可真实还原对端声音,高清音质。任杰介绍,融云目前可以提供高清音乐模式,针对乐器的高频音段和弱音音阶进行优化处理,高度还原音乐细节,带给用户更贴近线下场景的体验。在音频方面,融云完全可以满足素质教育领域的音乐培训的需求。

云通信能力之后,融云要延伸到更多在线教育场景

在沟通中,任杰始终强调融云在做的是底层技术能力的支持。但在介绍在线教育场景的应用时,芥末堆发现针对不同角色、不同班型、不同细分赛道的差异化需求,融云在技术上都有设计并满足。

提到融云通信云能力的特点,任杰总结到:性能稳定、全平台覆盖、全方位互动和全球部署。

 芥末堆4_副本.jpg

性能稳定是融云的核心优势。依托技术优势,融云可以为大型直播课程提供无用户上限和消息量限制的聊天室服务,亿级消息并发即时到达,互动稳定流畅、延迟低。任杰表示,在弱网优化策略上,融云的核心策略是能够迅速预估宽带,并采用降低码率来确保以声音与流畅优先的原则,保证用户最优体验。

在覆盖平台方面,融云实时音视频 SDK 可覆盖全平台,包括 iOS、Android、Web、Windows、macOS、Linux、Electron 等,并全面适配市场主流的各类终端设备,包括在智能手表、智能音箱、智能门禁等多种智能硬件设备中实现平台间通信,全面保障融云实时音视频在各类终端上的良好应用。

在全方位互动方面,任杰介绍,融云支持 1080P 高清视频,视频窗口放大缩小可切换不同分辨率;具备互动白板功能,支持上传常见办公文档,实时进行课堂演示;一对一、小班课、大班课、双师课等不同班型的实时直播、举手答疑和视频回看都能实现。他特意强调,为保障在线课程可 100% 回看,融云是采用两条线路双向录制的。

在全球部署方面,融云拥有覆盖全球的通信加速网络,全球多个数据中心,数千个加速点,触达全世界 233 个国家和地区,能够帮助教育企业实现全球范围内的高质量教学。

提到融云在在线教育领域的探索,任杰表示,教育的未来趋势一定是线上,但这离不开技术的发展和应用。融云后续将继续利用技术优势,持续在降低高质量音视频网络流量占用的方向上发力。

此外,将技术应用延伸到教育的各个场景也是融云的发力点之一。除了今年爆发的在线 K12 领域外,素质教育、在线考试等领域也亟待技术的重构。任杰以音乐教育为例,融云为管乐培训机构大雅乐盟做了针对性的音频通信改善,给出管乐培训的线上教育解决方案。而后续,钢琴、舞蹈等线上教学领域也是融云将会深化去探索的场景。

最新活动推荐:

融云年中大促活动海报.png

实时音视频选型 开发者应该避开哪些坑?

IM即时通讯融云那些事 发表了文章 • 0 个评论 • 220 次浏览 • 2020-07-24 15:19 • 来自相关话题

实时音视频技术的专业度和复杂度都很高,通过 PaaS 服务商来集成实时音视频,快速开发 App,是时下开发者的优先选择。所选 RTC 是否好用易用、契合所需场景,将直接影响项目开发进度和后期运维成本。开发者需要... ...查看全部

实时音视频技术的专业度和复杂度都很高,通过 PaaS 服务商来集成实时音视频,快速开发 App,是时下开发者的优先选择。所选 RTC 是否好用易用、契合所需场景,将直接影响项目开发进度和后期运维成本。

开发者需要了解实时音视频技术选型中要避开的坑点,以便提高开发集成效率。具体来说,以下四个方面要综合考虑。

一、实时音视频与 IM 能力不宜分散

几乎 100% 的实时音视频在线应用都有文字/语音消息、文件传输、图片显示等 IM 需求。

目前市场上 PaaS 服务商这两方面能力强弱不一:有的大厂虽然两方面能力都提供,但不能确保两种能力同样高质量;有的专业 RTC 厂商,只能提供 RTC 能力,IM 能力还得由第三方专业服务商提供。

这样,便迫使开发者在集成过程中不得不分别选择服务商。当实时音视频与 IM 质量不稳定时,需要逐一协调各个服务商,逐一排查问题,无形中增加了后期的运营成本。其实,IM 和音视频在很多场景下有耦合,建议开发者在选型一开始就要考虑具有 RTC+IM 双重高保障能力的通信云厂商,尽量“用一套 SDK,解决所有通信场景”。

任杰总2.png

对开发者来说两项功能同时开发,开发包相对比较小;如果前期只用到了 IM,没有用到 RTC,那么只需要学习 IM 方面的开发文档就可以了,一旦有了 RTC 需求,再去学习 RTC文档,开发者只需接入相关接口,快速与 IM 能力做对接和匹配,即可完成两类功能在 App 生命周期里的全覆盖。

除了开发上的易快速上手外,选择“IM+RTC+推送”整合的解决方案,开发者还可以享受一致的网络架构,提高传输的效率和质量,获得一致的服务保障。例如,融云近期升级了实时音视频能力,RTC的通信信令是复用 IM 信令通道,可以确保消息 100% 的连通率和到达率,使底层的通信优势发挥到最大。

二、延时、卡顿、抖动的质量问题要解决好

通过调研发现,用户最不能接受实时音视频的三个质量问题是延时、卡顿、抖动。

低延时要靠两个方面解决,一个是传输协议,一个是优化整体传输环节。实时音视频的主流传输协议有 RTMP 和 UDP 两种,一种支持 CDN 技术,一种支持 WebRTC 技术,相对来说,CDN 技术延时性在 3-5 秒,WebRTC 可以在几百毫秒以内,现在很多厂商可以同时支持这两种技术,分别适用于不同的场景。

整体传输环节中,采集/渲染、编解码/网络往返都会有一定的延时,有些是硬件的物理延迟,需要靠 5G 这样底层网络技术的提升,或者布更多的数据中心、边缘结点,便于就近接入;有些要针对实际场景,在具体形态上做一些权衡,比如在处理粒度上粗细的考虑,越细的粒度传输的数据包相对较大,延迟也会更高。

当音视频出现卡顿时,有一个视频流畅优先的原则。我们通过降低一些码率和帧率,即使画面模糊一点,也要让用户视觉上是流畅不卡顿的。这样在选型时候,要考虑几个方面:一个是优化低码率下的视频清晰度;二是要有带宽估算能力,当预判到这个带宽没法承受高清晰视频传输时,自动转化成低码率并通过优化算法,使低码率视频清晰度能媲美高清视频。

音视频弱网优势_副本.png


另外,数据包通常会以错误的顺序到达,从而产生抖动相关问题,或者直接丢失,造成音视频空白。谷歌一份资料显示,视频聊天应用 Duo 99% 的通话都有数据包丢失、过度抖动或网络延迟情况。20% 的通话丢失了超过 3% 的音频,10% 的通话丢包率超过 8%,也就是说每次通话都有很多音频需要替换。

处理上述问题,很多厂商会采用抗丢包及抗网络抖动能力的 NACK(丢包重传)、FEC(前向纠错)、自适应带宽调整(动态调整码)、接收端 Jitter Buffer(媒体流平稳)等各种机制,有些是组合使用,有些是单独使用,开发者在选型前一定要做到深入了解。

  • 拥有全球通信和场景化能力

    刚才谈到低延时、抗丢包的解决策略,有些是与网络接入路径长短直接相关的。比如中美两地的音视频连接,没有全球通信网络支持、数据中心和节点布局的厂商是提供不了服务的。开发者选型开发前,就要考虑到自己业务的所属范围。   

    选择全球化服务的云厂商,除了看数据中心和节点分布外,还要仔细考察全球网络布局的品质,简单说,有的厂商提供了全球网络优化能力,中美之间的音视频连接在未优化前要经过 100 多跳,而优化后仅 6 跳就能完成连通。这是由于,这些厂商拥有自有的路径最优算法,通过智能路由就近接入,即使在异国/地网络环境较差的情况下,仍然能够及时切换到更好的线路上去。比如融云拥有全球优化加速网络,实时音视频通话可做到全球端到端延时小于 400ms,最低延时 66ms,保障端到端之间延迟无感知的实时互动。

在场景化能力上,实际上相比 IM,实时音视频更加通道化,在各个场景中复用的程度也相对较高,能力也更基础。优秀的 PaaS 厂商会按场景提供不同的 Demo,音视频技术的升级也针对解决更多的应用场景去优化,便于开发者拿来即用,这种方式对入门级的开发者都十分友好。各种 API 接口相对独立,开发者只需关注和使用所需要的 SDK,就可以实现想要的场景,大大降低集成开发的难度。

四、开发者服务足够完善

在一些社区中,我们常常会看到一些技术文档下,开发者提出问题而没有回复。开发者为提高开发效率,越来越倾向于自助完成工作,因此,开发文档是否易懂,Demo 是否易用,都显得十分重要。

另外,工单回复的速度,微信群、社区的值守和响应程度等都能反映 PaaS 厂商服务意识的强弱。通常来说,7×24 小时技术支持服务,1 小时工单快速回复、快速远程接入、快速恢复的故障应急响应机制,这些都是对开发者很完善的服务支持。

有些厂商还会提供特色的质量监控服务,比如融云“北极星”的质量问题排查平台,通过可视化图表,快速定位卡顿位置,实时统计丢包率,使开发者可以自助排查每一次音视频通话过程中的丢包率、网络带宽等通信技术参数。可以直接定位用户问题,提高排查效率,提升用户体验。

点击阅读原文

最新活动推荐:

融云年中大促活动海报.png


LiveVideoStackCon 深圳站:融云解析 WebRTC 低延迟直播技术

WebRTC融云那些事 发表了文章 • 0 个评论 • 440 次浏览 • 2020-06-16 18:36 • 来自相关话题

“基于 WebRTC 的低延迟直播将会是未来直播行业的主流解决方案!”这是融云联合创始人兼CTO 杨攀在 8 月 LiveVideoStackCon 2019 音视频技术大会北京站上对于未来行业趋势的判断。仅仅 4 个月之后,当大会首次落户有“中国硅谷”之称的... ...查看全部

“基于 WebRTC 的低延迟直播将会是未来直播行业的主流解决方案!”这是融云联合创始人兼CTO 杨攀在 8 月 LiveVideoStackCon 2019 音视频技术大会北京站上对于未来行业趋势的判断。仅仅 4 个月之后,当大会首次落户有“中国硅谷”之称的深圳时,融云的另一位技术专家,首席架构师李淼就“基于 WebRTC 的低延迟直播方案”进行了深入的技术分享。

12 月 13-14 日,LiveVideoStackCon 音视频技术大会在深圳举办,大会聚焦音视频、图像、AI 等技术的最新探索与应用实践,覆盖社交、游戏、直播、智能设备等行业领域,面向开发者分享技术创新与最佳实践。本次大会,聚集了数十名海内外技术专家和上千名开发者围绕前沿技术发展进行探讨。


融云首席架构师李淼
随着我国 5G 正式走向商用,直播行业在获得更多发展机遇的同时,也对直播技术提出了新的挑战。传统直播解决方案如果无法解决技术层面导致的延时问题,那么这一弊病将在 5G 的高速网络环境下被无限放大,这也进一步促使了低延迟音视频直播技术方案的演化。对此,李淼结合 WebRTC 的低延迟特性,在现场展示了融云 WebRTC 直播场景的构建全过程及服务架构设计,并向开发者们分享了技术实践细节,希望通过新技术的应用来解决视频直播的延时问题。

为什么要选用 WebRTC 来做直播?李淼表示,相较于传统的直播解决方案,WebRTC 拥有着不可比拟的三大优势。首先是低延时,让直播用户可以享受低延时的观看体验。目前直播行业中绝大多数产品是基于 RTMP、HLS、HDL 方式构建的,即使在不考虑网络链路的情况下,也会产生秒级的延迟,而 WebRTC 则天生具备低延迟的优势,使用 WebRTC 直播可有效将延迟降低至 200ms 以下。

其次是流量消耗小。基于 UDP 传输的 WebRTC 相比基于 TCP 传输的 RTMP 等协议,由于 UDP 协议内容较 TCP 小,且数据包是基于 NACK 进行传输等特点,对于流量的使用也有明显的降低。对于开发者和直播企业而言,流量消耗大幅削减,成本也因此可以得到有效的控制。

而最重要的优势在于 WebRTC 技术方案可以使主播端与观众端保持一致。当主播端使用  WebRTC 进行推流时,主播端与观众端保持一致,可以减少开发的编码量,对于团队人员的占用和后期对于代码的维护,都能保证最低的资源消耗。

在 LiveVideoStackCon 现场,李淼向开发者讲解了如何通过 WebRTC 完成直播场景构建的全过程,并对于 WebRTC 直播的技术细节一一进行了详细解读。李淼表示,使用 WebRTC 直播方案,MCU 服务器的设计至关重要。一方面 MCU 可以按需进行编解码,另一方面需要以房间号进行聚合,记录每台MCU的状态并按最小资源分配新房间,通过这种设计来减少 WebRTC 直播方案的资源消耗。


WebRTC 直播发布订阅流程
当然,对于很多开发者而言,实际的生产环境中仍面临着如何做到秒开视频、降低 MCU 带宽压力以及避免流量风暴等难题,李淼从 GOP 缓存结构和 GOP 控制策略两个层面进行了分析。以解决首帧卡顿延迟为例,直播数据在客户端与 Media Sever 进行交互之后,通常会对 SPS 和 I 帧进行正常下发,但是在随后的 P 帧或 B 帧的下发阶段,融云会采用 1.2 倍速下发的方式进行,直至所有数据包与 MCU 端推包进程同步,这就将直播延迟降至了最低。

此外,李淼还指出,客户端的设计必须考虑就近接入,且支持多链路选择,数据中心间同源音视频只有一路级联;同时还可以利用 IaaS 层的能力,进行中心间级联链路的优化。遵循这些直播网络设计原则都可以有效地降低直播延迟。

在分享的最后,李淼表示在 5G 时代,直播、短视频等内容传播形态将迎来新一轮技术升级,用户体验将成为行业洗牌的关键,此次将 WebRTC 低延迟直播的设计理念和技术要点与开发者和行业人士们一同分享,希望能够给业界带来一些启发和思考。作为互联网通信云行业的技术领导者,融云也将持续优化实时音视频技术和场景化解决方案,助力音视频直播行业在 5G 时代的创新发展。

融云助力嘉和海森 以通信云技术服务在线医疗“战疫”到底

科技创新融云那些事 发表了文章 • 0 个评论 • 97 次浏览 • 2020-06-16 18:30 • 来自相关话题

这些天,新型冠状病毒肺炎疫情一直牵动着全国人民的心,与疫情相关的健康咨询需求也明显呈现激增趋势。然而,扎堆去发热门诊咨询问诊,在增加线下门诊压力的同时也极有可能会引发交叉感染。为平衡医疗资源,疏导人民群众进行针对性就医,提升问诊效率,安全便捷的线上咨询成为近期... ...查看全部

这些天,新型冠状病毒肺炎疫情一直牵动着全国人民的心,与疫情相关的健康咨询需求也明显呈现激增趋势。然而,扎堆去发热门诊咨询问诊,在增加线下门诊压力的同时也极有可能会引发交叉感染。为平衡医疗资源,疏导人民群众进行针对性就医,提升问诊效率,安全便捷的线上咨询成为近期就医形式的首选。

在这个全民抗疫的时刻,医疗软件行业知名企业嘉和海森与互联网通信云服务商融云联合多家医疗机构开展了网上免费健康咨询服务,包括北京朝阳医院、通辽市医院等全国各地的众多医疗机构纷纷通过嘉和海森互联网医疗软件开展网上咨询。所有在线咨询的患者,可通过文字、图片、音频和视频进行网络咨询,与医生进行实时互动,在线获取医生的健康指导,实现足不出户在线问医。


融云助力在线医疗App
以朝阳医院上线的朝阳健康云APP为例。在当患者进行健康咨询时,通常问的都是一些偏基础性的问题,医患之间可以通过图文、语音消息的方式进行互动。而在网络问诊中,考虑到需要对患者进行更详尽的检查,医患之间需要通过音频+视频的形式进行实时互动,同时通过图片+文字进行补充沟通。除了医患之间的在线问诊,App还可以让身处不同地区的医疗工作者,能够在同一群组中通过图文+语音的形式远程对患者的病情进行实时讨论,合理地平衡和调用各地医疗资源。


医生通过音视频在线问诊
毋庸置疑,在线问诊最需要的就是稳定可靠的互联网通信能力。无论是医疗行业的高精专属性还是考虑到病患问诊时紧张心理,医疗类App对于问诊时音视频画面和声音的稳定流畅性、图文交互的即时性都有着极高的要求。据了解,在嘉和海森医疗App中,所有医生与问诊者之间的图文、语音、视频的交互,都是通过融云互联网通信云实现的。

作为一家以技术立命的互联网科技公司,融云致力于为开发者和企业提供 IM即时通讯和实时音视频通信云服务。融云提供的 PaaS 层服务,可以通过 SDK 的形式集成到应用中,开发者可以根据自身业务的需要调用不同功能,自由组合下载。同时融云还针对在线医疗等不同行业属性,针对性地推出场景化解决方案,让开发者拿来即用,降低集成难度和开发时间。

在医疗资源紧张的当下,融云坚信“科技向善”,以安全稳定的即时通讯和实时音视频技术驰援互联网医疗。“足不出户,网络问医”这一模式不仅能有效分流患者,缓解门诊压力,同时通过在线问诊,医师可通过症状初步判断其发热原因,减少非新型肺炎患者前往医院就诊的交叉感染机率。

在此,融云也承诺从即日起至疫情结束,针对在线医疗场景不收取任何费用,向各在线健康咨询平台、政府疫情防控平台、互联网医院等平台免费开放即时通讯和实时音视频通信云服务。此外,融云也面向企业、机构免费开放在线教育以及协同办公场景下的实时音视频服务。

目前,融云已经开通了 7*24 小时咨询热线 13161856839,欢迎有需求的政府部门、医疗机构、企业机关与我们联系,待沟通确认符合相关场景需求后,我们将以最快的速度进行技术对接,助力政府和医疗机构应对此次疫情,为这场“战疫”献出一份绵薄之力。

疫情期间远程协同办公成刚需 融云助力拓维打造视频会议系

科技创新融云那些事 发表了文章 • 0 个评论 • 90 次浏览 • 2020-06-16 18:29 • 来自相关话题

一场新型冠状病毒疫情的突袭,严重影响了社会各领域的正常运行,也使得春节后的企业正常复工成为难题。为了减少人员流动和聚集,防止交叉感染,大量企业响应国家的号召,选择通过远程协同办公的形式来维持企业正常运营。如果说此前远程协同办公只是企业办公的一项补充条件,那么在... ...查看全部

一场新型冠状病毒疫情的突袭,严重影响了社会各领域的正常运行,也使得春节后的企业正常复工成为难题。

为了减少人员流动和聚集,防止交叉感染,大量企业响应国家的号召,选择通过远程协同办公的形式来维持企业正常运营。如果说此前远程协同办公只是企业办公的一项补充条件,那么在疫情期间显然已经成为一种用户刚需。据媒体报道,仅在 2 月 3 日当天,全国就有上千万企业、近 2 亿人开启远程办公模式。

为了帮助更多企业实现稳定高效的远程协同办公,拓维信息积极响应国家号召,免费向企业提供1个月的远程视频会议服务,同时对于原有客户免费增加用户许可,保证企业全员远程办公的需求,直到疫情结束。在这段视频会议流量大规模爆发的时期,拓维无纸化会议系统稳定可靠地支持了众多企业远程会议同时进行,有效地为企业在疫情期间正常运行提供了保障。


拓维无纸化会议系统
据了解,拓维无纸化会议系统可满足远程资料共享、远程视频通话、会议资料安全等会议需求,且支持多个会议同时召开,单个会议场景可支持数百人同时在线。而拓维无纸化会议系统之所以在用户量激增的情况下仍能保持平台的持续稳定运行,与其底层通信能力服务商融云多年来在实时音视频领域的技术积累和稳定服务密不可分。

作为国内领先的互联网通信云服务商,融云致力于为开发者和企业提供 IM 即时通讯和实时音视频通信云服务。融云 CPO 任杰介绍称,融云在实时音视频领域拥有多年研发经验,各项技术指标保持市场领先水平,支持流畅稳定的一对一、多对多音视频通话、服务端录像,同时通过分层编码、大小流设计、带宽估计以及多服务器优化等方面来实现对更多路的支撑。目前融云在视频会议方面可以做到同时支持 14 路,纯音频则能做到同时支持 25 路,优于市场上一般软件的能力表现。此外融云还在不断强化技术支持能力,让开发者 30 分钟即可快速集成音视频能力,加速产品的上线效率。

同时,在这次疫情期间,视频会议系统很容易出现在线人数激增、海量消息并发的峰值情况,往往因难以准确预估流量,错误部署服务器导致系统崩溃,非常考验系统架构稳定性。融云通过“锦囊”服务可以在前期以最佳实践经验安排专家团队协助客户进行服务器部署,当遇大规模流量涌入的峰值期时,运维人员和技术人员通过实时监控,可以动态调整服务器部署,针对可能的并发问题量身定制相应的保障方案,确保在疫情期间企业协同办公的正常运行。

在此,融云也向社会承诺,从即日起至疫情结束,免费向企业、机构提供协同办公场景下的实时音视频服务,同时免费开放在线医疗场景下的 IM 及实时音视频服务和在线教育场景下的实时音视频服务。目前,融云已经开通了 7*24 小时咨询热线 13161856839,欢迎有需求的企业、机构与我们联系,待沟通确认符合相关场景需求后,我们将以最快的速度进行技术对接,帮助更多企业解决疫情期间的协同办公难题,实现办公方式的迭代升级,为坚决打赢这场疫情防控阻击战提供更多助力。

LiveVideoStackCon 深圳站:融云解析 WebRTC 低延迟直播技术

WebRTC融云那些事 发表了文章 • 0 个评论 • 440 次浏览 • 2020-06-16 18:36 • 来自相关话题

“基于 WebRTC 的低延迟直播将会是未来直播行业的主流解决方案!”这是融云联合创始人兼CTO 杨攀在 8 月 LiveVideoStackCon 2019 音视频技术大会北京站上对于未来行业趋势的判断。仅仅 4 个月之后,当大会首次落户有“中国硅谷”之称的... ...查看全部

“基于 WebRTC 的低延迟直播将会是未来直播行业的主流解决方案!”这是融云联合创始人兼CTO 杨攀在 8 月 LiveVideoStackCon 2019 音视频技术大会北京站上对于未来行业趋势的判断。仅仅 4 个月之后,当大会首次落户有“中国硅谷”之称的深圳时,融云的另一位技术专家,首席架构师李淼就“基于 WebRTC 的低延迟直播方案”进行了深入的技术分享。

12 月 13-14 日,LiveVideoStackCon 音视频技术大会在深圳举办,大会聚焦音视频、图像、AI 等技术的最新探索与应用实践,覆盖社交、游戏、直播、智能设备等行业领域,面向开发者分享技术创新与最佳实践。本次大会,聚集了数十名海内外技术专家和上千名开发者围绕前沿技术发展进行探讨。


融云首席架构师李淼
随着我国 5G 正式走向商用,直播行业在获得更多发展机遇的同时,也对直播技术提出了新的挑战。传统直播解决方案如果无法解决技术层面导致的延时问题,那么这一弊病将在 5G 的高速网络环境下被无限放大,这也进一步促使了低延迟音视频直播技术方案的演化。对此,李淼结合 WebRTC 的低延迟特性,在现场展示了融云 WebRTC 直播场景的构建全过程及服务架构设计,并向开发者们分享了技术实践细节,希望通过新技术的应用来解决视频直播的延时问题。

为什么要选用 WebRTC 来做直播?李淼表示,相较于传统的直播解决方案,WebRTC 拥有着不可比拟的三大优势。首先是低延时,让直播用户可以享受低延时的观看体验。目前直播行业中绝大多数产品是基于 RTMP、HLS、HDL 方式构建的,即使在不考虑网络链路的情况下,也会产生秒级的延迟,而 WebRTC 则天生具备低延迟的优势,使用 WebRTC 直播可有效将延迟降低至 200ms 以下。

其次是流量消耗小。基于 UDP 传输的 WebRTC 相比基于 TCP 传输的 RTMP 等协议,由于 UDP 协议内容较 TCP 小,且数据包是基于 NACK 进行传输等特点,对于流量的使用也有明显的降低。对于开发者和直播企业而言,流量消耗大幅削减,成本也因此可以得到有效的控制。

而最重要的优势在于 WebRTC 技术方案可以使主播端与观众端保持一致。当主播端使用  WebRTC 进行推流时,主播端与观众端保持一致,可以减少开发的编码量,对于团队人员的占用和后期对于代码的维护,都能保证最低的资源消耗。

在 LiveVideoStackCon 现场,李淼向开发者讲解了如何通过 WebRTC 完成直播场景构建的全过程,并对于 WebRTC 直播的技术细节一一进行了详细解读。李淼表示,使用 WebRTC 直播方案,MCU 服务器的设计至关重要。一方面 MCU 可以按需进行编解码,另一方面需要以房间号进行聚合,记录每台MCU的状态并按最小资源分配新房间,通过这种设计来减少 WebRTC 直播方案的资源消耗。


WebRTC 直播发布订阅流程
当然,对于很多开发者而言,实际的生产环境中仍面临着如何做到秒开视频、降低 MCU 带宽压力以及避免流量风暴等难题,李淼从 GOP 缓存结构和 GOP 控制策略两个层面进行了分析。以解决首帧卡顿延迟为例,直播数据在客户端与 Media Sever 进行交互之后,通常会对 SPS 和 I 帧进行正常下发,但是在随后的 P 帧或 B 帧的下发阶段,融云会采用 1.2 倍速下发的方式进行,直至所有数据包与 MCU 端推包进程同步,这就将直播延迟降至了最低。

此外,李淼还指出,客户端的设计必须考虑就近接入,且支持多链路选择,数据中心间同源音视频只有一路级联;同时还可以利用 IaaS 层的能力,进行中心间级联链路的优化。遵循这些直播网络设计原则都可以有效地降低直播延迟。

在分享的最后,李淼表示在 5G 时代,直播、短视频等内容传播形态将迎来新一轮技术升级,用户体验将成为行业洗牌的关键,此次将 WebRTC 低延迟直播的设计理念和技术要点与开发者和行业人士们一同分享,希望能够给业界带来一些启发和思考。作为互联网通信云行业的技术领导者,融云也将持续优化实时音视频技术和场景化解决方案,助力音视频直播行业在 5G 时代的创新发展。

实现类似微信视频聊天的功能

WebRTC赵炳东 发表了文章 • 0 个评论 • 177 次浏览 • 2021-04-15 15:15 • 来自相关话题

前言今年因为疫情的影响,很多线下的项目或者活动都放在了线上,所以集成音视频等相关的话题一直比较火热。 作为一个小码农对微信音视频通话的功能还是比较好奇的,所以空闲的时候就想着自己能不能也实现一个,给朋友一个链接地址然后就可以愉快的视频通话了(小小的满足一下虚荣... ...查看全部

前言

今年因为疫情的影响,很多线下的项目或者活动都放在了线上,所以集成音视频等相关的话题一直比较火热。 作为一个小码农对微信音视频通话的功能还是比较好奇的,所以空闲的时候就想着自己能不能也实现一个,给朋友一个链接地址然后就可以愉快的视频通话了(小小的满足一下虚荣心)

微信截图_20210415151315.png

对于集成音视频纯小白来说,前期准备工作的确不好整,因为对这块没什么概念,对后面需要做什么列不出一个清晰的步骤出来(蓝廋香菇~~~),幸运的是,在网上找相关知识时意外发现了一家做音视频 SDK 的(融云https://www.rongcloud.cn/

为什么推荐他们家呢,主要是他们的开发文档比较适用于像我这样的小白,跟着他们的快速文档无脑实现音视频通话。点这个地址,开启你的音视频之旅吧:https://docs.rongcloud.cn/v4/views/rtc/call/noui/quick/web.html

特别意外的是融云还提供了 Web 端在线集成体验示例,体验完后还能拿到 demo ,附上跳转连接:https://docs.rongcloud.cn/v4/views/rtc/call/noui/demo/web.html

音视频通话介绍 音视频通话 SDK 含呼叫流程,可以实现一对一呼叫、多人呼叫等音视频通话场景,音视频通话 SDK 区别于音视频会议 SDK,音视频通话(呼叫)SDK 不需要双方约定房间号,呼叫流程自动处理房间号,适用于类似微信音视频通话等必须含有呼叫流程的应用场景,由两部分组成 信令通知 和 音视频传输

信令通知:基于 IMLib SDK,例如 A 给 B 发起通话,A 优先会通过 IM 给 B 发送一个通话请求,B 同意后再进入音视频房间进行音视频通信 音视频传输:基于 RongRTCLib SDK,例如 A 给 B 发起通话,信令传输完成后,会适用融云提供的音视频能力进行通信

但是在集成的时候,有几点是需要吐槽的:

1、我给其他人发起通话的时候,直接就触发挂断了返回 "SummaryMessage" 类型消息,咋又看不懂在文档找了半天都没发现 SummaryMessage 的详细描述,只知道是挂断电话返回的消息,最后提交了工单才了解挂断的原因。

Web 端挂断原因 HungupMessage 消息中 reason 字段及 SummaryMessage 消息中 status 字段都为挂断原因

状态码说明状态码说明
1己方取消已发出的通话请求11对方取消已发出的通话请求
2己方拒绝收到的通话请求12对方拒绝收到的通话请求
3己方挂断13对方挂断
4己方忙碌14对方忙碌
5己方未接听15对方未接听
6己方不支持当前引擎16对方不支持当前引擎
7己方网络错误17对方网络错误
8其他设备已处理18CallLib 不可用

原来是对方忙碌导致的接听失败!!!

2、前段时间还能正常通话,升级完 chrome 后就报错了(一脸懵逼 ),报错信息如下: Failed to execute 'setLocalDescription' on 'RTCPeerConnection': Failed to parse SessionDescription. m=video 9 UDP/TLS/RTP/SAVPF 98 99 96 97 100 101 127 Invalid value:

提供单询问融云的技术人员,才知道是由于 chrome 86 升级了 webRTC 的规范,SDK 在 3.2.6 版本做了相关适配。如果 SDK 使用的不是 3.2.6 版本。需要升级至 3.2.6 版本


超大规模会议技术优化策略 轻松实现 500 人线上流畅沟通

WebRTC梅川酷子 发表了文章 • 0 个评论 • 206 次浏览 • 2021-03-01 16:44 • 来自相关话题

受疫情影响,许多公司已经形成线上办公习惯,尤其是在线音视频会议,已经成为一种常态。对于一些大型企业和组织机构来说,分支机构遍布全国各地,员工异地参会人数众多,大规模音视频会议成为刚需。而当前音视频会议主流产品中,单个会议最多支持 500 人入会进行互动。但是 ... ...查看全部
受疫情影响,许多公司已经形成线上办公习惯,尤其是在线音视频会议,已经成为一种常态。对于一些大型企业和组织机构来说,分支机构遍布全国各地,员工异地参会人数众多,大规模音视频会议成为刚需。而当前音视频会议主流产品中,单个会议最多支持 500 人入会进行互动。


但是 500 人同时线上开会,对于资源消耗比较高。而传统的 WebRTC 架构并不擅长超过 200 人以上的会议场景。在面对超大规模会议室、聊天室、直播等各种复杂场景时,对流进行按需合流,可以降低带宽占用和设备压力;对流进行有选择的订阅分发,有助于扩展各种组合场景。针对 App 具体的应用场景,可以配合订阅分发模式,组合使用 SFU 和 MCU 架构。下来我们将详细分析一下大规模会议的资源优化策略。

1.超大规模会议架构对比


WebRTC 多对多网络架构有 P2P、MCU、SFU 三种。各种网络拓扑的优缺点如下:

1.png

SFU 方式灵活,只要降低带宽就可以实现大规模会议的要求。


2.超大规模会议中存在的挑战


在超过 20 人会议场景下,SFU 及 WebRTC 兼容场景仍然无法很好的解决。如果直接选择参会人之间进行音视频互动,音视频数据完全转发对服务器资源的要求是巨大的,如果会议中有大量人员同时接入,服务端上行流量和下行流量陡增,会对服务器造成巨大压力。


这里我们来对比一下 20 人与 200 人同时参加音视频会议时,对服务端造成压力的差距:
20人
各端流量:
20*(1Mbps+32Kbps)=20.64Mbps
服务端上行流量:
20*(1Mbps+32Kbps)=20.64Mbps
服务端下行流量:
20*(20-1)*(1Mbps+32Kbps)=392.16Mbps
200人
各端流量:
200*(1Mbps+32Kbps)=206.4Mbps
服务端上行流量:
200*(1Mbps+32Kbps)=206.4Mbps
服务端下行流量:
200*(200-1)*(1Mbps+32Kbps)=41.07Gbps

从对比结果中可以看出,服务端下行流量直接上升了一个量级。如果采用视频按需订阅,音频选择出音量最大的几路可以大大降低下行流量。比如每个客户端订阅 4 路视频,服务器只需下发 4 路音量最大的音频,服务端下行流量只需要 200*4*(1Mbps+32Kbps)=800+25.6=825.6Mbps,可以极大缓解服务器压力。
 
若要解决上面的问题,建议通过按需订阅与转发、音频流量两个方面来制定策略,在保证效果的前提下,降低服务端的压力。

3.按需订阅与转发以及音频流量优化策略


3.1 按需订阅与转发


按需订阅与转发的方式有:
支持单独订阅某个人的某路视频或某路音频。
接收端仅订阅正在说话的人的视频,音频全部订阅。
融云 SDK 支持发送端视频编码支持大小流。接收端按需订阅大流或小流。大流的清晰度高,码率高;小流的清晰度低,码率低。这样当接收端想观看清晰视频的时候订阅大流;对清晰度要求不高的时候订阅小流。另外,弱网下自动切换大小流,可以保证视频的流畅性。

3.2 音频流量优化策略

针对音频全部订阅有以下几种优化音频流量的方法。

3.2.1 发送端静音时不发送数据
WebRTC 的音频 codec 如果采用 Opus,可以开启 Opus 的 DTX(Discontinuous Transmission)。SDP 对应的设置为 usedtx=1。但测试中发现流量下降不如预期,因为用户的使用环境多少有点背景音。背景音量很容易超出静音阈值。像 Android/iOS 这种定制开发端可以手动调整静音阈值,而 PC 的 Web 端因为是浏览器,则无法调整静音阈值。

3.2.2 调整音频码率
通过设置客户端上音频码率,降低客户端上行的音频码率。当音频路数跟多的时候,限定每一路的音频码率后,总的音频码率会减少很多。SDP 设置方式 b=AS:码率。下面是摘自 RFC3556 的原文:

The Session Description Protocol includes an optional bandwidth
   attribute with the following syntax:

      b=<modifier>:<bandwidth-value>

   where <modifier> is a single alphanumeric word giving the meaning of
   the bandwidth figure, and where the default units for <bandwidth-
   value> are kilobits per second.  This attribute specifies the
   proposed bandwidth to be used by the session or media.

   A typical use is with the modifier "AS" (for Application Specific
   Maximum) which may be used to specify the total bandwidth for a
   single media stream from one site (source).
3.2.3 服务器下发音量 Top N 路

客户端收到音频流,在音频解码后,默认一般仅混流播放音量最大的 3(WebRTC 中的 kMaximumAmountOfMixedAudioSources 值)路声音。所以避免不必要的音频包的转发可以减少服务流量的。步骤如下:
发送端通过 Audio Level 标识音频能量。
音频包进入 SFU 转发队列,先进入计算队列,定期弹出 Top N 的音频包。
只有有效音频包,会进入到下行分发队列。
 
下面介绍音频如何转发音量最大几路的方法实践。

4. 音频 Top N 选择


4.1 客户端处理

客户端会计算出音量大小,并把值记录在 RTP 包中。所以客户端需要开启 audio-level 的 RTP 扩展, 如下: a=extmap:1urn:ietf:params:rtp-hdrext:ssrc-audio-level 开启这个 RTP 扩展后,WebRTC 客户端机会计算 audio 包的音量大小。这个音量大小计算方法 RFC6464 有明确定义。WebRTC 中的计算方法为 modules/audio_processing/rms_level.cc 的 ComputeRms 方法:

// Calculates the normalized RMS value from a mean square value. The input
// should be the sum of squared samples divided by the number of samples. The
// value will be normalized to full range before computing the RMS, wich is
// returned as a negated dBfs. That is, 0 is full amplitude while 127 is very
// faint.
int ComputeRms(float mean_square) {
  if (mean_square <= kMinLevel * kMaxSquaredLevel) {
    // Very faint; simply return the minimum value.
    return RmsLevel::kMinLevelDb;
  }
  // Normalize by the max level.
  const float mean_square_norm = mean_square / kMaxSquaredLevel;
  RTC_DCHECK_GT(mean_square_norm, kMinLevel);
  // 20log_10(x^0.5) = 10log_10(x)
  const float rms = 10.f * log10(mean_square_norm);
  RTC_DCHECK_LE(rms, 0.f);
  RTC_DCHECK_GT(rms, -RmsLevel::kMinLevelDb);
  // Return the negated value.
  return static_cast<int>(-rms + 0.5f);
}
客户端告诉服务器音频包的音量大小。服务器收到音频包后不用做解码,就能知道从客户端上来的音频包的音量值,为后面的服务器音频包下发策略奠定了基础。


4.2 服务器处理

下面用 Publisher 表示发布者的音频流,Subscriber 表示订阅者的音频流。RtpAudioPacket 表示一个音频包。RtpAudioPacket 里有个 mute 属性,标记这个音频包时是否静音。

在没有音频根据音量大小转发的逻辑前,Publisher 和 Subscriber 的处理关系如下。


2.png

Subscriber1、Subscriber2、Subscriber3 订阅 Publisher1、Publisher2、Publisher3。Publisher 发上来的音频包都会转发给各自的订阅者。


音频根据音量大小转发的逻辑如下:
AudioLevelHandler 表示每个 Publisher 的音频处理单元。AudioLevelHandler 里有两个音频包缓冲队列,计算队列 calculate_queue 和发送队列 send_queue。Publisher 的音频包先进入计算队列 calculate_queue 中。有个定时计算任务 AudioLevelCalculator。AudioLevelCalculator 会每隔一个音频打包时间 ptime 进行一次对所有 Publisher 的计算队列里音频包的 audio_level 均值(因为均值表示这个 Publisher 收到的若干个音频包的音量)做排序计算,选出音量值最大的几路。这几路的音频包 RtpAudioPacket 的 mute 被标记为 false,而其他音频包标记为 true。
排序后,这些音频包会从计算队列里移入到发送队列 send_queue 中。
之后音频包从 send_queue 出队,转发给 Subscriber。Subscriber 中的 MuteHandler 有以下两个作用:
a. 根据 RtpAudioPacket 的 mute 属性,mute 为 true 时,这个音频包直接被吞掉,false 表示转发给订阅者。
b. 因为下发给订阅者的音频包 RTP 序号 SeqNum 不是连续的,需要做连续化处理。

下面图中 Subscriber1、Subscriber2、Subscriber3 订阅 Publisher1、Publisher2、Publisher3。假设 Publisher1 收到的当前音量最大,最终只有它的音频包会转发给 Subscriber1、Subscriber2、Subscriber3。

3.png

4.3 级联的考虑


比如下面的图中,Subscriber4 通过级联服务器连接到当前 MediaServer 上。Publisher1、Publisher2、Publisher3 的音频包都会直接转发级联服务器。由级联服务器负责计算 Top N 音频包的计算下发给 Subscriber4。

5.png

下面是这部逻辑的伪代码:
void Publisher::Process(RtpAudioPacket packet, AudioLevelHandler handler) {
    handler.calculate_queue.enqueue(packet)

    RtpAudioPacket packetSend = handler.send_queue.dequeue();
    for (对当前Publisher的所有Subscriber subscriber) {
        if (subscriber是级联服务器) {
            转发packet
        } else {
            转发packetSend
        }
    }
}
4.4 音频下发策略优化


现实中人的说话是有停顿的。比如停顿前后人声比较大,如果简单的排序下发音频包,客户端会收到连续的非静音包。经测试,这样的体验并不理想,因此需要加入平滑处理。这里 history 为过去若干次的音频是否进入 Top N。音频包是最大的几路中的,加入 history 队列尾部加入 true,转发表示此次声音大而发。否则,加入 history 队列尾部加入 false。因为本次静音,还需判断过去的静音情况,若 history 中有 true 值,转发可表示过去一小段说过话,所以需要转发。若 history 中全为 false, 不转发则表示本次声音不大,过去一小段声音也不大,所以不转。

4.5 其他相关策略

当会议中的人数相对比较的少的时候,音频包为上面所述的正常转发。而当多个 Publisher 的订阅人数超过某个阈值(比如 50),此时 MediaServer 发的音频码率很大,对应客户端也要收相应的音频流量。这时可以走超大会议音频最大几路转发逻辑。而当会议中多个 Publisher 的订阅人数下降到阈值之下,再回归正常的转发逻辑。
经过选取最大几路流的下发方式,音频流量已经大大降低了。而在此基础上实际设置的选取路数做少许冗余,可以多发一些有音量的音频包,提高接收方体验。
当参会者增加时,相应的 MediaServer 也需要动态调度。通过把参会者音视频流打到多个 MediaServer 上,通过级联的方式解决问题,保证每台 MediaServer 服务器上的 CPU、内存、带宽的正常。

5. 总结


以上是基于超大规模会议技术优化进行的策略方面的探索。其主要思想是视频按需订阅,音频降低不必要的流量。其中涉及客户端音量值的上传、服务器端音量选择、级联、优化体验、减少音频流量等多个方面。研发过程中,超大会议需要多测试,才能暴露其中的问题,从而提高最终的会议体验。


iOS 基于实时音视频 SDK 实现屏幕共享功能——2

WebRTCadmin 发表了文章 • 0 个评论 • 800 次浏览 • 2020-12-03 18:26 • 来自相关话题

iOS 基于实时音视频 SDK 实现屏幕共享功能——1iOS 基于实时音视频 SDK 实现屏幕共享功能——2iOS 基于实时音视频 SDK 实现屏幕共享功能——3iOS 基于实时音视频 SDK 实现屏幕共享功能——4这里的核心思想是拿到屏幕共享的数据之后,先进... ...查看全部

微信截图_20201203182500.png


iOS 基于实时音视频 SDK 实现屏幕共享功能——1

iOS 基于实时音视频 SDK 实现屏幕共享功能——2

iOS 基于实时音视频 SDK 实现屏幕共享功能——3

iOS 基于实时音视频 SDK 实现屏幕共享功能——4



这里的核心思想是拿到屏幕共享的数据之后,先进行压缩,当压缩完成后会通过回调上报给当前类。既而通过 clientSend 方法,发给主 App。发给主 App 的数据中自定义了一个头部,头部添加了一个前缀和一个每次发送字节的长度,当接收端收到数据包后解析即可。

- (void)clientSend:(NSData *)data {
    //data length
    NSUInteger dataLength = data.length;
    // data header struct
    DataHeader dataH;
    memset((void *)&dataH, 0, sizeof(dataH));
    // pre
    PreHeader preH;
    memset((void *)&preH, 0, sizeof(preH));
    preH.pre[0] = '&';
    preH.dataLength = dataLength;
    dataH.preH = preH;
    // buffer
    int headerlength = sizeof(dataH);
    int totalLength = dataLength + headerlength;
    // srcbuffer
    Byte *src = (Byte *)[data bytes];
    // send buffer
    char *buffer = (char *)malloc(totalLength * sizeof(char));
    memcpy(buffer, &dataH, headerlength);
    memcpy(buffer + headerlength, src, dataLength);
    // to send
    [self sendBytes:buffer length:totalLength];
    free(buffer);
}

1.4 接收屏幕共享数据

//
//  RongRTCServerSocket.m
//  SealRTC
//
//  Created by Sun on 2020/5/7.
//  Copyright © 2020 RongCloud. All rights reserved.
//
#import "RongRTCServerSocket.h"
#import <arpa/inet.h>
#import <netdb.h>
#import <sys/types.h>
#import <sys/socket.h>
#import <ifaddrs.h>
#import <UIKit/UIKit.h>
#import "RongRTCThread.h"
#import "RongRTCSocketHeader.h"
#import "RongRTCVideoDecoder.h"
@interface RongRTCServerSocket() <RongRTCCodecProtocol>
{
    pthread_mutex_t lock;
    int _frameTime;
    CMTime _lastPresentationTime;
    Float64 _currentMediaTime;
    Float64 _currentVideoTime;
    dispatch_queue_t _frameQueue;
}
@property (nonatomic, assign) int acceptSocket;
/**
 data length
 */
@property (nonatomic, assign) NSUInteger dataLength;
/**
 timeData
 */
@property (nonatomic, strong) NSData *timeData;
/**
 decoder queue
 */
@property (nonatomic, strong) dispatch_queue_t decoderQueue;
/**
 decoder
 */
@property (nonatomic, strong) RongRTCVideoDecoder *decoder;
@end
@implementation RongRTCServerSocket
- (BOOL)createServerSocket {
    if ([self createSocket] == -1) {
        return NO;
    }
    [self setReceiveBuffer];
    [self setReceiveTimeout];
    BOOL isB = [self bind];
    BOOL isL = [self listen];
    if (isB && isL) {
        _decoderQueue = dispatch_queue_create("cn.rongcloud.decoderQueue", NULL);
        _frameTime = 0;
        [self createDecoder];
        [self receive];
        return YES;
    } else {
        return NO;
    }
}
- (void)createDecoder {
    self.decoder = [[RongRTCVideoDecoder alloc] init];
    self.decoder.delegate = self;
    RongRTCVideoEncoderSettings *settings = [[RongRTCVideoEncoderSettings alloc] init];
    settings.width = 720;
    settings.height = 1280;
    settings.startBitrate = 300;
    settings.maxFramerate = 30;
    settings.minBitrate = 1000;
    [self.decoder configWithSettings:settings onQueue:_decoderQueue];
}
- (void)receiveData {
    struct sockaddr_in rest;
    socklen_t rest_size = sizeof(struct sockaddr_in);
    self.acceptSocket = accept(self.socket, (struct sockaddr *) &rest, &rest_size);
    while (self.acceptSocket != -1) {
        DataHeader dataH;
        memset(&dataH, 0, sizeof(dataH));
        if (![self receiveData:(char *)&dataH length:sizeof(dataH)]) {
            continue;
        }
        PreHeader preH = dataH.preH;
        char pre = preH.pre[0];
        if (pre == '&') {
            // rongcloud socket
            NSUInteger dataLenght = preH.dataLength;
            char *buff = (char *)malloc(sizeof(char) * dataLenght);
            if ([self receiveData:(char *)buff length:dataLenght]) {
                NSData *data = [NSData dataWithBytes:buff length:dataLenght];
                [self.decoder decode:data];
                free(buff);
            }
        } else {
            NSLog(@"pre is not &");
            return;
        }
    }
}
- (BOOL)receiveData:(char *)data length:(NSUInteger)length {
    LOCK(lock);
    int receiveLength = 0;
    while (receiveLength < length) {
        ssize_t res = recv(self.acceptSocket, data, length - receiveLength, 0);
        if (res == -1 || res == 0) {
            UNLOCK(lock);
            NSLog(@"receive data error");
            break;
        }
        receiveLength += res;
        data += res;
    }
    UNLOCK(lock);
    return YES;
}
- (void)didGetDecodeBuffer:(CVPixelBufferRef)pixelBuffer {
    _frameTime += 1000;
    CMTime pts = CMTimeMake(_frameTime, 1000);
    CMSampleBufferRef sampleBuffer = [RongRTCBufferUtil sampleBufferFromPixbuffer:pixelBuffer time:pts];
    // Check to see if there is a problem with the decoded data. If the image appears, you are right.
    UIImage *image = [RongRTCBufferUtil imageFromBuffer:sampleBuffer];
    [self.delegate didProcessSampleBuffer:sampleBuffer];
    CFRelease(sampleBuffer);
}
- (void)close {
    int res = close(self.acceptSocket);
    self.acceptSocket = -1;
    NSLog(@"shut down server: %d", res);
    [super close];
}
- (void)dealloc {
    NSLog(@"dealoc server socket");
}
@end

主 App 通过 Socket 会持续收到数据包,再将数据包进行解码,将解码后的数据通过代理 didGetDecodeBuffer 代理方法回调给 App 层。App 层就可以通过融云 RongRTCLib 的发送自定义流方法将视频数据发送到对端。



iOS 基于实时音视频 SDK 实现屏幕共享功能——1

WebRTCadmin 发表了文章 • 0 个评论 • 834 次浏览 • 2020-12-03 18:21 • 来自相关话题

iOS 基于实时音视频 SDK 实现屏幕共享功能——1iOS 基于实时音视频 SDK 实现屏幕共享功能——2iOS 基于实时音视频 SDK 实现屏幕共享功能——3iOS 基于实时音视频 SDK 实现屏幕共享功能——4Replaykit 介绍在之前的 iOS 版... ...查看全部

微信截图_20201203181458.png

iOS 基于实时音视频 SDK 实现屏幕共享功能——1

iOS 基于实时音视频 SDK 实现屏幕共享功能——2

iOS 基于实时音视频 SDK 实现屏幕共享功能——3

iOS 基于实时音视频 SDK 实现屏幕共享功能——4

Replaykit 介绍

在之前的 iOS 版本中,iOS 开发者只能拿到编码后的数据,拿不到原始的 PCM 和 YUV,到 iOS 10 之后,开发者可以拿到原始数据,但是只能录制 App 内的内容,如果切到后台,将停止录制,直到 iOS 11,苹果对屏幕共享进行了升级并开放了权限,既可以拿到原始数据,又可以录制整个系统,以下我们重点来说 iOS 11 之后的屏幕共享功能。

系统屏幕共享

- (void)initMode_1 {
    self.systemBroadcastPickerView = [[RPSystemBroadcastPickerView alloc] initWithFrame:CGRectMake(0, 64, ScreenWidth, 80)];
    self.systemBroadcastPickerView.preferredExtension = @"cn.rongcloud.replaytest.Recoder";
    self.systemBroadcastPickerView.backgroundColor = [UIColor colorWithRed:53.0/255.0 green:129.0/255.0 blue:242.0/255.0 alpha:1.0];
    self.systemBroadcastPickerView.showsMicrophoneButton = NO;
    [self.view addSubview:self.systemBroadcastPickerView];
}

在 iOS 11 创建一个 Extension 之后,调用上面的代码就可以开启屏幕共享了,然后系统会为我们生成一个 SampleHandler 的类,在这个方法中,苹果会根据 RPSampleBufferType 上报不同类型的数据。

- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType

那怎么通过融云的 RongRTCLib 将屏幕共享数据发送出去呢?

1. 基于 Socket 的逼格玩法

1.1. Replaykit 框架启动和创建 Socket

//
//  ViewController.m
//  Socket_Replykit
//
//  Created by Sun on 2020/5/19.
//  Copyright © 2020 RongCloud. All rights reserved.
//
#import "ViewController.h"
#import <ReplayKit/ReplayKit.h>
#import "RongRTCServerSocket.h"
@interface ViewController ()<RongRTCServerSocketProtocol>
@property (nonatomic, strong) RPSystemBroadcastPickerView *systemBroadcastPickerView;
/**
 server socket
 */
@property(nonatomic , strong)RongRTCServerSocket *serverSocket;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    // Do any additional setup after loading the view.
    [self.serverSocket createServerSocket];
    self.systemBroadcastPickerView = [[RPSystemBroadcastPickerView alloc] initWithFrame:CGRectMake(0, 64, [UIScreen mainScreen].bounds.size.width, 80)];
    self.systemBroadcastPickerView.preferredExtension = @"cn.rongcloud.sealrtc.RongRTCRP";
    self.systemBroadcastPickerView.backgroundColor = [UIColor colorWithRed:53.0/255.0 green:129.0/255.0 blue:242.0/255.0 alpha:1.0];
    self.systemBroadcastPickerView.showsMicrophoneButton = NO;
    [self.view addSubview:self.systemBroadcastPickerView];
}
- (RongRTCServerSocket *)serverSocket {
    if (!_serverSocket) {
        RongRTCServerSocket *socket = [[RongRTCServerSocket alloc] init];
        socket.delegate = self;
        _serverSocket = socket;
    }
    return _serverSocket;
}
- (void)didProcessSampleBuffer:(CMSampleBufferRef)sampleBuffer {
    // 这里拿到了最终的数据,比如最后可以使用融云的音视频SDK RTCLib 进行传输就可以了
}
@end

其中,包括了创建 Server Socket 的步骤,我们把主 App 当做 Server,然后屏幕共享 Extension 当做 Client ,通过 Socket 向我们的主 APP 发送数据。

在 Extension 里面,我们拿到 ReplayKit 框架上报的屏幕视频数据后:

//
//  SampleHandler.m
//  SocketReply
//
//  Created by Sun on 2020/5/19.
//  Copyright © 2020 RongCloud. All rights reserved.
//
#import "SampleHandler.h"
#import "RongRTCClientSocket.h"
@interface SampleHandler()
/**
 Client Socket
 */
@property (nonatomic, strong) RongRTCClientSocket *clientSocket;
@end
@implementation SampleHandler
- (void)broadcastStartedWithSetupInf(NSDictionary<NSString *,NSObject *> *)setupInfo {
    // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional.
    self.clientSocket = [[RongRTCClientSocket alloc] init];
    [self.clientSocket createCliectSocket];
}
- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
    switch (sampleBufferType) {
        case RPSampleBufferTypeVide
            // Handle video sample buffer
            [self sendData:sampleBuffer];
            break;
        case RPSampleBufferTypeAudioApp:
            // Handle audio sample buffer for app audio
            break;
        case RPSampleBufferTypeAudioMic:
            // Handle audio sample buffer for mic audio
            break;
        default:
            break;
    }
}
- (void)sendData:(CMSampleBufferRef)sampleBuffer {
    [self.clientSocket encodeBuffer:sampleBuffer];
}
@end

可见 ,这里我们创建了一个 Client Socket,然后拿到屏幕共享的视频 sampleBuffer 之后,通过 Socket 发给我们的主 App,这就是屏幕共享的流程。

1.2 Local Socket 的使用

//
//  RongRTCSocket.m
//  SealRTC
//
//  Created by Sun on 2020/5/7.
//  Copyright © 2020 RongCloud. All rights reserved.
//
#import "RongRTCSocket.h"
#import <arpa/inet.h>
#import <netdb.h>
#import <sys/types.h>
#import <sys/socket.h>
#import <ifaddrs.h>
#import "RongRTCThread.h"
@interface RongRTCSocket()
/**
 receive thread
 */
@property (nonatomic, strong) RongRTCThread *receiveThread;
@end
@implementation RongRTCSocket
- (int)createSocket {
    int socket = socket(AF_INET, SOCK_STREAM, 0);
    self.socket = socket;
    if (self.socket == -1) {
        close(self.socket);
        NSLog(@"socket error : %d", self.socket);
    }
    self.receiveThread = [[RongRTCThread alloc] init];
    [self.receiveThread run];
    return socket;
}
- (void)setSendBuffer {
    int optVal = 1024 * 1024 * 2;
    int optLen = sizeof(int);
    int res = setsockopt(self.socket, SOL_SOCKET, SO_SNDBUF, (char *)&optVal,optLen);
    NSLog(@"set send buffer:%d", res);
}
- (void)setReceiveBuffer {
    int optVal = 1024 * 1024 * 2;
    int optLen = sizeof(int);
    int res = setsockopt(self.socket, SOL_SOCKET, SO_RCVBUF, (char*)&optVal,optLen );
    NSLog(@"set send buffer:%d",res);
}
- (void)setSendingTimeout {
    struct timeval timeout = {10,0};
    int res = setsockopt(self.socket, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(int));
    NSLog(@"set send timeout:%d", res);
}
- (void)setReceiveTimeout {
    struct timeval timeout = {10, 0};
    int  res = setsockopt(self.socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(int));
    NSLog(@"set send timeout:%d", res);
}
- (BOOL)connect {
    NSString *serverHost = [self ip];
    struct hostent *server = gethostbyname([serverHost UTF8String]);
    if (server == NULL) {
        close(self.socket);
        NSLog(@"get host error");
        return NO;
    }
    struct in_addr *remoteAddr = (struct in_addr *)server->h_addr_list[0];
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr = *remoteAddr;
    addr.sin_port = htons(CONNECTPORT);
    int res = connect(self.socket, (struct sockaddr *) &addr, sizeof(addr));
    if (res == -1) {
        close(self.socket);
        NSLog(@"connect error");
        return NO;
    }
    NSLog(@"socket connect to server success");
    return YES;
}
- (BOOL)bind {
    struct sockaddr_in client;
    client.sin_family = AF_INET;
    NSString *ipStr = [self ip];
    if (ipStr.length <= 0) {
        return NO;
    }
    const char *ip = [ipStr cStringUsingEncoding:NSASCIIStringEncoding];
    client.sin_addr.s_addr = inet_addr(ip);
    client.sin_port = htons(CONNECTPORT);
    int bd = bind(self.socket, (struct sockaddr *) &client, sizeof(client));
    if (bd == -1) {
        close(self.socket);
        NSLog(@"bind error: %d", bd);
        return NO;
    }
    return YES;
}
- (BOOL)listen {
    int ls = listen(self.socket, 128);
    if (ls == -1) {
        close(self.socket);
        NSLog(@"listen error: %d", ls);
        return NO;
    }
     return YES;
}
- (void)receive {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self receiveData];
    });
}
- (NSString *)ip {
    NSString *ip = nil;
    struct ifaddrs *addrs = NULL;
    struct ifaddrs *tmpAddrs = NULL;
    BOOL res = getifaddrs(&addrs);
    if (res == 0) {
        tmpAddrs = addrs;
        while (tmpAddrs != NULL) {
            if (tmpAddrs->ifa_addr->sa_family == AF_INET) {
                // Check if interface is en0 which is the wifi connection on the iPhone
                NSLog(@"%@", [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)tmpAddrs->ifa_addr)->sin_addr)]);
                if ([[NSString stringWithUTF8String:tmpAddrs->ifa_name] isEqualToString:@"en0"]) {
                    // Get NSString from C String
                    ip = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)tmpAddrs->ifa_addr)->sin_addr)];
                }
            }
            tmpAddrs = tmpAddrs->ifa_next;
        }
    }
    // Free memory
    freeifaddrs(addrs);
    NSLog(@"%@",ip);
    return ip;
}
- (void)close {
    int res = close(self.socket);
    NSLog(@"shut down: %d", res);
}
- (void)receiveData {
}
- (void)dealloc {
    [self.receiveThread stop];
}

@end

首先创建了一个 Socket 的父类,然后用 Server Socket 和 Client Socket 分别继承类来实现链接、绑定等操作。可以看到有些数据可以设置,有些则不用,这里不是核心,核心是怎样收发数据。

1.3 发送屏幕共享数据

//
//  RongRTCClientSocket.m
//  SealRTC
//
//  Created by Sun on 2020/5/7.
//  Copyright © 2020 RongCloud. All rights reserved.
//
#import "RongRTCClientSocket.h"
#import <arpa/inet.h>
#import <netdb.h>
#import <sys/types.h>
#import <sys/socket.h>
#import <ifaddrs.h>
#import "RongRTCThread.h"
#import "RongRTCSocketHeader.h"
#import "RongRTCVideoEncoder.h"
@interface RongRTCClientSocket() <RongRTCCodecProtocol> {
    pthread_mutex_t lock;
}
/**
 video encoder
 */
@property (nonatomic, strong) RongRTCVideoEncoder *encoder;
/**
 encode queue
 */
@property (nonatomic, strong) dispatch_queue_t encodeQueue;
@end
@implementation RongRTCClientSocket
- (BOOL)createClientSocket {
    if ([self createSocket] == -1) {
        return NO;
    }
    BOOL isC = [self connect];
    [self setSendBuffer];
    [self setSendingTimeout];
    if (isC) {
        _encodeQueue = dispatch_queue_create("cn.rongcloud.encodequeue", NULL);
        [self createVideoEncoder];
        return YES;
    } else {
        return NO;
    }
}
- (void)createVideoEncoder {
    self.encoder = [[RongRTCVideoEncoder alloc] init];
    self.encoder.delegate = self;
    RongRTCVideoEncoderSettings *settings = [[RongRTCVideoEncoderSettings alloc] init];
    settings.width = 720;
    settings.height = 1280;
    settings.startBitrate = 300;
    settings.maxFramerate = 30;
    settings.minBitrate = 1000;
    [self.encoder configWithSettings:settings onQueue:_encodeQueue];
}
- (void)clientSend:(NSData *)data {
    //data length
    NSUInteger dataLength = data.length;
    // data header struct
    DataHeader dataH;
    memset((void *)&dataH, 0, sizeof(dataH));
    // pre
    PreHeader preH;
    memset((void *)&preH, 0, sizeof(preH));
    preH.pre[0] = '&';
    preH.dataLength = dataLength;
    dataH.preH = preH;
    // buffer
    int headerlength = sizeof(dataH);
    int totalLength = dataLength + headerlength;
    // srcbuffer
    Byte *src = (Byte *)[data bytes];
    // send buffer
    char *buffer = (char *)malloc(totalLength * sizeof(char));
    memcpy(buffer, &dataH, headerlength);
    memcpy(buffer + headerlength, src, dataLength);
    // tosend
    [self sendBytes:buffer length:totalLength];
    free(buffer);
}
- (void)encodeBuffer:(CMSampleBufferRef)sampleBuffer {
    [self.encoder encode:sampleBuffer];
}
- (void)sendBytes:(char *)bytes length:(int)length {
    LOCK(self->lock);
    int hasSendLength = 0;
    while (hasSendLength < length) {
        // connect socket success
        if (self.socket > 0) {
            // send
            int sendRes = send(self.socket, bytes, length - hasSendLength, 0);
            if (sendRes == -1 || sendRes == 0) {
                UNLOCK(self->lock);
                NSLog(@"send buffer error");
                [self close];
                break;
            }
            hasSendLength += sendRes;
            bytes += sendRes;
        } else {
            NSLog(@"client socket connect error");
            UNLOCK(self->lock);
        }
    }
    UNLOCK(self->lock); 
}
- (void)spsData:(NSData *)sps ppsData:(NSData *)pps {
    [self clientSend:sps];
    [self clientSend:pps];
}
- (void)naluData:(NSData *)naluData {
    [self clientSend:naluData];
}
- (void)deallo c{
    NSLog(@"dealoc cliect socket");
}
@end


30万+App都在用的服务商,有什么特别之处?

科技创新融云那些事 发表了文章 • 0 个评论 • 182 次浏览 • 2020-08-03 16:57 • 来自相关话题

两个月前,一款名为 Clubhouse 的 App 开始流行于美国 VC、名人圈层,并且一直热度不减。尽管它的用户只有几千人,但其估值却已超过 1 亿美元。Clubhouse 是一款语音社交软件,被誉为是“音频 Twitter”。打开 Clubhouse,选一... ...查看全部

两个月前,一款名为 Clubhouse 的 App 开始流行于美国 VC、名人圈层,并且一直热度不减。尽管它的用户只有几千人,但其估值却已超过 1 亿美元。

Clubhouse 是一款语音社交软件,被誉为是“音频 Twitter”。

讯飞1.webp.jpg

打开 Clubhouse,选一个房间进入,人们可以查看房间里都有哪些参与者,并可以听到他们的谈话,再决定是否继续听或直接加入群聊。

如果要说它最大的规则是什么,那就是:只用语音,没有文字

近年来,移动互联网飞速发展,语音功能已开始成为 App 的标配,各种属性的音频类 App 尤其是声音社交类 App 也在悄然壮大。

红杉中国发布的《创造未来——红杉 00 后泛娱乐消费研究报告》显示,社交性、潮流性和个性化是“00 后”用户最看重的三大产品特征。因此,比图片和文字有温度,比视频更含蓄的声音社交方式,开始在他们之中风靡。

而根据艾瑞咨询的数据,2019 年中国网络音频行业市场规模为 175.8 亿元,同比增长 55.1%,预计 2020 年中国网络音频行业市场规模达 272.4 亿元。越来越多的资本和资源看到了声音背后蕴藏的更多可能性。

讯飞2.png

随着 5G 网络的逐渐普及,网络速度变得更快,以音频、视频为主要交互方式的互联网产品或将大行其道。

今年年初的疫情期间,我们已经看到各种音视频软件在在线教育、在线办公中发挥着巨大的作用。但有一个不得不面对的事实是,它们都曾因短时期内使用人数过多而崩溃。

对于音视频类互联网产品来说,保证用户使用过程中不会出现卡顿、声音效果差等问题十分重要,这就需要技术上的强力支持与保证,在这一方面能够提供“实时音视频”技术的服务商做的相当专业。

目前,在我国从事“实时音视频”技术底层搭建的服务商主要分为两大阵营。其一,是只专注“实时音视频”技术的单一产业服务商;其二,是 IM 领域已经耕耘多年,现将 IM 即时通讯及实时音视频两大业务版块交融聚合的服务商。例如,已多年稳居 IM 市场占有率第一的融云。

讯飞3.webp.jpg

北京云中融信网络科技有限公司(简称融云),是一家安全、可靠的全球互联网通信云服务商,向开发者和企业提供即时通讯和实时音视频通信云服务。虽成立时间不长,但发展迅速,成立不久就获得了 1 亿元人民币 B 轮融资。

截止目前,已有中原地产、汽车之家、融创地产、丽兹行、寺库、哈啰出行、核桃编程、易车网、编程猫以及 Castbox、Opera 等超过 30 万款海内外 App 通过融云实现了全球化的互联网通信能力。

融云现已在科大讯飞旗下人工智能全产业链综合服务平台——讯飞 AI 服务市场入驻,来一起看看,它的哪些技术能让如此之多的 App “钟情”于它?

融云IM即时通讯

许多 85 后甚至 90 后的大学记忆中都有一款即时通讯工具,飞信。在那个短信 1 毛钱一条,流量 5 块钱只有 30M 的年代,飞信免流量、免费发短信的功能,很有吸引力。作为当初中国移动即时通讯的扛鼎之作,飞信注册用户最高时达到了 5 亿,高峰时拥有高达 9000 万的活跃用户,其技术水平自不必多说。

融云的技术团队源自飞信技术团队,在即时通讯领域有着丰富的经验和强大的核心技术实力。

融云即时通讯支持多聊天模式、多消息类型、自定义界面等功能,同时支持聊天记录漫游、消息回执与召回、消息内容全文搜索、消息内容审核等功能。用户可以根据自己的需求调用相关接口,大大节约在通信能力上的研发时间和成本。

讯飞7.png

基于融云私有通信协议,可以保障消息不丢不重不乱,支持无上限用户数的群组和聊天室互动,公有云平台 150 亿条日均消息量2218 亿条日消息量峰值。融云建立了全球多数据中心,服务覆盖全球所有国家及地区(共 233 个),实时监控全球网络,基于融云分布在全球的数据中心与节点建设,向客户提供链路接入方案,持续的海外链路优化让消息发送稳定可靠,畅通无阻。

讯飞8.webp.jpg

无论是私密社交、兴趣社交还是商业沟通、系统消息等场景,融云都可以轻松应对,它也是业内唯一承诺消息可靠性 100% 的厂商。

融云RTC实时音视频

为了适应移动互联网发展和市场需求,融云在即时通讯之外还发展了实时音视频业务。融云实时音视频主要包括音视频通话、低延迟直播、音视频会议、云端录制等功能。用户可以根据自己的需求调用相关接口,大大节约在通信能力上的研发时间。

讯飞9.png

融云实时音视频具备低延迟、低成本、高流畅、高品质、部署简单、扩展灵活等技术优势。在整体网络架构上,全球分布式架构的部署让扩容时间大幅缩短,能轻松应对海量流量的激增。弱网优化策略方面,增强了抗丢包及抗网络抖动能力,音频能对抗 80% 丢包,视频能对抗 40% 丢包,延时最低可达 66ms,以保证低成本输出高性能的实时音视频能力(可以提供最高 1080P 的高清画质和最高 48KHz 的音频采样率) 。经过海量客户业务验证,融云实时音视频业务在稳定性、连通性、并发/负载等方面服务可用性达到 99.9%

融云在疫情期间还免费开放了在线医疗、在线教育及协同办公场景的通信能力,并开发了 VR 看房等新的业务场景。

最新活动推荐:

 融云年中大促活动海报.png


芥末堆专访:线上教学交付能力背后,在线音视频通信技术成刚需

科技创新融云那些事 发表了文章 • 0 个评论 • 279 次浏览 • 2020-08-03 16:43 • 来自相关话题

2020 年过半,疫情加速在线教育发展已成事实。疫情防控常态化使得线下教育场景面临着长期挑战,线上教学交付能力俨然成为教培机构的标配。伴随着线下机构转型线上探索教育 OMO,线上教学背后的技术能力开始受到重视。相比线下场景天然的临场感和及时的互动性,线上化教学... ...查看全部

2020 年过半,疫情加速在线教育发展已成事实。疫情防控常态化使得线下教育场景面临着长期挑战,线上教学交付能力俨然成为教培机构的标配。

伴随着线下机构转型线上探索教育 OMO,线上教学背后的技术能力开始受到重视。相比线下场景天然的临场感和及时的互动性,线上化教学不是仅仅利用音视频工具就足够。线下场景中的信息传递是基于面对面的沟通,如何在线上场景“重现”甚至“超越”面对面沟通的信息传递方式是关键问题。

 芥末堆1_副本.jpg

无论教学形式或场景怎样变化,教育始终是效果为王。“全民网课”的时代,教学效果的保证离不开高质量的音视频信息传递、全方位的及时互动和多维度的数据分析。然而这些对于教培机构,尤其是以教学见长的线下机构,都并不熟悉。

全球通信云服务商融云 CPO 任杰表示:“在互联网时代,教育创业者应该聚焦自己的核心业务逻辑,而不是去关心通用型能力的实现。”

为此,融云专注于互联网云通信领域,为包括在线教育在内的互联网行业提供“IM 即时通讯+实时音视频+推送”的一体化解决方案,凭借技术积累实现安全、可靠、实时的即时通讯及音视频服务,满足各类教育场景的通用型和定制化的信息交互需求。

“IM 即时通讯+实时音视频+推送”,在线教育一体化解决方案

据任杰介绍,融云最初选择互联网云通信领域,就是看到互联网发展的趋势和通信需求的刚性。时至今日,伴随着移动互联网的发展,云通信已经成为标配能力。融云也从最初的“IM 即时通讯”业务,发展到“用一套 SDK,解决所有通信场景”,涵盖“IM 即时通讯+实时音视频+推送”的一体化解决方案。

任杰告诉芥末堆,融云整体的服务形式是以客户端 SDK 的方式体现,App 只要通过简单的几行代码就可以完成通信 SDK 集成,实现通信和社交能力。此外,融云还提供实时音视频相关功能和整体解决方案。

芥末堆2_副本.jpg 

去年 11 月 30 日,融云正式宣布已完成数亿元 C 轮融资

技术的发展背后是需求的演变。从图文时代到音视频时代,信息传播的媒介和方式都在发生着巨大变化。与之相应的是互联网企业对音视频通信技术的更高要求。任杰表示,如何打造一流的实时音视频服务,赋能开发者追赶音视频领域的技术红利,是融云始终关注的。

而作为互联网通信的主要应用场景之一,在线教育的内容以视频直播为主要形式。相比娱乐内容,教育内容的严肃性和互动性使得低延迟的高质量音视频传输成为刚需。任杰告诉芥末堆,以 PC 或平板为设备载体的在线教育对画质的要求,要远远高于移动端。

 芥末堆3_副本.jpg

如果将内容看作在线教育的产品核心,那么音视频质量可以看作在线教育的技术核心。相比其他内容行业,同样面对卡顿或延迟,在线教育用户的容忍度更低。任杰介绍,融云在今年 5 月份对融云实时音视频业务进行全面升级,针对开发者最关心的音视频通话质量,融云使用的是 WebRTC 技术,完全可以满足在线教育等场景中低延迟、强互动的需求。

据了解,在视频方面,融云目前能够提供最高 1080P 的分辨率,画面纤毫毕现,尤其适合特殊高清场景,如在线教育领域中的双师课堂场景的大屏直播。同时,融云还提供各种高中低分辨率供不同业务场景调用,实现画面和流量平衡,帧率最高支持 30FPS,可以匹配不同设备端的教学需求。

在音频方面,融云采用最高音频采样率 48KHz,可真实还原对端声音,高清音质。任杰介绍,融云目前可以提供高清音乐模式,针对乐器的高频音段和弱音音阶进行优化处理,高度还原音乐细节,带给用户更贴近线下场景的体验。在音频方面,融云完全可以满足素质教育领域的音乐培训的需求。

云通信能力之后,融云要延伸到更多在线教育场景

在沟通中,任杰始终强调融云在做的是底层技术能力的支持。但在介绍在线教育场景的应用时,芥末堆发现针对不同角色、不同班型、不同细分赛道的差异化需求,融云在技术上都有设计并满足。

提到融云通信云能力的特点,任杰总结到:性能稳定、全平台覆盖、全方位互动和全球部署。

 芥末堆4_副本.jpg

性能稳定是融云的核心优势。依托技术优势,融云可以为大型直播课程提供无用户上限和消息量限制的聊天室服务,亿级消息并发即时到达,互动稳定流畅、延迟低。任杰表示,在弱网优化策略上,融云的核心策略是能够迅速预估宽带,并采用降低码率来确保以声音与流畅优先的原则,保证用户最优体验。

在覆盖平台方面,融云实时音视频 SDK 可覆盖全平台,包括 iOS、Android、Web、Windows、macOS、Linux、Electron 等,并全面适配市场主流的各类终端设备,包括在智能手表、智能音箱、智能门禁等多种智能硬件设备中实现平台间通信,全面保障融云实时音视频在各类终端上的良好应用。

在全方位互动方面,任杰介绍,融云支持 1080P 高清视频,视频窗口放大缩小可切换不同分辨率;具备互动白板功能,支持上传常见办公文档,实时进行课堂演示;一对一、小班课、大班课、双师课等不同班型的实时直播、举手答疑和视频回看都能实现。他特意强调,为保障在线课程可 100% 回看,融云是采用两条线路双向录制的。

在全球部署方面,融云拥有覆盖全球的通信加速网络,全球多个数据中心,数千个加速点,触达全世界 233 个国家和地区,能够帮助教育企业实现全球范围内的高质量教学。

提到融云在在线教育领域的探索,任杰表示,教育的未来趋势一定是线上,但这离不开技术的发展和应用。融云后续将继续利用技术优势,持续在降低高质量音视频网络流量占用的方向上发力。

此外,将技术应用延伸到教育的各个场景也是融云的发力点之一。除了今年爆发的在线 K12 领域外,素质教育、在线考试等领域也亟待技术的重构。任杰以音乐教育为例,融云为管乐培训机构大雅乐盟做了针对性的音频通信改善,给出管乐培训的线上教育解决方案。而后续,钢琴、舞蹈等线上教学领域也是融云将会深化去探索的场景。

最新活动推荐:

融云年中大促活动海报.png

实时音视频选型 开发者应该避开哪些坑?

IM即时通讯融云那些事 发表了文章 • 0 个评论 • 220 次浏览 • 2020-07-24 15:19 • 来自相关话题

实时音视频技术的专业度和复杂度都很高,通过 PaaS 服务商来集成实时音视频,快速开发 App,是时下开发者的优先选择。所选 RTC 是否好用易用、契合所需场景,将直接影响项目开发进度和后期运维成本。开发者需要... ...查看全部

实时音视频技术的专业度和复杂度都很高,通过 PaaS 服务商来集成实时音视频,快速开发 App,是时下开发者的优先选择。所选 RTC 是否好用易用、契合所需场景,将直接影响项目开发进度和后期运维成本。

开发者需要了解实时音视频技术选型中要避开的坑点,以便提高开发集成效率。具体来说,以下四个方面要综合考虑。

一、实时音视频与 IM 能力不宜分散

几乎 100% 的实时音视频在线应用都有文字/语音消息、文件传输、图片显示等 IM 需求。

目前市场上 PaaS 服务商这两方面能力强弱不一:有的大厂虽然两方面能力都提供,但不能确保两种能力同样高质量;有的专业 RTC 厂商,只能提供 RTC 能力,IM 能力还得由第三方专业服务商提供。

这样,便迫使开发者在集成过程中不得不分别选择服务商。当实时音视频与 IM 质量不稳定时,需要逐一协调各个服务商,逐一排查问题,无形中增加了后期的运营成本。其实,IM 和音视频在很多场景下有耦合,建议开发者在选型一开始就要考虑具有 RTC+IM 双重高保障能力的通信云厂商,尽量“用一套 SDK,解决所有通信场景”。

任杰总2.png

对开发者来说两项功能同时开发,开发包相对比较小;如果前期只用到了 IM,没有用到 RTC,那么只需要学习 IM 方面的开发文档就可以了,一旦有了 RTC 需求,再去学习 RTC文档,开发者只需接入相关接口,快速与 IM 能力做对接和匹配,即可完成两类功能在 App 生命周期里的全覆盖。

除了开发上的易快速上手外,选择“IM+RTC+推送”整合的解决方案,开发者还可以享受一致的网络架构,提高传输的效率和质量,获得一致的服务保障。例如,融云近期升级了实时音视频能力,RTC的通信信令是复用 IM 信令通道,可以确保消息 100% 的连通率和到达率,使底层的通信优势发挥到最大。

二、延时、卡顿、抖动的质量问题要解决好

通过调研发现,用户最不能接受实时音视频的三个质量问题是延时、卡顿、抖动。

低延时要靠两个方面解决,一个是传输协议,一个是优化整体传输环节。实时音视频的主流传输协议有 RTMP 和 UDP 两种,一种支持 CDN 技术,一种支持 WebRTC 技术,相对来说,CDN 技术延时性在 3-5 秒,WebRTC 可以在几百毫秒以内,现在很多厂商可以同时支持这两种技术,分别适用于不同的场景。

整体传输环节中,采集/渲染、编解码/网络往返都会有一定的延时,有些是硬件的物理延迟,需要靠 5G 这样底层网络技术的提升,或者布更多的数据中心、边缘结点,便于就近接入;有些要针对实际场景,在具体形态上做一些权衡,比如在处理粒度上粗细的考虑,越细的粒度传输的数据包相对较大,延迟也会更高。

当音视频出现卡顿时,有一个视频流畅优先的原则。我们通过降低一些码率和帧率,即使画面模糊一点,也要让用户视觉上是流畅不卡顿的。这样在选型时候,要考虑几个方面:一个是优化低码率下的视频清晰度;二是要有带宽估算能力,当预判到这个带宽没法承受高清晰视频传输时,自动转化成低码率并通过优化算法,使低码率视频清晰度能媲美高清视频。

音视频弱网优势_副本.png


另外,数据包通常会以错误的顺序到达,从而产生抖动相关问题,或者直接丢失,造成音视频空白。谷歌一份资料显示,视频聊天应用 Duo 99% 的通话都有数据包丢失、过度抖动或网络延迟情况。20% 的通话丢失了超过 3% 的音频,10% 的通话丢包率超过 8%,也就是说每次通话都有很多音频需要替换。

处理上述问题,很多厂商会采用抗丢包及抗网络抖动能力的 NACK(丢包重传)、FEC(前向纠错)、自适应带宽调整(动态调整码)、接收端 Jitter Buffer(媒体流平稳)等各种机制,有些是组合使用,有些是单独使用,开发者在选型前一定要做到深入了解。

  • 拥有全球通信和场景化能力

    刚才谈到低延时、抗丢包的解决策略,有些是与网络接入路径长短直接相关的。比如中美两地的音视频连接,没有全球通信网络支持、数据中心和节点布局的厂商是提供不了服务的。开发者选型开发前,就要考虑到自己业务的所属范围。   

    选择全球化服务的云厂商,除了看数据中心和节点分布外,还要仔细考察全球网络布局的品质,简单说,有的厂商提供了全球网络优化能力,中美之间的音视频连接在未优化前要经过 100 多跳,而优化后仅 6 跳就能完成连通。这是由于,这些厂商拥有自有的路径最优算法,通过智能路由就近接入,即使在异国/地网络环境较差的情况下,仍然能够及时切换到更好的线路上去。比如融云拥有全球优化加速网络,实时音视频通话可做到全球端到端延时小于 400ms,最低延时 66ms,保障端到端之间延迟无感知的实时互动。

在场景化能力上,实际上相比 IM,实时音视频更加通道化,在各个场景中复用的程度也相对较高,能力也更基础。优秀的 PaaS 厂商会按场景提供不同的 Demo,音视频技术的升级也针对解决更多的应用场景去优化,便于开发者拿来即用,这种方式对入门级的开发者都十分友好。各种 API 接口相对独立,开发者只需关注和使用所需要的 SDK,就可以实现想要的场景,大大降低集成开发的难度。

四、开发者服务足够完善

在一些社区中,我们常常会看到一些技术文档下,开发者提出问题而没有回复。开发者为提高开发效率,越来越倾向于自助完成工作,因此,开发文档是否易懂,Demo 是否易用,都显得十分重要。

另外,工单回复的速度,微信群、社区的值守和响应程度等都能反映 PaaS 厂商服务意识的强弱。通常来说,7×24 小时技术支持服务,1 小时工单快速回复、快速远程接入、快速恢复的故障应急响应机制,这些都是对开发者很完善的服务支持。

有些厂商还会提供特色的质量监控服务,比如融云“北极星”的质量问题排查平台,通过可视化图表,快速定位卡顿位置,实时统计丢包率,使开发者可以自助排查每一次音视频通话过程中的丢包率、网络带宽等通信技术参数。可以直接定位用户问题,提高排查效率,提升用户体验。

点击阅读原文

最新活动推荐:

融云年中大促活动海报.png


LiveVideoStackCon 深圳站:融云解析 WebRTC 低延迟直播技术

WebRTC融云那些事 发表了文章 • 0 个评论 • 440 次浏览 • 2020-06-16 18:36 • 来自相关话题

“基于 WebRTC 的低延迟直播将会是未来直播行业的主流解决方案!”这是融云联合创始人兼CTO 杨攀在 8 月 LiveVideoStackCon 2019 音视频技术大会北京站上对于未来行业趋势的判断。仅仅 4 个月之后,当大会首次落户有“中国硅谷”之称的... ...查看全部

“基于 WebRTC 的低延迟直播将会是未来直播行业的主流解决方案!”这是融云联合创始人兼CTO 杨攀在 8 月 LiveVideoStackCon 2019 音视频技术大会北京站上对于未来行业趋势的判断。仅仅 4 个月之后,当大会首次落户有“中国硅谷”之称的深圳时,融云的另一位技术专家,首席架构师李淼就“基于 WebRTC 的低延迟直播方案”进行了深入的技术分享。

12 月 13-14 日,LiveVideoStackCon 音视频技术大会在深圳举办,大会聚焦音视频、图像、AI 等技术的最新探索与应用实践,覆盖社交、游戏、直播、智能设备等行业领域,面向开发者分享技术创新与最佳实践。本次大会,聚集了数十名海内外技术专家和上千名开发者围绕前沿技术发展进行探讨。


融云首席架构师李淼
随着我国 5G 正式走向商用,直播行业在获得更多发展机遇的同时,也对直播技术提出了新的挑战。传统直播解决方案如果无法解决技术层面导致的延时问题,那么这一弊病将在 5G 的高速网络环境下被无限放大,这也进一步促使了低延迟音视频直播技术方案的演化。对此,李淼结合 WebRTC 的低延迟特性,在现场展示了融云 WebRTC 直播场景的构建全过程及服务架构设计,并向开发者们分享了技术实践细节,希望通过新技术的应用来解决视频直播的延时问题。

为什么要选用 WebRTC 来做直播?李淼表示,相较于传统的直播解决方案,WebRTC 拥有着不可比拟的三大优势。首先是低延时,让直播用户可以享受低延时的观看体验。目前直播行业中绝大多数产品是基于 RTMP、HLS、HDL 方式构建的,即使在不考虑网络链路的情况下,也会产生秒级的延迟,而 WebRTC 则天生具备低延迟的优势,使用 WebRTC 直播可有效将延迟降低至 200ms 以下。

其次是流量消耗小。基于 UDP 传输的 WebRTC 相比基于 TCP 传输的 RTMP 等协议,由于 UDP 协议内容较 TCP 小,且数据包是基于 NACK 进行传输等特点,对于流量的使用也有明显的降低。对于开发者和直播企业而言,流量消耗大幅削减,成本也因此可以得到有效的控制。

而最重要的优势在于 WebRTC 技术方案可以使主播端与观众端保持一致。当主播端使用  WebRTC 进行推流时,主播端与观众端保持一致,可以减少开发的编码量,对于团队人员的占用和后期对于代码的维护,都能保证最低的资源消耗。

在 LiveVideoStackCon 现场,李淼向开发者讲解了如何通过 WebRTC 完成直播场景构建的全过程,并对于 WebRTC 直播的技术细节一一进行了详细解读。李淼表示,使用 WebRTC 直播方案,MCU 服务器的设计至关重要。一方面 MCU 可以按需进行编解码,另一方面需要以房间号进行聚合,记录每台MCU的状态并按最小资源分配新房间,通过这种设计来减少 WebRTC 直播方案的资源消耗。


WebRTC 直播发布订阅流程
当然,对于很多开发者而言,实际的生产环境中仍面临着如何做到秒开视频、降低 MCU 带宽压力以及避免流量风暴等难题,李淼从 GOP 缓存结构和 GOP 控制策略两个层面进行了分析。以解决首帧卡顿延迟为例,直播数据在客户端与 Media Sever 进行交互之后,通常会对 SPS 和 I 帧进行正常下发,但是在随后的 P 帧或 B 帧的下发阶段,融云会采用 1.2 倍速下发的方式进行,直至所有数据包与 MCU 端推包进程同步,这就将直播延迟降至了最低。

此外,李淼还指出,客户端的设计必须考虑就近接入,且支持多链路选择,数据中心间同源音视频只有一路级联;同时还可以利用 IaaS 层的能力,进行中心间级联链路的优化。遵循这些直播网络设计原则都可以有效地降低直播延迟。

在分享的最后,李淼表示在 5G 时代,直播、短视频等内容传播形态将迎来新一轮技术升级,用户体验将成为行业洗牌的关键,此次将 WebRTC 低延迟直播的设计理念和技术要点与开发者和行业人士们一同分享,希望能够给业界带来一些启发和思考。作为互联网通信云行业的技术领导者,融云也将持续优化实时音视频技术和场景化解决方案,助力音视频直播行业在 5G 时代的创新发展。

融云助力嘉和海森 以通信云技术服务在线医疗“战疫”到底

科技创新融云那些事 发表了文章 • 0 个评论 • 97 次浏览 • 2020-06-16 18:30 • 来自相关话题

这些天,新型冠状病毒肺炎疫情一直牵动着全国人民的心,与疫情相关的健康咨询需求也明显呈现激增趋势。然而,扎堆去发热门诊咨询问诊,在增加线下门诊压力的同时也极有可能会引发交叉感染。为平衡医疗资源,疏导人民群众进行针对性就医,提升问诊效率,安全便捷的线上咨询成为近期... ...查看全部

这些天,新型冠状病毒肺炎疫情一直牵动着全国人民的心,与疫情相关的健康咨询需求也明显呈现激增趋势。然而,扎堆去发热门诊咨询问诊,在增加线下门诊压力的同时也极有可能会引发交叉感染。为平衡医疗资源,疏导人民群众进行针对性就医,提升问诊效率,安全便捷的线上咨询成为近期就医形式的首选。

在这个全民抗疫的时刻,医疗软件行业知名企业嘉和海森与互联网通信云服务商融云联合多家医疗机构开展了网上免费健康咨询服务,包括北京朝阳医院、通辽市医院等全国各地的众多医疗机构纷纷通过嘉和海森互联网医疗软件开展网上咨询。所有在线咨询的患者,可通过文字、图片、音频和视频进行网络咨询,与医生进行实时互动,在线获取医生的健康指导,实现足不出户在线问医。


融云助力在线医疗App
以朝阳医院上线的朝阳健康云APP为例。在当患者进行健康咨询时,通常问的都是一些偏基础性的问题,医患之间可以通过图文、语音消息的方式进行互动。而在网络问诊中,考虑到需要对患者进行更详尽的检查,医患之间需要通过音频+视频的形式进行实时互动,同时通过图片+文字进行补充沟通。除了医患之间的在线问诊,App还可以让身处不同地区的医疗工作者,能够在同一群组中通过图文+语音的形式远程对患者的病情进行实时讨论,合理地平衡和调用各地医疗资源。


医生通过音视频在线问诊
毋庸置疑,在线问诊最需要的就是稳定可靠的互联网通信能力。无论是医疗行业的高精专属性还是考虑到病患问诊时紧张心理,医疗类App对于问诊时音视频画面和声音的稳定流畅性、图文交互的即时性都有着极高的要求。据了解,在嘉和海森医疗App中,所有医生与问诊者之间的图文、语音、视频的交互,都是通过融云互联网通信云实现的。

作为一家以技术立命的互联网科技公司,融云致力于为开发者和企业提供 IM即时通讯和实时音视频通信云服务。融云提供的 PaaS 层服务,可以通过 SDK 的形式集成到应用中,开发者可以根据自身业务的需要调用不同功能,自由组合下载。同时融云还针对在线医疗等不同行业属性,针对性地推出场景化解决方案,让开发者拿来即用,降低集成难度和开发时间。

在医疗资源紧张的当下,融云坚信“科技向善”,以安全稳定的即时通讯和实时音视频技术驰援互联网医疗。“足不出户,网络问医”这一模式不仅能有效分流患者,缓解门诊压力,同时通过在线问诊,医师可通过症状初步判断其发热原因,减少非新型肺炎患者前往医院就诊的交叉感染机率。

在此,融云也承诺从即日起至疫情结束,针对在线医疗场景不收取任何费用,向各在线健康咨询平台、政府疫情防控平台、互联网医院等平台免费开放即时通讯和实时音视频通信云服务。此外,融云也面向企业、机构免费开放在线教育以及协同办公场景下的实时音视频服务。

目前,融云已经开通了 7*24 小时咨询热线 13161856839,欢迎有需求的政府部门、医疗机构、企业机关与我们联系,待沟通确认符合相关场景需求后,我们将以最快的速度进行技术对接,助力政府和医疗机构应对此次疫情,为这场“战疫”献出一份绵薄之力。

疫情期间远程协同办公成刚需 融云助力拓维打造视频会议系

科技创新融云那些事 发表了文章 • 0 个评论 • 90 次浏览 • 2020-06-16 18:29 • 来自相关话题

一场新型冠状病毒疫情的突袭,严重影响了社会各领域的正常运行,也使得春节后的企业正常复工成为难题。为了减少人员流动和聚集,防止交叉感染,大量企业响应国家的号召,选择通过远程协同办公的形式来维持企业正常运营。如果说此前远程协同办公只是企业办公的一项补充条件,那么在... ...查看全部

一场新型冠状病毒疫情的突袭,严重影响了社会各领域的正常运行,也使得春节后的企业正常复工成为难题。

为了减少人员流动和聚集,防止交叉感染,大量企业响应国家的号召,选择通过远程协同办公的形式来维持企业正常运营。如果说此前远程协同办公只是企业办公的一项补充条件,那么在疫情期间显然已经成为一种用户刚需。据媒体报道,仅在 2 月 3 日当天,全国就有上千万企业、近 2 亿人开启远程办公模式。

为了帮助更多企业实现稳定高效的远程协同办公,拓维信息积极响应国家号召,免费向企业提供1个月的远程视频会议服务,同时对于原有客户免费增加用户许可,保证企业全员远程办公的需求,直到疫情结束。在这段视频会议流量大规模爆发的时期,拓维无纸化会议系统稳定可靠地支持了众多企业远程会议同时进行,有效地为企业在疫情期间正常运行提供了保障。


拓维无纸化会议系统
据了解,拓维无纸化会议系统可满足远程资料共享、远程视频通话、会议资料安全等会议需求,且支持多个会议同时召开,单个会议场景可支持数百人同时在线。而拓维无纸化会议系统之所以在用户量激增的情况下仍能保持平台的持续稳定运行,与其底层通信能力服务商融云多年来在实时音视频领域的技术积累和稳定服务密不可分。

作为国内领先的互联网通信云服务商,融云致力于为开发者和企业提供 IM 即时通讯和实时音视频通信云服务。融云 CPO 任杰介绍称,融云在实时音视频领域拥有多年研发经验,各项技术指标保持市场领先水平,支持流畅稳定的一对一、多对多音视频通话、服务端录像,同时通过分层编码、大小流设计、带宽估计以及多服务器优化等方面来实现对更多路的支撑。目前融云在视频会议方面可以做到同时支持 14 路,纯音频则能做到同时支持 25 路,优于市场上一般软件的能力表现。此外融云还在不断强化技术支持能力,让开发者 30 分钟即可快速集成音视频能力,加速产品的上线效率。

同时,在这次疫情期间,视频会议系统很容易出现在线人数激增、海量消息并发的峰值情况,往往因难以准确预估流量,错误部署服务器导致系统崩溃,非常考验系统架构稳定性。融云通过“锦囊”服务可以在前期以最佳实践经验安排专家团队协助客户进行服务器部署,当遇大规模流量涌入的峰值期时,运维人员和技术人员通过实时监控,可以动态调整服务器部署,针对可能的并发问题量身定制相应的保障方案,确保在疫情期间企业协同办公的正常运行。

在此,融云也向社会承诺,从即日起至疫情结束,免费向企业、机构提供协同办公场景下的实时音视频服务,同时免费开放在线医疗场景下的 IM 及实时音视频服务和在线教育场景下的实时音视频服务。目前,融云已经开通了 7*24 小时咨询热线 13161856839,欢迎有需求的企业、机构与我们联系,待沟通确认符合相关场景需求后,我们将以最快的速度进行技术对接,帮助更多企业解决疫情期间的协同办公难题,实现办公方式的迭代升级,为坚决打赢这场疫情防控阻击战提供更多助力。

实时音视频交流