の続き。
概要
動画を受け取り、動画から受け取ったフレームをある間隔で画像処理するようなアプリケーションをgolangで書きたい。
画像処理ライブラリとしてopencvを利用する必要があり、ラッパーとしてgocvを利用した。
試行錯誤した結果 opencv.OpenVideoCaptureという関数のバックエンドとしてgstreamerが使用できるという記事があり、試してみた。
構造
実際にはRTPの前にはWebRTCがあり、今回はSkyway WebRTC Gatewayを用いてWebRTC->RTPに変換している。
WebRTC周りについては別の機会に。
RTPを受け取る方法にはいろいろあると思うが、今回はopencvのgstreamer backendを使用した。この機能はgocvでは最新(0.26)でしか動作しないため、バージョンは要確認。
module | version |
---|---|
golang | 1.15.x |
opencv | 4.5.1 |
gocv | 0.26.0 |
gstreamer | 1.0.0 |
実装
videocapture.go
package middleware
import (
"context"
"fmt"
"time"
"github.com/pkg/errors"
"go.uber.org/zap"
"gocv.io/x/gocv"
)
// Handler function of VideoCaptureServer
type VideoCaptureHandler func(ctx context.Context, img *gocv.Mat) error
// VideoCaptureServer is server to handle gocv.OpenVideoCapture image
type VideoCaptureServer struct {
handler VideoCaptureHandler
pipeline string
duration time.Duration
}
func NewVideoCaptureServer(handler VideoCaptureHandler, pipeline string, duration time.Duration) *VideoCaptureServer {
return &VideoCaptureServer{
handler: handler,
pipeline: pipeline,
duration: duration,
}
}
// Start start server
func (s *VideoCaptureServer) Start(ctx context.Context) error {
cap, err := gocv.OpenVideoCapture(s.pipeline)
if err != nil {
return errors.Wrap(err, "OpenVideoCapture failed")
}
defer cap.Close()
ticker := time.NewTicker(s.duration)
defer ticker.Stop()
img := gocv.NewMat()
for {
select {
case <-ticker.C:
if err := s.handler(ctx, &img); err != nil {
logger.Error("exec handler failed", zap.Error(err))
}
case <-ctx.Done():
return nil
default:
if ok := cap.Read(&img); !ok {
logger.Error("cap.Read failed")
}
}
}
}
処理の内容はVideoCaptureHandlerとして関数インターフェースのみ定義し、動画を受け取って処理に引き渡す周りのコードをVideoCaptureServerとして定義。
videocapture_test.go
package middleware
import (
"context"
"fmt"
"testing"
"time"
"gocv.io/x/gocv"
)
func TestVideoCaptureServer(t *testing.T) {
ctx := context.Background()
window := gocv.NewWindow("test")
handler := func(ctx context.Context, img *gocv.Mat) error {
window.IMShow(*img) // Xサーバーにテスト画像を表示
window.WaitKey(1)
return nil
}
pipeline := `udpsrc port=8888 caps="application/x-rtp,media=video,clock-rate=90000,encoding-name=H264" ! rtph264depay ! avdec_h264 ! videoconvert ! appsink`
//pipelineを使ってOpenVideCaptureを実行し、1秒おきにhandler関数を実行
sv := NewVideoCaptureServer(handler, pipeline, time.Second)
sv.Start(ctx)
}
こちらがテスト。
- Xサーバーにテスト用画像を表示するので、動作させるためには事前にX Serverを起動しておく必要がある。
- gst pipelineでudp port=8888で動画を受け取るように書いているが、mp4ファイルを使ったテストをしたければpipeline文字列を入れ替えるだけで良い。
評価
環境構築に難しい面はあるものの、gocv.OpenVideoCapture関数さえ使いこなせれば簡単にRTPのようなプロトコルを受け取る事が可能で、そこからの処理はほかのサーバーとそう変わらず、難しくないと感じた。
何らかの動画をgolangで受け取るような要件がある場合は、候補になると思う。
なお、pipelineの動作については、事前にgst-launchを使って確認しておくこと。appsinkを使った時のデバッグメッセージは分かりにくいと感じました・・・