go(goav) 中使用 ffmpeg 获取摄像头视频流,并转换成图片,发送给前端界面实时展示
最编程
2024-01-08 10:26:47
...
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
}