多媒体框架

更新时间:
2023-11-23
下载文档

多媒体框架

本章主要介绍爱智多媒体框架。

概述

爱智提供多种多媒体处理模块,用于处理多媒体应用的解码、视频和流媒体服务,具体请参考:

应用示例

爱智提供以下多媒体应用示例,供开发者参考:

智能摄像头

功能介绍

定制 MediaSource

  1. 以流媒体服务为例,需要自定义 MediaSource 接口,具体请参考 Multi-Media/WebMedia,新实现的 CameraSource 位于 camera_src.js 文件中。

    参考以下示例,CameraSource 直接继承至 JSRE 内置的 FlvSrc 类(注册名称: flv),在 FlvSrc 基础上已经具备基础的流解析和分发功能。

    var FlvSrc = require("webmedia/source/flv");
    class CameraSource extends FlvSrc {
      constructor(ser, mode, inOpts, outOpts) {
        super(ser, mode, inOpts, outOpts);
        // ...
      }
      // ...
    }
    
  2. 参考以下示例,重写 start() 接口,将 MediaDecoder 对象封装到 CameraSource 中,使 CameraSource 类能够完整地接收和处理 rtsp 流。

    var MediaDecoder = require('mediadecoder');
    // ...
    start() {
      var netcam = new MediaDecoder();
      this._netcam = netcam;
      var self = this;
      var input = this.inOpts;
      var url = `rtsp://${input.user}:${input.pass}@${input.host}:${input.port}${input.path}`;
      var name = `${input.host}:${input.port}${input.path}`;
      new Promise((resolve, reject) => {
        netcam.open(url, { proto: 'tcp', name: name }, 10000, (err) => {
          // ...
          netcam.on('remux', self.onStream.bind(self));
          netcam.on('header', self.onStream.bind(self));
          netcam.on('eof', self.onEnd.bind(self));
        }
      }
    }
    
  3. 参考以下示例,netcam 对象将转换后的流交由 onStream() 方法处理, 由于 FlvSrc 实现了 pushStream() 接口,只需直接将流推送给 FlvSrc 处理即可,CameraSource 实现了完整的获取、转换、分发流功能。

    onStream(frame) {
      if (!this._netcam) {
        return;
      }
      var buf = Buffer.from(frame.arrayBuffer);
      try {
        this.pushStream(buf);
      } catch (e) {
        console.error(e);
        this.stop();
      }
    }
    

创建流媒体服务器

  1. 参考以下流媒体服务示例,先在 WebMedia 框架中注册 CameraSource(main.js),注册完成后,就可以使用名称为 camera-flv 的 MediaSource。

    var WebMedia = require("webmedia");
    var CameraSource = require("./camera_src");
    
    const sourceName = "camera-flv";
    WebMedia.registerSource(sourceName, CameraSource);
    
  2. 参考以下示例,创建流媒体服务器。

    方式一

    引用 @edgeros/jsre-medias 模块创建流媒体服务器。

    const { Manager } = require("@edgeros/jsre-medias");
    

    @edgeros/jsre-medias 主要由 Media 和 Manager 构成:

    • Manager 集成了 onvif 模块,可以自动发现、记录设备,创建和管理摄像头对象,创建和管理 Media 对象。
    • Media 是对 WebMedia 服务器的封装,Manager 服务可以同时接入多路流媒体。

    方式二

    使用 Manager 创建流媒体服务器。

    var server = undefined;
    // ...
    function createMediaSer() {
      console.log("Create media server.");
      if (server) {
        return server;
      }
    
      var opts = {
        mediaTimeout: 1800000,
        searchCycle: 20000,
        autoGetCamera: false,
      };
      server = new Manager(app, null, opts, (opts) => {
        return {
          source: sourceName,
          inOpts: opts,
          outOpts: null,
        };
      });
      // ...
    }
    

    Manager 创建的流媒体服务器是双通道模式,流媒体通道与数据通道都是基于 WebSocket 协议,Manager 内部自动创建 WebSocket 服务器,并动态生成流媒体通道访问路径。

获取设备列表

参考以下流媒体示例,前端通过 REST 接口请求设备列表,交由后端处理。

app.get("/api/list", (req, res) => {
  // ...
  var devs = [];
  server.iterDev((key, dev) => {
    var info = dev.dev;
    var stream = dev.mainStream;
    var media = stream ? stream.media : null;
    devs.push({
      devId: key,
      alias: `${info.hostname}:${info.port}${info.path}`,
      report: info.urn,
      path: media ? "/" + media.sid : "",
      status: media ? true : false,
    });
  });
  res.send(JSON.stringify(devs));
});

连接设备

  1. 参考以下流媒体示例,前端获取到设备列表后,对于未连接的设备(media 为空)需提供账号密码尝试连接设备,用于创建流媒体服务。

    app.post("/api/login", bodyParser.json(), (req, res) => {
      // ...
      var ret = { result: false, msg: "error" };
      var info = req.body;
      try {
        connectMedia(info, (media) => {
          if (!media || media instanceof Error) {
            ret.msg = `Device ${info.devId} login fail.`;
            console.warn(media ? media.message : ret.msg);
          } else {
            ret.result = true;
            ret.msg = "ok";
            ret.path = "/" + media.sid;
          }
          res.send(JSON.stringify(ret));
        });
      }
      // ...
    });
    
  2. 参考以下示例,connectMedia() 是创建流媒体服务过程,回调返回 media 对象,这个过程可能会失败,失败原因可能是账号密码不正确,也可能是该设备不是 rtsp 设备。

    • 识别摄像头设备:
    function connectMedia(info, cb) {
      var devId = info.devId;
      var dev = server.findDev(devId);
      // ...
      var stream = dev.mainStream;
      if (!stream) {
        server.createStream(devId, info, (err, streams) => {
          if (err) {
            cb(err);
          } else {
            getMedia(cb);
          }
        });
      }
      // ...
    }
    
    • 创建流媒体服务:
    function connectMedia(info, cb) {
      // ...
      function getMedia(cb) {
        var stream = dev.mainStream;
        if (!stream) {
          return cb();
        } else if (stream.media) {
          return cb(stream.media);
        }
        server.createMedia(devId, stream.token, info, (err, media) => {
          if (err) {
            cb(err);
          } else {
            cb(media);
          }
        });
      }
    }
    

连接流媒体

本示例前端采用 Vue 实现,位于前端项目的 web 目录下,以下代码在 Player.vue 组件中。

  1. 前端连接流媒体服务时,双通道有握手过程,使用 @edgeros/web-mediaclient 模块处理连接过程,参考以下示例在页面中引用模块。

    <script type="text/javascript" src="./mediaclient.min.js"></script>
    
  2. 参考以下示例,页面初始化时创建 MediaClient 对象。

    this.np = new NodePlayer();
    // ...
    var proto = location.protocol === "http:" ? "ws:" : "wss:";
    var host = `${proto}//${window.location.host}`;
    var mediaClient = new MediaClient(
      host,
      (client, path) => {
        this.np.start(host + path);
      },
      { path: this.dev.path }
    );
    
  3. 参考以下示例,可使用 mediaClient 打开连接。

    startPlay: function () {
      console.log('Start play.');
      if (!this.isStarting) {
        console.log('Start.');
        this.isStarting = true;
        this.mediaClient.open(getAuth());
      }
    }
    
  4. 参考以下示例,可使用 mediaClient 关闭连接。

    stopPlay: function() {
      if (this.isStarting) {
        console.log('Stop.');
        this.mediaClient.close();
      }
    }
    
    mediaClient.on('close', () => {
      console.log('MediaClient on close');
      this.np.stop();
      this.isStarting = false;
    });
    
文档内容是否对您有所帮助?
有帮助
没帮助