RTPで受け取った動画をgolangで画像処理したい

https://qiita.com/kishibashi3/items/e20aecef45ed8341e739

の続き。
# 概要


動画を受け取り、動画から受け取ったフレームをある間隔で画像処理するようなアプリケーションをgolangで書きたい。

画像処理ライブラリとしてopencvを利用する必要があり、ラッパーとしてgocvを利用した。
試行錯誤した結果 opencv.OpenVideoCaptureという関数のバックエンドとしてgstreamerが使用できるという記事があり、試してみた。

https://elsammit-beginnerblg.hatenablog.com/entry/2020/12/06/224117

# 構造

実際にはRTPの前にはWebRTCがあり、今回はSkyway WebRTC Gatewayを用いてWebRTC->RTPに変換している。

https://github.com/skyway/skyway-webrtc-gateway

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236681/ddbf4992-b4ab-7d10-498e-2a89d9332f0a.png)

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
```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
```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を使った時のデバッグメッセージは分かりにくいと感じました・・・

https://qiita.com/kishibashi3/items/983ae9df29b6d9f8c9dd
続き