package video import ( "context" "image" "image/draw" "io" "time" "github.com/pion/mediadevices/pkg/frame" "github.com/pion/mediadevices/pkg/io/video" "github.com/pion/mediadevices/pkg/prop" "github.com/sirupsen/logrus" _ "github.com/jbuchbinder/gopnm" ) const DefaultFPS float32 = 60.0 type Frame struct { Time time.Time Image io.ReadCloser } type PPMStreamDriver struct { Height, Width int FPS float32 PPMImage <-chan Frame closed <-chan struct{} cancel func() } func (v *PPMStreamDriver) Open() error { defer logrus.Debug("device opened") ctx, cancel := context.WithCancel(context.Background()) v.closed = ctx.Done() v.cancel = cancel return nil } func (v *PPMStreamDriver) Close() error { v.cancel() return nil } func (v *PPMStreamDriver) Properties() []prop.Media { return []prop.Media{ { Video: prop.Video{ Width: v.Width, Height: v.Height, FrameRate: v.FPS, FrameFormat: frame.FormatYUYV, }, }, } } func (v *PPMStreamDriver) VideoRecord(p prop.Media) (video.Reader, error) { logrus.Debug(p) canvas := image.NewRGBA(image.Rect(0, 0, p.Width, p.Height)) var ( prevHeight, prevWidth int ) r := video.ReaderFunc(func() (img image.Image, release func(), err error) { select { case <-v.closed: return nil, func() {}, io.EOF case ppmF := <-v.PPMImage: defer ppmF.Image.Close() // skip timeouted frame if time.Since(ppmF.Time) > time.Second/time.Duration(p.FrameRate) { return canvas, func() {}, nil } img, _, err := image.Decode(ppmF.Image) if err != nil { return nil, func() {}, err } // screen geometroy change if img.Bounds().Dx() != prevWidth || img.Bounds().Dy() != prevHeight { draw.Draw(canvas, canvas.Rect, image.Black, image.Black.Bounds().Min, draw.Over) prevWidth = img.Bounds().Dx() prevHeight = img.Bounds().Dy() } offsetX := (canvas.Rect.Dx() - img.Bounds().Dx()) / 2 offsetY := (canvas.Rect.Dy() - img.Bounds().Dy()) / 2 draw.Draw(canvas, image.Rect( offsetX, offsetY, offsetX+img.Bounds().Dx(), offsetY+img.Bounds().Dy(), ), img, img.Bounds().Min, draw.Over) default: } return canvas, func() {}, nil }) return r, nil }