Elsaの技術日記(徒然なるままに)

主に自分で作ったアプリとかの報告・日記を記載

MENU

go言語でのストリーミング配信

以前、pythonを用いてストーリング配信を行ってみました。
pythonを用いた時にはサーバサイドにdangoを用いていました。

今回はgo言語を用いてストリーミング配信を行ってみることにしました。


■構成

こちらの構成で実施してみました。
ただ実際、LinuxサーバはVirtualBoxを用いた仮想環境を用いました。
f:id:Elsammit:20201122162617p:plain

・PC:windows10
Linuxサーバ:ubuntu16.04(Virtual Box上)
・カメラ:LOGICOOL HDプロ ウェブカム C920

■go言語でのカメラキャプチャ

カメラキャプチャにはgocvを用いました。
gocvはgo言語版のOpenCVです。
インストールなどはこちらを参考に。
elsammit-beginnerblg.hatenablog.com

gocvを用いたカメラキャプチャはこちらのようなコードを作成すればOKです。

package main

import (
	"fmt"

	"gocv.io/x/gocv"
)

var (
	err    error
	webcam *gocv.VideoCapture
)

func main() {
	webcam, err = gocv.OpenVideoCapture(0)
	if err != nil {
		fmt.Printf("Error opening capture device: \n")
		return
	}
	defer webcam.Close()
	img := gocv.NewMat()
	defer img.Close()

	window := gocv.NewWindow("Video Capture")

	for {
		ok := webcam.Read(&img)
		if !ok {
			fmt.Printf("Device closed\n")
			return
		}
		window.IMShow(img)
		window.WaitKey(1)
	}
}

こちらでキャプチャ画像(動画)を取得するカメラ番号を取得。

webcam, err = gocv.OpenVideoCapture(0)

こちらで1フレーム毎の画像を読み出し、IMShowで表示させます。
imgはMat形式になっており、Mat形式でのデータ読み出し、表示になります。

	for {
		ok := webcam.Read(&img)
		if !ok {
			fmt.Printf("Device closed\n")
			return
		}
		window.IMShow(img)
		window.WaitKey(1)
	}

■ストリーミング配信(サーバサイド)

カメラキャプチャのストリーミング配信のコードはこちらのように作成しました。

package main

import (
	"fmt"
	"html/template"
	"image"
	"log"
	"net/http"
	"sync"
	"time"

	"gocv.io/x/gocv"
)

var (
	err    error
	webcam *gocv.VideoCapture
)

var buffer = make(map[int][]byte)
var frame []byte
var mutex = &sync.Mutex{}

func main() {

	go getframes()
	host := "0.0.0.0:8080"

	http.HandleFunc("/video", func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("call video")
		w.Header().Set("Content-Type", "multipart/x-mixed-replace; boundary=frame")
		data := ""
		for {
			mutex.Lock()
			data = "--frame\r\n  Content-Type: image/jpeg\r\n\r\n" + string(frame) + "\r\n\r\n"
			mutex.Unlock()
			time.Sleep(1 * time.Millisecond)
			w.Write([]byte(data))
		}
	})
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		t, _ := template.ParseFiles("index.html")
		t.Execute(w, "index")
	})

	log.Fatal(http.ListenAndServe(host, nil))
}

func getframes() {
	webcam, err = gocv.OpenVideoCapture(0)
	if err != nil {
		fmt.Printf("Error opening capture device: \n")
		return
	}
	defer webcam.Close()
	img := gocv.NewMat()
	defer img.Close()

	for {
		if ok := webcam.Read(&img); !ok {
			fmt.Printf("Device closed\n")
			return
		}
		if img.Empty() {
			continue
		}
		
		gocv.Resize(img, &img, image.Point{}, float64(1.5), float64(1.5), 0)

		frame, _ = gocv.IMEncode(".jpg", img)
	}
}

getframes()関数とmain関数を並列処理させております。

var frame []byte

グローバル変数で定義し、
getframes()関数にてframeを格納していきます。
同時に、

	http.HandleFunc("/video", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "multipart/x-mixed-replace; boundary=frame")
		data := ""
		for {
			mutex.Lock()
			data = "--frame\r\n  Content-Type: image/jpeg\r\n\r\n" + string(frame) + "\r\n\r\n"
			mutex.Unlock()
			time.Sleep(1 * time.Millisecond)
			w.Write([]byte(data))
		}
	})

により、/videoに対してhttpリクエストがあった場合にレスポンスとしてストリーミング配信を行っております。
for文でframeを読み出し、文字列変換、レスポンス返信をtime.sleep(1*time.Millisecond)の感覚で行っております。

■ストリーミング配信(フロントサイド)

フロントサイドでのhtmlはこちらです。

<head>
    <h1>ストリーミング</h1>
</head>
<body>
    <iframe src="httpパス/video" width="960" Height="720"></iframe><br/>
</body>

フロントサイドは単純でiframeによりhtml内に別ページであるvideoパスを表示させております。

■結果

先ほどのソースコードを実行するとこんな感じになります。
f:id:Elsammit:20201122172309g:plain

■ちょっと改造してみた

ちょっと改造してグレースケールへの変換やスタート・ストップを追加してみました。
こんな感じになりました!!
f:id:Elsammit:20201122172605g:plain

ソースコードは後でGihubに上げておこうと思います!!

■最後に

今回はgo言語でストリーミング配信頑張ってみました!!
だけど、、、現状httpレスポンス実行するサーバと画像キャプチャを分離出来ていないので汎用性が低いよな。。。
今度は分離させる方法を調べてみたいと思います!!
分かったらブログ書こうと思います。