从0打造音视频直播系统
李超
前新东方音视频直播技术专家,前沪江音视频架构师
立即订阅
2827 人已学习
课程目录
已完结 40 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 5G的到来将会为音视频插上飞翔的翅膀
免费
WebRTC 1对1通话 (23讲)
01 | 原来通过浏览器访问摄像头这么容易
02 | 如何通过WebRTC进行音视频设备检测呢?
03 | 如何使用浏览器给自己拍照呢?
04 | 可以把采集到的音视频数据录制下来吗?
05 | 原来浏览器还能抓取桌面?
06 | WebRTC中的RTP及RTCP详解
07 | 你竟然不知道SDP?它可是WebRTC的驱动核心!
08 | 有话好商量,论媒体协商
09 | 让我们揭开WebRTC建立连接的神秘面纱
10 | WebRTC NAT穿越原理
11 | 如何通过Node.js实现一套最简单的信令系统?
12 | RTCPeerConnection:音视频实时通讯的核心
13 | 在WebRTC中如何控制传输速率呢?
14 | 如何打开/关闭音视频?
15 | WebRTC中的数据统计原来这么强大(上)
16 | WebRTC中的数据统计原来这么强大(下)
17 | 如何使用Canvas绘制统计图表(上)?
18 | 如何使用Canvas绘制统计图表(下)?
19 | WebRTC能不能进行文本聊天呢?
20 | 原来WebRTC还可以实时传输文件?
21 | 如何保证数据传输的安全(上)?
22 | 如何保证数据传输的安全(下)?
23 | 实战演练:通过WebRTC实现一个1对1音视频实时直播系统
WebRTC多人音视频实时通话 (7讲)
24 | 多人音视频实时通讯是怎样的架构?
25 | 那些常见的流媒体服务器,你该选择谁?
26 | 为什么编译Medooze Server这么难?
27 | 让我们一起探索Medooze的具体实现吧(上)
28 | 让我们一起探索Medooze的具体实现吧(下)
29 | 如何使用Medooze 实现多方视频会议?
30 | 实战演练:通过WebRTC实现多人音视频实时互动直播系统
支持上万人同时在线的直播系统 (8讲)
31 | 一对多直播系统RTMP/HLS,你该选哪个?
32 | HLS:实现一对多直播系统的必备协议
33 | FLV:适合录制的多媒体格式
34 | 如何使用Nginx搭建最简单的直播服务器?
35 | 如何构建云端一对多直播系统?
36 | 如何使用 flv.js 播放 FLV 多媒体文件呢?
37 | 如何使用 video.js 播放多媒体文件?
38 | 实战推演:带你实现一个支持万人同时在线的直播系统
结束语 (1讲)
结束语 | 路漫漫其修远兮,吾将上下而求索
从0打造音视频直播系统
登录|注册

05 | 原来浏览器还能抓取桌面?

李超 2019-07-25
无论是做音视频会议,还是做远程教育,共享桌面都是一个必备功能。如果说在 PC 或 Mac 端写个共享桌面程序你不会有太多感受,但通过浏览器也可以共享桌面是不是觉得就有些神奇了呢?
WebRTC 的愿景就是要让这些看似神奇的事情,不知不觉地发生在我们身边。
你可以想象一下,假如浏览器有了共享桌面功能,这会使得浏览器有更广阔的应用空间,一个最直接的例子就是我们可以直接通过浏览器进行远程办公、远程协助等工作,而不用再下载共享桌面的应用了,这大大提高了我们的工作效率。

在 WebRTC 处理过程中的位置

在正式进行主题之前,我们还是来看看本文在整个 WebRTC 处理过程中的位置,如下图所示:
WebRTC 处理过程图
没错,它仍然属于音视频采集的范畴,但是这次采集的不是音视频数据而是桌面。不过这也没什么关系,桌面也可以当作一种特殊的视频数据来看待

共享桌面的基本原理

共享桌面的基本原理其实非常简单,我们可以分“两头”来说明:
对于共享者,每秒钟抓取多次屏幕(可以是 3 次、5 次等),每次抓取的屏幕都与上一次抓取的屏幕做比较,取它们的差值,然后对差值进行压缩;如果是第一次抓屏或切幕的情况,即本次抓取的屏幕与上一次抓取屏幕的变化率超过 80% 时,就做全屏的帧内压缩,其过程与 JPEG 图像压缩类似(有兴趣的可以自行学习)。最后再将压缩后的数据通过传输模块传送到观看端;数据到达观看端后,再进行解码,这样即可还原出整幅图片并显示出来。
对于远程控制端,当用户通过鼠标点击共享桌面的某个位置时,会首先计算出鼠标实际点击的位置,然后将其作为参数,通过信令发送给共享端。共享端收到信令后,会模拟本地鼠标,即调用相关的 API,完成最终的操作。一般情况下,当操作完成后,共享端桌面也发生了一些变化,此时就又回到上面共享者的流程了,我就不再赘述了。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《从0打造音视频直播系统》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(20)

  • 月光伴奏
    老师!文中(不能在采集桌面的同时采集音频),好像是能同时采集的,弹出了一个分享音频的选择框,勾上好像就能采集音频了

    作者回复: 不能用同一个 stream进行采集

    2019-11-22
    1
  • 恋着歌
    如果是解决网络引起的模糊,那么可能就要牺牲实时性,提高延迟,就像我们看视频时卡顿要缓冲一下。

    具体的解决方法是:
    1,解决网络问题😂
    2,关闭 Web RTC 的自适应码率,frameRate,width,height 设置固定值或高范围值。

    作者回复: 赞!

    2019-07-25
    1
  • 技术乞丐
    因为带宽不够,而视频模糊一点也没事,不影响理解
    2019-11-29
  • 张旭
    是否有办法能主动触发桌面流结束,现在做桌面采集后gpu升高,机器变卡,手动切换成采集摄像头内容,采集桌面流的进程似乎没有终止,机器依然很卡顿

    作者回复: 的到你的桌面的track, 然后调用close 方法。

    2019-11-26
  • 月光伴奏
    老师,也就是说webrtc这个分享时没办法实现分享系统声音的嘛?分享游戏听不到声音的嘛?

    作者回复: 可以分别采集,然后入到同一个stream中。

    2019-11-15
    1
  • 春风吹又生
    老师,webrtc能否录制系统内声音呢?

    作者回复: 你是指的播放了的多媒体文件的声音吧?是可以的!

    2019-10-27
    1
  • 初音韶歌
    老师,我的截取桌面播放的视频内容是嵌套的是怎么回事呢,就是一个桌面内容里又有n层小的内容

    作者回复: 你切换一下窗口就好了,你可以想一下为什么会这样哈

    2019-08-26
  • ZeroIce
    老师,录制的像素太低怎么办?

    作者回复: getUserMedia API 中调整一下分辩率就好了!

    2019-08-04
  • ZeroIce
    交作业:https://codepen.io/vicksiyi/pen/oKGJzL

    // 判断是否为PC端的Chrome
    let ifMachine = (() => {
    var ua = navigator.userAgent,
    isWindowsPhone = /(?:Windows Phone)/.test(ua),
    isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,
    isAndroid = /(?:Android)/.test(ua),
    isFireFox = /(?:Firefox)/.test(ua),
    isChrome = /(?:Chrome|CriOS)/.test(ua),
    isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),
    isPhone = /(?:iPhone)/.test(ua) && !isTablet,
    isPc = !isPhone && !isAndroid && !isSymbian;
    return {
    isChrome: isChrome,
    isPc: isPc
    };
    })();


    作者回复: 赞!

    2019-08-04
  • 相见恨晚
    大佬,4章以后的代码啥时候能提供,应该每章都有demo吧,再多的理论都不如代码来得实在。

    作者回复: 第五篇文章的demo这两天就会有

    2019-08-03
  • ZeroIce
    老师,课程大概什么时候可以完结?

    作者回复: 9月份

    2019-08-03
  • tommy_zhang
    老师,弹出的共享屏幕界面上内容能自定义吗?比如上面的文字"xxx想要共享共享您屏幕上的内容"

    作者回复: 不能!

    2019-07-29
  • LongXiaJun
    打卡

    作者回复: 打卡!打卡!

    2019-07-26
  • K
    //第二部分

    function playbackVideo(){
        blob = new Blob(buffer, {type:'video/webm'});
        rePlayVideo.src = window.URL.createObjectURL(blob);
        rePlayVideo.srcObject = null;
        rePlayVideo.constructor = true;
        rePlayVideo.play();
    }

    function download(){
        blob = new Blob(buffer, {type:'video/webm'});
        let a = document.createElement('a');
        a.href = window.URL.createObjectURL(blob);
        a.download = 'download.webm';
        a.click();
        a.remove();
    }

    recordingButton.addEventListener('click', function () {
        recordingVideo();
    });

    playbackButton.addEventListener('click', function () {
        playbackVideo();
    });

    downloadButton.addEventListener('click', function () {
        download();
    });

    init();
    2019-07-25
  • K
    //第一部分代码

    let videoTypes = "video/webm\;codecs=vp8";
    let userMediaSetting = { video: true };
    let playVideo = document.querySelector('video#play');
    let rePlayVideo = document.querySelector('video#replay');
    let recordingButton = document.querySelector('button#recording');
    let playbackButton = document.querySelector('button#playback');
    let downloadButton = document.querySelector('button#download');
    let buffer;
    let mediaRecorder;
    let blob;

    function init() {
        if (!(mediaSupport() && mediaRecorderSupport()))
            return;
        getVideo();
    }

    function mediaSupport() {
        if (!navigator.mediaDevices) {
            console.log('不支持 mediaDevices');
            return false;
        }
        console.log('支持 mediaDevices');
        return true;
    }

    function mediaRecorderSupport() {
        if (!MediaRecorder.isTypeSupported(videoTypes)) {
            console.log(`不支持 ${videoTypes}`);
            return false;
        }
        console.log(`支持 ${videoTypes}`);
        return true;
    }

    function getVideo() {
        navigator.mediaDevices.getDisplayMedia(userMediaSetting)
            .then(handleGetVideo)
            .catch(handleGetVideoError)
    }

    function handleGetVideo(mediaStream) {
        playVideo.srcObject = mediaStream;
    }

    function handleGetVideoError(err) {
        console.log(`获取视频输入出错: ${err.name} : ${err.message}`)
    }

    function recordingVideo() {
        if ( typeof(mediaRecorder) != "undefined" && mediaRecorder.state === 'recording') {
            console.log('已开始录制,请勿重复录制');
            return;
        }
        console.log('开始录制');
        buffer = [];
        let options = {
            mimeType : videoTypes
        };

        mediaRecorder = new MediaRecorder(playVideo.srcObject, options);
        mediaRecorder.ondataavailable = handleDataAvailable;
        mediaRecorder.start(10);
    }

    function handleDataAvailable(d) {
        if (d && d.data && d.data.size > 0) {
            buffer.push(d.data);
        }
    }

    作者回复: 赞!

    2019-07-25
  • Jason
    思考题:我猜一下,wenrtc底层主要使用udp传输,网络不好的情况下,会有丢包。所以会有模糊的现象发生。

    作者回复: 如果是自适应码率,当发生丢包时,编码器会降低码率,当分辨率不变,码率变低时,就是模糊哈!

    2019-07-25
  • 漫慢
    使用的chrome提示不支持navigator.mediaDevices.getDisplayMedia API,请问该如何解决?

    作者回复: 是使用的https吗?

    2019-07-25
  • xiao豪
    简单demo

    var deskVideo = document.querySelector("video#deskVideo");
    function getDeskStream(stream){
        deskVideo.srcObject = stream;
    }

    // 对采集的数据做一些限制
    var constraints = {
        video : {
                width: 1280,
                height: 720,
                frameRate:15,
        },
        audio : false
    }

    function handleError(err){
        console.log('getUserMedia error:', err);
    }

    function IsPC() {
        var userAgentInfo = navigator.userAgent;
        var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPod", "iPad"];
        var flag = true;
        for (var i = 0; i < Agents.length; i++) {
            if (userAgentInfo.indexOf(Agents[i]) > 0) {
                flag = false;
                break;
            }
        }
        return flag;
    };
    // 抓取桌面
    function shareDesktop(){
        // 只有在 PC 下才能抓取桌面
        if(IsPC()){
                // 开始捕获桌面数据
                navigator.mediaDevices.getDisplayMedia(constraints)
                        .then(getDeskStream)
                        .catch(handleError);
                return true;
        }
        return false;
    }

    var buffer;
    // 创建录制对象
    var mediaRecorder;

    function handleDataAvailable(e){
            if(e && e.data && e.data.size > 0){
                    buffer.push(e.data);
            }
    }

    function startRecord(){
            // 定义一个数组,用于缓存桌面数据,最终将数据存储到文件中
            buffer = [];

            var options = {
                    mimeType: 'video/webm;codecs=vp8'
            }

            if(!MediaRecorder.isTypeSupported(options.mimeType)){
                    console.error(`${options.mimeType} is not supported!`);
                    return;
            }

            try{
                    // 创建录制对象,用于将桌面数据录制下来
                    mediaRecorder = new MediaRecorder(deskVideo.srcObject, options);
            }catch(e){
                    console.error('Failed to create MediaRecorder:', e);
                    return;
            }
            // 当捕获到桌面数据后,该事件触发
            mediaRecorder.ondataavailable = handleDataAvailable;
            mediaRecorder.start(10);
    }
    document.querySelector("button#record").onclick = function (){
        startRecord();
    }

    shareDesktop()

    作者回复: 赞!

    2019-07-25
  • 许童童
    老师能否给一个能够完整跑起来的Demo。

    作者回复: 这个主题结束之后,会有一个完整的 demo

    2019-07-25
  • 流浪剑客
    看完打卡

    作者回复: 打卡! 打卡!

    2019-07-25
收起评论
20
返回
顶部