metroluffy / blog

用于记录平时开发中所遇到问题的解决方法、笔记等
9 stars 1 forks source link

如何在Web上进行直播 #21

Open metroluffy opened 5 years ago

metroluffy commented 5 years ago

直播目前而言并非是一个很陌生的概念,也有很多应用场景,如在线教育。 在线教育的场景中讲师可以通过直播视频同时为用户讲课,将教室搬到了没有地域束缚的线上,同时在线听课人数可以千百倍于传统课堂。直播模式用户接受度强,将传统课堂上的反馈、交互、答疑搬到线上。 推流一般会选择更稳定的客户端进行直播,例如OBS等软件。但这类软件使用起来相对比较复杂,对硬件设备要求高,部分客户用起来成本较大,因此可以使用浏览器进行直播(Web 直播),在直播外亦可以将PPT课件使用、讨论、答疑这样的交互搬到浏览器内。目前能够进行推流的方案中,Flash算是性价比较高的方案,兼容性不错,实现起来也简单,基本如下: 1.从摄像头、麦克风获取媒体信息

    // 初始化麦克风
    _mic = Microphone.getMicrophone();// default microphone
    // 其他操作,如回音消除,需要硬件支持,Flash有API可以检测
    // Microphone.getEnhancedMicrophone()
    // 设置编码器,这里要注意CDN分发平台对音频编码的支持程度
    _mic.codec = "Speex";
    // 一些附加的事件
    // 初始化摄像头
    _cam = Camera.getCamera(); // default camera
    // 其他操作,如设置带宽及质量、设置帧间隔等
    // _cam.setKeyFrameInterval(num); //设置帧间隔
    // 设置好解码器
    _videoCodec = new H264VideoStreamSettings();
    _videoCodec.setProfileLevel(H264Profile.BASELINE, "3");
    // 一些附加的事件,如摄像头活动状态
    // 可以把视频流挂到页面播放器里边去
    player.attachCamera(_cam);

2.建立连接,推流到CDN

      _outstream = new NetStream(_connection);// 建立流连接
      // 将本地获取的音视频流挂上去
      _outstream.attachCamera(_cam);
      _outstream.attachAudio(_mic);
      _outstream.videoStreamSettings = _videoCodec;
      _outstream.publish();

主要的代码如上,具体实现Adobe AS文档里也有。这里只是客户端推流到服务端,将内容下发到用户还是依赖于CDN分发平台的。 Flash的问题在于即将被停止维护,一些浏览器如FireFox更是不支持了,当然在国内应该还能蹦跶好久,360、QQ都是支持的。HTML5已经广受支持,因此可以使用一些新的API来做到一样的事情,具体步骤还是和上面一样。获取音视频流,可以通过WebRTC一系的MediaStream(aka getUserMedia) API完成,推流到服务端,可以使用WebSocket以二进制的形式传输,到了服务端就好办了,可以通过Ws下发,也可以用FFmpeg 以rtmp到分发平台下发,这里讲下后者: 1.获取音视频流,使用ws推流到服务端

    // client.js
    const mediaSource = new MediaSource();
    const constraints = {
        audio: {
          echoCancellation: {exact: true}
        },
        video: {
          width: 1280, height: 720
        }
    };
    const stream = await navigator.mediaDevices.getUserMedia(constraints);
    ws = new WebSocket('ws://localhost:1080');// 建立
    ws.addEventListener('open', e => {
      let options = {
        mimeType: 'video/webm;codecs=h264', // 编码格式
        audioBitsPerSecond: 44100,  // 44.1kHz
        videoBitsPerSecond: 3000000 // 3000k 画质
      };
      try {
        mediaRecorder = new MediaRecorder(stream, options);//进行媒体录制,把流变成二进制数据
      } catch (e) {
        console.error('Exception while creating MediaRecorder:', e);
        return;
      }
      mediaRecorder.ondataavailable = () => {
        if (event.data && event.data.size > 0) {
          ws.send(event.data);
        }
      };
      mediaRecorder.start(10); // collect 10ms of data
  });

2.ffmpeg rtmp推流

    // server.js
    ws.on('connection', (ws) => {
      const ffmpeg = spawn('ffmpeg', [
        // 从 stdin 中读入视频数据
        '-i', '-',
        // 视频转码
        // 由于视频已经是 H.264 编码,可以直接复制
        // 若需要转码则填 libx264
        '-vcodec', 'copy',
        // 音频转码
        '-acodec', 'aac',
        // 输出为 flv 格式
        '-f', 'flv',
        // RTMP 服务器
        // RTMP_SERVER
        // 生成本地文件
        'liveRecord.flv'
      ]);

      ws.on('message', (msg) => {
        // 收到时 msg 的类型是 Buffer
        ffmpeg.stdin.write(msg);
      });
    });

ws有更稳定的socket.io,nodejs 也有node-fluent-ffmpeg,实现起来还是比较简单。缺点是这些API支持度不是很好,兼容性也有问题,毕竟不是用户都用Chrome、FireFox这样的浏览器。 搭建一个稳定的能承载从客户端收流的服务端成本也挺高的,因此可以把这个服务端用Electron打包起来以插件的方式放到客户端,从客户端直接推流到CDN。 就测试的demo而言,效果比flash好一些,flash推流延时基本是15-30s间,上述方案网络好的情况可以到8-10s。

但是。。。有了Electron还要浏览器上做文章?可以把这些一股脑的功能打包到一起,一个直播客户端由此诞生,相较于OBS等,可以符合产品设想,使用复杂度也可以降下来。缺点也挺明显,不是每个电脑都安装了。Electron的优点就不絮叨了,各有个的好处吧。