2022-09-28 06:14:26 +00:00
|
|
|
package video
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"image"
|
|
|
|
"image/color"
|
|
|
|
"io"
|
2022-10-12 03:23:14 +00:00
|
|
|
"time"
|
2022-09-28 06:14:26 +00:00
|
|
|
|
|
|
|
"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
|
|
|
|
|
2022-10-12 03:23:14 +00:00
|
|
|
type Frame struct {
|
|
|
|
Time time.Time
|
|
|
|
Image io.ReadCloser
|
|
|
|
}
|
|
|
|
|
2022-09-28 06:14:26 +00:00
|
|
|
type PPMStreamDriver struct {
|
|
|
|
Height, Width int
|
|
|
|
FPS float32
|
2022-10-12 03:23:14 +00:00
|
|
|
PPMImage <-chan Frame
|
2022-09-28 06:14:26 +00:00
|
|
|
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) {
|
2022-09-30 04:24:32 +00:00
|
|
|
logrus.Debug(p)
|
2022-10-02 10:50:08 +00:00
|
|
|
canvas := image.NewYCbCr(
|
|
|
|
image.Rect(0, 0, p.Width, p.Height),
|
|
|
|
image.YCbCrSubsampleRatio420,
|
|
|
|
)
|
2022-10-12 03:23:14 +00:00
|
|
|
|
|
|
|
Y, Cb, Cr := color.RGBToYCbCr(0, 0, 0)
|
|
|
|
for y := 0; y < canvas.Rect.Dy(); y++ {
|
|
|
|
for x := 0; x < canvas.Rect.Dx(); x++ {
|
|
|
|
canvas.Y[canvas.YOffset(x, y)] = Y
|
|
|
|
canvas.Cb[canvas.COffset(x, y)] = Cb
|
|
|
|
canvas.Cr[canvas.COffset(x, y)] = Cr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-28 06:14:26 +00:00
|
|
|
r := video.ReaderFunc(func() (img image.Image, release func(), err error) {
|
|
|
|
select {
|
|
|
|
case <-v.closed:
|
|
|
|
return nil, func() {}, io.EOF
|
2022-10-02 10:50:08 +00:00
|
|
|
case ppmF := <-v.PPMImage:
|
2022-10-12 03:23:14 +00:00
|
|
|
defer ppmF.Image.Close()
|
2022-09-28 06:14:26 +00:00
|
|
|
|
2022-10-12 03:23:14 +00:00
|
|
|
img, _, err := image.Decode(ppmF.Image)
|
2022-09-28 06:14:26 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, func() {}, err
|
|
|
|
}
|
|
|
|
|
2022-09-28 06:29:54 +00:00
|
|
|
// resize image and draw it to canvas
|
2022-10-12 03:23:14 +00:00
|
|
|
//resized := resize.Resize(uint(p.Width), uint(p.Height), img, resize.Bilinear)
|
|
|
|
offsetX := (canvas.Rect.Dx() - img.Bounds().Dx()) / 2
|
|
|
|
offsetY := (canvas.Rect.Dy() - img.Bounds().Dy()) / 2
|
|
|
|
|
|
|
|
for y := 0; y < img.Bounds().Dy(); y++ {
|
|
|
|
for x := 0; x < img.Bounds().Dx(); x++ {
|
|
|
|
r, g, b, _ := img.At(x, y).RGBA()
|
|
|
|
|
2022-09-28 06:14:26 +00:00
|
|
|
Y, Cb, Cr := color.RGBToYCbCr(uint8(r), uint8(g), uint8(b))
|
|
|
|
|
2022-10-12 03:23:14 +00:00
|
|
|
_x := offsetX + x
|
|
|
|
_y := offsetY + y
|
|
|
|
|
|
|
|
canvas.Y[canvas.YOffset(_x, _y)] = Y
|
|
|
|
canvas.Cb[canvas.COffset(_x, _y)] = Cb
|
|
|
|
canvas.Cr[canvas.COffset(_x, _y)] = Cr
|
2022-09-28 06:14:26 +00:00
|
|
|
}
|
|
|
|
}
|
2022-10-11 00:31:16 +00:00
|
|
|
default:
|
2022-09-28 06:14:26 +00:00
|
|
|
}
|
|
|
|
return canvas, func() {}, nil
|
|
|
|
})
|
|
|
|
return r, nil
|
|
|
|
}
|