栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Go语言

go(goav) 中使用 ffmpeg 获取摄像头视频流,并转换成图片,发送给前端界面实时展示

Go语言 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

go(goav) 中使用 ffmpeg 获取摄像头视频流,并转换成图片,发送给前端界面实时展示

本文的主角: ffmpeg goav。
之前写过一篇文章,实时展示摄像头内容 中有提到过一种实时展示摄像头内容的方式:集成ffmpeg相关的代码,并转换成图片传给web界面进行相应的展示,现补充下具体的实现。

ffmpeg简介

ffmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案(百度百科)。

goav

一个开源库,能够方便的在 go 中使用 C 的相应方法。要使用 goav,还是得先熟悉 ffmpeg 的基本使用方式,这篇就不详述了,可以看大神雷霄骅的相关文章进行学习(真的赞)。
该仓库主要是通过 cgo 对 c 中的相关方法进行绑定,前人栽树后人乘凉,这里就直接拿来用了。但是该库集成的东西并不全面,若感觉不可靠,可自行实现。

实际效果

直接上代码

这里主要使用 go 的 gin 框架作为 http 服务器,通过 websocket 实时地推送图片给 web 界面进行展示。

go
package api

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/giorgisio/goav/avcodec"
	"github.com/giorgisio/goav/avdevice"
	"github.com/giorgisio/goav/avformat"
	"github.com/giorgisio/goav/avutil"
	"github.com/giorgisio/goav/swscale"
	"github.com/gorilla/websocket"
	"image"
	"image/color"
	"image/jpeg"
	"net/http"
	"unsafe"
)

type wsParam struct {
	Name string
}
// 用于测试实时读取摄像头的内容,并发送 base64 给界面
func ConnectWs(c *gin.Context) {
	var param wsParam
	if err := c.Bind(¶m); err != nil {
		c.String(http.StatusBadRequest, err.Error())
		return
	}

	var wsUpgrader = websocket.Upgrader{
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
		CheckOrigin:     func(r *http.Request) bool { return true }, // disable origin check
	}
	conn, err := wsUpgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		c.String(http.StatusInternalServerError, err.Error())
		return
	}
	defer conn.Close()
	go tSendPic(conn)

	_, _ , err = conn.ReadMessage()
	if err != nil {
		fmt.Println(err)
	}
}
func tSendPic(conn *websocket.Conn) {
	avdevice.AvdeviceRegisterAll()
	avcodec.AvcodecRegisterAll()

	pFormatCtx := avformat.AvformatAllocContext()
	defer pFormatCtx.AvformatCloseInput()

	inputFmt := avformat.AvFindInputFormat("dshow")
	// 注意这里的第二个参数,需要改成自己的摄像头名称
	if ret := avformat.AvformatOpenInput(&pFormatCtx, "video=USB2.0 PC CAMERA", inputFmt, nil); ret != 0 {
		fmt.Println("Unable to open video USB2.0 PC CAMERA")
		return
	}

	if pFormatCtx.AvformatFindStreamInfo(nil) < 0 {
		fmt.Println("Unable to find stream")
		return
	}

	videoindex := -1
	nbStreams := int(pFormatCtx.NbStreams())
	for i := 0; i < nbStreams; i++ {
		streams := pFormatCtx.Streams()
		if streams[i].Codec().GetCodecType() == avformat.AVMEDIA_TYPE_VIDEO {
			videoindex = i
			break
		}
	}
	if videoindex == -1 {
		fmt.Printf("Couldn't find a video stream.n")
		return
	}

	pCodecCtxOrig := pFormatCtx.Streams()[videoindex].Codec()
	defer (*avcodec.Context)(unsafe.Pointer(pCodecCtxOrig)).AvcodecClose()
	pCodec := avcodec.AvcodecFindDecoder(avcodec.CodecId(pCodecCtxOrig.GetCodecId()))
	if pCodec == nil {
		fmt.Println("没有找到解码器")
		return
	}

	pCodecCtx := pCodec.AvcodecAllocContext3()
	defer pCodecCtx.AvcodecClose()
	if pCodecCtx.AvcodecCopyContext((*avcodec.Context)(unsafe.Pointer(pCodecCtxOrig))) != 0 {
		fmt.Println("Couldn't copy codec context")
		return
	}

	// Open codec
	if pCodecCtx.AvcodecOpen2(pCodec, nil) < 0 {
		fmt.Println("Could not open codec")
		return
	}

	pFrame := avutil.AvFrameAlloc()
	if pFrame == nil {
		fmt.Println("Unable to allocate Frame")
		return
	}
	defer avutil.AvFrameFree(pFrame)
	pFrameRGB := avutil.AvFrameAlloc()
	if pFrameRGB == nil {
		fmt.Println("Unable to allocate RGB Frame")
		return
	}
	defer avutil.AvFrameFree(pFrameRGB)

	width := pCodecCtx.Width()
	height := pCodecCtx.Height()

	img_convert_ctx := swscale.SwsGetcontext(
		width,
		height,
		(swscale.PixelFormat)(pCodecCtx.PixFmt()),
		width,
		height,
		avcodec.AV_PIX_FMT_RGB24,
		avcodec.SWS_BILINEAR,
		nil,
		nil,
		nil,
	)

	numBytes := uintptr(avcodec.AvpictureGetSize(avcodec.AV_PIX_FMT_RGB24, width, height))
	buffer := avutil.AvMalloc(numBytes)
	defer avutil.AvFree(buffer)
	avp := (*avcodec.Picture)(unsafe.Pointer(pFrameRGB))
	avp.AvpictureFill((*uint8)(buffer), avcodec.AV_PIX_FMT_RGB24, width, height)

	packet := avcodec.AvPacketAlloc()
	defer packet.AvFreePacket()

	for pFormatCtx.AvReadFrame(packet) >= 0 {
		if packet.StreamIndex() != videoindex {
			continue
		}
		var got_picture int
		if ret := pCodecCtx.AvcodecDecodeVideo2((*avcodec.Frame)(unsafe.Pointer(pFrame)), &got_picture, packet); ret < 0 {
			fmt.Println("Decode Error")
			return
		}
		if got_picture > 0 {
			swscale.SwsScale2(img_convert_ctx, avutil.Data(pFrame),
				avutil.Linesize(pFrame), 0, height,
				avutil.Data(pFrameRGB), avutil.Linesize(pFrameRGB))
			// 直接存储
			// 转成图片并传给界面
			if buf, err := getJpegStreams(pFrameRGB, width, height); err == nil {
				if er := conn.WriteMessage(websocket.TextMessage, buf); er != nil {
					fmt.Println("write err: ", er)
					return
				}
			}
		} else {
			fmt.Println("got_picture err: ", got_picture)
		}
	}
	fmt.Println("exit video!!!")
}
func getJpegStreams(frame *avutil.Frame, width, height int) ([]byte, error) {
	img := image.NewRGBA(image.Rect(0, 0, width, height))
	for y := 0; y < height; y++ {
		data0 := avutil.Data(frame)[0]
		startPos := uintptr(unsafe.Pointer(data0)) + uintptr(y)*uintptr(avutil.Linesize(frame)[0])
		//fmt.Println("startPos: ", startPos)
		xxx := width * 3
		for x := 0; x < width; x++ {
			var pixel = make([]byte, 3)
			for i := 0; i < 3; i++ {
				element := *(*uint8)(unsafe.Pointer(startPos + uintptr(xxx)))
				pixel[i] = element
				xxx++
			}
			img.SetRGBA(x, y, color.RGBA{pixel[0], pixel[1], pixel[2], 0xff})
		}
	}

	var b []byte
	buffer := bytes.NewBuffer(b)
	err := jpeg.Encode(buffer, img, nil)
	if err != nil {
		fmt.Println("jpeg.Encode err: ", err)
		return nil, err
	}


	dst := make([]byte, base64.StdEncoding.EncodedLen(buffer.Len()))
	base64.StdEncoding.Encode(dst, buffer.Bytes())
	return dst, nil
}
html



    
    ws获取图片数据展示



测试服务器返回值

完整的代码可见gitee(平时写的一些测试都在这个仓库)。该示例的代码在 ginServer 目录下。

总结

其实难点主要还是在于 ffmpeg 的使用,由于时间精力有限,并未进行深入研究,对音视频感兴趣的小伙伴可以去看雷神(雷霄骅)的博客。
感慨一下:知道雷神这个人,是在搜索 ffmpeg 的相关用法的时候出现的博客。翻看其博客内容,真是大为惊叹,文章通俗易懂,排版格式也看的很舒服,默默的给了个赞加关注。但是翻到最后评论的时候,才知道天妒英才,雷神已经走了,惋惜。
不求成为栽树人,但愿变成养树人。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/910365.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号