JS调用摄像头、实时视频流上传(一次不成功的试验)

本文记录了一次使用JS调用摄像头,通过WebRTC获取视频流并尝试实时上传的试验过程。作者遇到的问题是每100ms抓取的一帧800*800 png图片大小约2MB,导致上传不切实际。后端使用Go接收websocket消息并解码。由于问题,作者决定转向研究RTMP技术。
摘要由CSDN通过智能技术生成

JS调用摄像头、实时视频流上传(一次不成功的试验)

思路

前端调用摄像头,获取视频流,从视频流中取一帧转图片,用websocket上传图片。

研究了一下发现了WebRTC这种技术,看到有博客说一些直播应用使用WebRTC将视频流发布到服务器上,然后用hls等技术在前端播放,但是我花了些时间还是没搞清楚WebRTC的原理,只能以后再尝试了。

1.前端代码

前端用的React+Typescript,所以这里贴出tsx代码。
逻辑比较简单,调用WebRTC的接口getUserMedia获取视频流,然后把stream绑定到video元素上,接下来设个clock,每100ms抓一次视频帧(fps=10),将其画到canvas上后就可以调canvas.toDataUrl把图片的位图数据编码成png格式,编码是必须的,因为编码后的数据一般比原始数据小一个数量级,节约带宽。也可以编码成其他格式,像jpeg或者webp,我对这些格式了解不多,可能压缩率最高的是webp。

其实到这一步已经出现问题了,800*800大小的png图片大小也近2M左右,这意味着一秒钟要上传10 * 2M = 20M的数据,十分不现实。没有测试过转webp格式,我觉得效果也不会太好,还是直接上传视频流比较好

export default class App extends React.Component<any, State> {

  componentDidMount() {
    let canvas = (this.refs.canvas as HTMLCanvasElement)
    let canvasContext = canvas.getContext('2d') as CanvasRenderingContext2D

    let video = (this.refs.video as HTMLVideoElement)
     navigator.getUserMedia({video: true},
      (stream: MediaStream) => {
              video.srcObject = stream
        video.play()
        // upload stream to server
        let ws = this.initWebSocket(
          () => {
            console.log('start send frames')
            setInterval(() => {
                canvasContext.drawImage(video, 0, 0, 800, 800)
                // TODO: check if ws is closed
                ws.send(canvas.toDataURL('image/png', 1))
            }, 100) // 10 fps
          }
        )
      }, 
      (err: MediaStreamError) => console.error(err))
  }
  
  initWebSocket(onReady: () => void) {
    let ws = new WebSocket(format('ws://%s:%d/ws', config.server.host, config.server.port))
    ws.onopen = () => {
      console.log('websocket opened')
      onReady()
    }
    ws.onclose = () => console.log('websocket closed')
    return ws
  }
  
  render() {
    return (
      <Layout style={{
        display: 'block',
        height: '100%', width: '100%'}}>
        <Layout.Header>
          <Typography.Title style={{color: 'white', textShadow: '1px 1px 3px black'}}>Face Detect</Typography.Title>
        </Layout.Header>
        <Layout.Content style={{width: '60%', height: '100%', margin: '0px auto'}}>
          <video ref='video' width={800} height={800} style={{border: '1px black solid'}}></video>
          <canvas ref='canvas' width={800} height={800} style={{display: 'block', border: '1px black solid'}}></canvas>
        </Layout.Content>
      </Layout>
    )
  }
}
2.后端代码

后端用go写的,篇幅有限,搭建http服务和websocket相关的代码就不贴出来了。功能就是把从websocket接收到的每一条消息,从DataUrl解码成Image对象。

import (
	"bytes"
	"encoding/base64"
	"github.com/gorilla/websocket"
	"image"
	"image/png"
	"net/http"

	"github.com/Luncert/slog"
)

func serverWs(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Error(err)
		return
	}
	defer func() {
		_ = conn.Close()
	}()
	for {
		_, message, err := conn.ReadMessage()
		if err != nil {
			log.Error("read:", err)
			break
		}
		// 将DataUrl解码成png字节数据
		src := decodeDataUrl(message)
		// 调image/png库解码出Image对象
		img, err := png.Decode(bytes.NewReader(src))
		if err != nil {
			log.Fatal(err)
		}
	}
}

func decodeDataUrl(raw []byte) []byte {
    // 去掉DataUrl中的元数据:从第一个字符到第一个','
	i := 0
	for raw[i] != ',' {
		i++
	}
	i++
	src := raw[i:]
	// 调base64解码后续数据
	maxLen := base64.StdEncoding.DecodedLen(len(src))
	dst := make([]byte, maxLen)
	if _, err := base64.StdEncoding.Decode(dst, src); err != nil {
		log.Fatal(err)
	}
	return dst
}

这个方案到此为止了,接下来转向Rtmp

由于涉及到浏览器的安全限制,前端无法直接调用摄像头实时上传视频数据。一般情况下,需要使用浏览器提供的WebRTC API来实现。 以下是一个使用WebRTC API实现视频采集并上传的完整代码例子: HTML代码: ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>WebRTC视频采集与上传</title> </head> <body> <video id="video" width="640" height="480" autoplay></video> <button id="startButton">开始采集</button> <button id="stopButton">停止采集</button> <script src="main.js"></script> </body> </html> ``` JavaScript代码: ```javascript // 获取DOM元素 const video = document.getElementById('video'); const startButton = document.getElementById('startButton'); const stopButton = document.getElementById('stopButton'); let stream; // 存储视频流 // 获取视频流 function getMediaStream() { // 判断浏览器是否支持WebRTC API if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { alert('您的浏览器不支持WebRTC API!'); return; } // 获取视频流 navigator.mediaDevices.getUserMedia({ video: true, // 开启视频 audio: false // 禁用音频 }).then(function (mediaStream) { // 将视频流绑定到video元素中 video.srcObject = mediaStream; stream = mediaStream; }).catch(function (error) { console.error('获取视频流出错:', error); }); } // 停止视频采集 function stopMediaStream() { if (stream) { stream.getTracks().forEach(function (track) { track.stop(); }); } } // 开始采集视频 startButton.addEventListener('click', function () { getMediaStream(); }); // 停止采集视频 stopButton.addEventListener('click', function () { stopMediaStream(); }); ``` 上述代码实现了获取视频流,并将视频流绑定到video元素中。如果需要上传视频数据,可以在获取视频流成功回调中获取到视频数据,并通过ajax等方式上传到服务器。具体实现方式可以根据具体需求进行调整。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值