Compare commits

...

4 Commits

Author SHA1 Message Date
renovate[bot]
be5f684ea6 Update module github.com/pion/webrtc/v3 to v3.1.10 (#363)
Generated by Renovate Bot

Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-21 23:08:53 +09:00
Atsushi Watanabe
a88c2daf89 Allow double close of codecs (#364)
Video/AudioTrack.NewRTPReader() internally closes encoder on error.
It caused double free if user closes reader after error.
2021-11-21 22:57:50 +09:00
Atsushi Watanabe
1f313a9d61 Add simple read test to codecs (#367) 2021-11-20 15:42:24 +09:00
Atsushi Watanabe
19eaf375ff Fix build test target without CGO (#366)
Fix `go: warning: "pkg/..." matched no packages`
2021-11-20 13:39:57 +09:00
16 changed files with 461 additions and 14 deletions

View File

@@ -25,6 +25,7 @@ codec_list := $(shell ls $(codec_dir)/*/Makefile)
codec_list := $(codec_list:$(codec_dir)/%/Makefile=%)
targets := $(foreach codec, $(codec_list), $(addprefix $(cmd_build)-$(codec)-, $(supported_platforms)))
pkgs_without_mmal := $(shell go list ./... | grep -v mmal)
pkgs_without_cgo := $(shell go list ./... | grep -v pkg/codec | grep -v pkg/driver | grep -v pkg/avfoundation)
define BUILD_TEMPLATE
ifneq (,$$(findstring $(2)-$(3),$$(supported_platforms)))
@@ -74,7 +75,7 @@ $(cmd_test):
go vet $(pkgs_without_mmal)
go build $(pkgs_without_mmal)
# go build without CGO
CGO_ENABLED=0 go build . pkg/...
CGO_ENABLED=0 go build $(pkgs_without_cgo)
# go build with CGO
CGO_ENABLED=1 go build $(pkgs_without_mmal)
$(MAKE) --directory=$(examples_dir)

2
go.mod
View File

@@ -9,7 +9,7 @@ require (
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329
github.com/pion/logging v0.2.2
github.com/pion/rtp v1.7.4
github.com/pion/webrtc/v3 v3.1.6
github.com/pion/webrtc/v3 v3.1.10
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)

22
go.sum
View File

@@ -44,13 +44,13 @@ github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvw
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg=
github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0=
github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg=
github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E=
github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ=
github.com/pion/dtls/v2 v2.0.9/go.mod h1:O0Wr7si/Zj5/EBFlDzDd6UtVxx25CE1r7XM7BQKYQho=
github.com/pion/dtls/v2 v2.0.10 h1:wgys7gPR1NMbWjmjJ3CW7lkUGaun8djgH8nahpNLnxI=
github.com/pion/dtls/v2 v2.0.10/go.mod h1:00OxfeCRWHShcqT9jx8pKKmBWuTt0NCZoVPCaC4VKvU=
github.com/pion/ice/v2 v2.1.13 h1:/YNYcIw56LT/whwuzkTnrprcRnapj2ZNqUsR0W8elmo=
github.com/pion/ice/v2 v2.1.13/go.mod h1:ovgYHUmwYLlRvcCLI67PnQ5YGe+upXZbGgllBDG/ktU=
github.com/pion/ice/v2 v2.1.14 h1:nD9GZs3MiR1/dPa5EiMRMe8hLBG3/qqCdx/hTS2g8VE=
github.com/pion/ice/v2 v2.1.14/go.mod h1:ovgYHUmwYLlRvcCLI67PnQ5YGe+upXZbGgllBDG/ktU=
github.com/pion/interceptor v0.1.0 h1:SlXKaDlEvSl7cr4j8fJykzVz4UdH+7UDtcvx+u01wLU=
github.com/pion/interceptor v0.1.0/go.mod h1:j5NIl3tJJPB3u8+Z2Xz8MZs/VV6rc+If9mXEKNuFmEM=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
@@ -60,15 +60,15 @@ github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0=
github.com/pion/rtcp v1.2.8 h1:Cys8X6r0xxU65ESTmXkqr8eU1Q1Wx+lNkoZCUH4zD7E=
github.com/pion/rtcp v1.2.8/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
github.com/pion/rtcp v1.2.9 h1:1ujStwg++IOLIEoOiIQ2s+qBuJ1VN81KW+9pMPsif+U=
github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
github.com/pion/rtp v1.7.0/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/rtp v1.7.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/rtp v1.7.4 h1:4dMbjb1SuynU5OpA3kz1zHK+u+eOCQjW3MAeVHf1ODA=
github.com/pion/rtp v1.7.4/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
github.com/pion/sctp v1.7.12 h1:GsatLufywVruXbZZT1CKg+Jr8ZTkwiPnmUC/oO9+uuY=
github.com/pion/sctp v1.7.12/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
github.com/pion/sctp v1.8.0 h1:6erMF2qmQwXr+0iB1lm0AUSmDr9LdmpaBzgSVAEgehw=
github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8=
github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk=
github.com/pion/srtp/v2 v2.0.5 h1:ks3wcTvIUE/GHndO3FAvROQ9opy0uLELpwHJaQ1yqhQ=
@@ -83,10 +83,8 @@ github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA=
github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw=
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
github.com/pion/webrtc/v3 v3.1.6 h1:r6WQRayW2SyKTYeRl4vBUQ43XXp7RSwBJ9+tNQWI5zQ=
github.com/pion/webrtc/v3 v3.1.6/go.mod h1:tkwdWNYdZhc200hH/wPx6AtNo/rcTAM6MICA6dg1je8=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pion/webrtc/v3 v3.1.10 h1:DO99F6/X1HrQho3LxTWHPjI3c388btBf56lR5UNRNNk=
github.com/pion/webrtc/v3 v3.1.10/go.mod h1:eL2HHZOvX+W+Q+lenuidTrWfMD9gG3aobtGcCqJ5G48=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=

View File

@@ -0,0 +1,121 @@
// Package codectest provides shared test for codec implementations.
package codectest
import (
"image"
"io"
"testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/mediadevices/pkg/wave"
)
func assertNoPanic(t *testing.T, fn func() error, msg string) error {
defer func() {
if r := recover(); r != nil {
t.Errorf("panic: %v: %s", r, msg)
}
}()
return fn()
}
func AudioEncoderSimpleReadTest(t *testing.T, c codec.AudioEncoderBuilder, p prop.Media, w wave.Audio) {
var eof bool
enc, err := c.BuildAudioEncoder(audio.ReaderFunc(func() (wave.Audio, func(), error) {
if eof {
return nil, nil, io.EOF
}
return w, nil, nil
}), p)
if err != nil {
t.Fatal(err)
}
for i := 0; i < 16; i++ {
b, release, err := enc.Read()
if err != nil {
t.Fatal(err)
}
if len(b) == 0 {
t.Fatal("Encoded frame is empty")
}
release()
}
eof = true
if _, _, err := enc.Read(); err != io.EOF {
t.Fatalf("Expected EOF, got %v", err)
}
if err := enc.Close(); err != nil {
t.Fatal(err)
}
}
func VideoEncoderSimpleReadTest(t *testing.T, c codec.VideoEncoderBuilder, p prop.Media, img image.Image) {
var eof bool
enc, err := c.BuildVideoEncoder(video.ReaderFunc(func() (image.Image, func(), error) {
if eof {
return nil, nil, io.EOF
}
return img, nil, nil
}), p)
if err != nil {
t.Fatal(err)
}
for i := 0; i < 16; i++ {
b, release, err := enc.Read()
if err != nil {
t.Fatal(err)
}
if len(b) == 0 {
t.Errorf("Encoded frame is empty (%d)", i)
}
release()
}
eof = true
if _, _, err := enc.Read(); err != io.EOF {
t.Fatalf("Expected EOF, got %v", err)
}
if err := enc.Close(); err != nil {
t.Fatal(err)
}
}
func AudioEncoderCloseTwiceTest(t *testing.T, c codec.AudioEncoderBuilder, p prop.Media) {
enc, err := c.BuildAudioEncoder(audio.ReaderFunc(func() (wave.Audio, func(), error) {
return nil, nil, io.EOF
}), p)
if err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, enc.Close, "on first Close()"); err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, enc.Close, "on second Close()"); err != nil {
t.Fatal(err)
}
}
func VideoEncoderCloseTwiceTest(t *testing.T, c codec.VideoEncoderBuilder, p prop.Media) {
enc, err := c.BuildVideoEncoder(video.ReaderFunc(func() (image.Image, func(), error) {
return nil, nil, io.EOF
}), p)
if err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, enc.Close, "on first Close()"); err != nil {
t.Fatal(err)
}
if err := assertNoPanic(t, enc.Close, "on second Close()"); err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,46 @@
package mmal
import (
"image"
"testing"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop"
)
func TestEncoder(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderSimpleReadTest(t, &p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
t.Run("CloseTwice", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderCloseTwiceTest(t, &p, prop.Media{
Video: prop.Video{
Width: 640,
Height: 480,
FrameRate: 30,
FrameFormat: frame.FormatI420,
},
})
})
}

View File

@@ -92,6 +92,10 @@ func (e *encoder) Close() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.closed {
return nil
}
e.closed = true
var rv C.int

View File

@@ -0,0 +1,46 @@
package openh264
import (
"image"
"testing"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop"
)
func TestEncoder(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderSimpleReadTest(t, &p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
t.Run("CloseTwice", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderCloseTwiceTest(t, &p, prop.Media{
Video: prop.Video{
Width: 640,
Height: 480,
FrameRate: 30,
FrameFormat: frame.FormatI420,
},
})
})
}

View File

@@ -126,6 +126,9 @@ func (e *encoder) ForceKeyFrame() error {
}
func (e *encoder) Close() error {
if e.engine == nil {
return nil
}
C.opus_encoder_destroy(e.engine)
e.engine = nil
return nil

View File

@@ -0,0 +1,43 @@
package opus
import (
"testing"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/mediadevices/pkg/wave"
)
func TestEncoder(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.AudioEncoderSimpleReadTest(t, &p,
prop.Media{
Audio: prop.Audio{
SampleRate: 48000,
ChannelCount: 2,
},
},
wave.NewInt16Interleaved(wave.ChunkInfo{
Len: 960,
SamplingRate: 48000,
Channels: 2,
}),
)
})
t.Run("CloseTwice", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
codectest.AudioEncoderCloseTwiceTest(t, &p, prop.Media{
Audio: prop.Audio{
SampleRate: 48000,
ChannelCount: 2,
},
})
})
}

View File

@@ -1,3 +1,4 @@
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build dragonfly freebsd linux netbsd openbsd solaris
// Package vaapi implements hardware accelerated codecs.

View File

@@ -0,0 +1,70 @@
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build dragonfly freebsd linux netbsd openbsd solaris
package vaapi
import (
"errors"
"image"
"os"
"testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop"
)
func TestEncoder(t *testing.T) {
if _, err := os.Stat("/dev/dri/card0"); errors.Is(err, os.ErrNotExist) {
t.Skip("/dev/dri/card0 not found")
}
for name, factory := range map[string]func() (codec.VideoEncoderBuilder, error){
"VP8": func() (codec.VideoEncoderBuilder, error) {
p, err := NewVP8Params()
return &p, err
},
"VP9": func() (codec.VideoEncoderBuilder, error) {
p, err := NewVP9Params()
return &p, err
},
} {
factory := factory
t.Run(name, func(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := factory()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderSimpleReadTest(t, p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
t.Run("CloseTwice", func(t *testing.T) {
p, err := factory()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderCloseTwiceTest(t, p, prop.Media{
Video: prop.Video{
Width: 640,
Height: 480,
FrameRate: 30,
FrameFormat: frame.FormatI420,
},
})
})
})
}
}

View File

@@ -1,3 +1,4 @@
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build dragonfly freebsd linux netbsd openbsd solaris
package vaapi
@@ -552,6 +553,10 @@ func (e *encoderVP8) Close() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.closed {
return nil
}
C.vaDestroySurfaces(e.display, &e.surfs[0], C.int(len(e.surfs)))
C.vaDestroyContext(e.display, e.ctxID)
C.vaDestroyConfig(e.display, e.confID)

View File

@@ -1,3 +1,4 @@
//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build dragonfly freebsd linux netbsd openbsd solaris
package vaapi
@@ -487,6 +488,10 @@ func (e *encoderVP9) Close() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.closed {
return nil
}
C.vaDestroySurfaces(e.display, &e.surfs[0], C.int(len(e.surfs)))
C.vaDestroyContext(e.display, e.ctxID)
C.vaDestroyConfig(e.display, e.confID)

View File

@@ -310,6 +310,10 @@ func (e *encoder) Close() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.closed {
return nil
}
e.closed = true
C.free(unsafe.Pointer(e.raw))

View File

@@ -7,11 +7,63 @@ import (
"testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
)
func TestEncoder(t *testing.T) {
for name, factory := range map[string]func() (codec.VideoEncoderBuilder, error){
"VP8": func() (codec.VideoEncoderBuilder, error) {
p, err := NewVP8Params()
return &p, err
},
"VP9": func() (codec.VideoEncoderBuilder, error) {
p, err := NewVP9Params()
p.LagInFrames = 0
return &p, err
},
} {
factory := factory
t.Run(name, func(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := factory()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderSimpleReadTest(t, p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
t.Run("CloseTwice", func(t *testing.T) {
p, err := factory()
if err != nil {
t.Fatal(err)
}
codectest.VideoEncoderCloseTwiceTest(t, p, prop.Media{
Video: prop.Video{
Width: 640,
Height: 480,
FrameRate: 30,
FrameFormat: frame.FormatI420,
},
})
})
})
}
}
func TestImageSizeChange(t *testing.T) {
for name, factory := range map[string]func() (codec.VideoEncoderBuilder, error){
"VP8": func() (codec.VideoEncoderBuilder, error) {

View File

@@ -0,0 +1,48 @@
package x264
import (
"image"
"testing"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop"
)
func TestEncoder(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
p.BitRate = 200000
codectest.VideoEncoderSimpleReadTest(t, &p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
t.Run("CloseTwice", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
p.BitRate = 200000
codectest.VideoEncoderCloseTwiceTest(t, &p, prop.Media{
Video: prop.Video{
Width: 640,
Height: 480,
FrameRate: 30,
FrameFormat: frame.FormatI420,
},
})
})
}