mirror of
https://github.com/pion/mediadevices.git
synced 2025-09-26 20:41:46 +08:00
279 lines
6.7 KiB
Go
279 lines
6.7 KiB
Go
package mediadevices
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pion/webrtc/v2"
|
|
"github.com/pion/webrtc/v2/pkg/media"
|
|
|
|
"github.com/pion/mediadevices/pkg/codec"
|
|
"github.com/pion/mediadevices/pkg/driver"
|
|
_ "github.com/pion/mediadevices/pkg/driver/audiotest"
|
|
_ "github.com/pion/mediadevices/pkg/driver/videotest"
|
|
"github.com/pion/mediadevices/pkg/io/audio"
|
|
"github.com/pion/mediadevices/pkg/io/video"
|
|
"github.com/pion/mediadevices/pkg/prop"
|
|
)
|
|
|
|
func TestGetUserMedia(t *testing.T) {
|
|
videoParams := mockParams{
|
|
BaseParams: codec.BaseParams{
|
|
BitRate: 100000,
|
|
},
|
|
name: "MockVideo",
|
|
}
|
|
audioParams := mockParams{
|
|
BaseParams: codec.BaseParams{
|
|
BitRate: 32000,
|
|
},
|
|
name: "MockAudio",
|
|
}
|
|
md := NewMediaDevicesFromCodecs(
|
|
map[webrtc.RTPCodecType][]*webrtc.RTPCodec{
|
|
webrtc.RTPCodecTypeVideo: {
|
|
{Type: webrtc.RTPCodecTypeVideo, Name: "MockVideo", PayloadType: 1},
|
|
},
|
|
webrtc.RTPCodecTypeAudio: {
|
|
{Type: webrtc.RTPCodecTypeAudio, Name: "MockAudio", PayloadType: 2},
|
|
},
|
|
},
|
|
WithTrackGenerator(
|
|
func(_ uint8, _ uint32, id, _ string, codec *webrtc.RTPCodec) (
|
|
LocalTrack, error,
|
|
) {
|
|
return newMockTrack(codec, id), nil
|
|
},
|
|
),
|
|
)
|
|
constraints := MediaStreamConstraints{
|
|
Video: func(c *MediaTrackConstraints) {
|
|
c.Enabled = true
|
|
c.Width = prop.Int(640)
|
|
c.Height = prop.Int(480)
|
|
params := videoParams
|
|
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{¶ms}
|
|
},
|
|
Audio: func(c *MediaTrackConstraints) {
|
|
c.Enabled = true
|
|
params := audioParams
|
|
c.AudioEncoderBuilders = []codec.AudioEncoderBuilder{¶ms}
|
|
},
|
|
}
|
|
constraintsWrong := MediaStreamConstraints{
|
|
Video: func(c *MediaTrackConstraints) {
|
|
c.Enabled = true
|
|
c.Width = prop.Int(640)
|
|
c.Height = prop.Int(480)
|
|
params := videoParams
|
|
params.BitRate = 0
|
|
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{¶ms}
|
|
},
|
|
Audio: func(c *MediaTrackConstraints) {
|
|
c.Enabled = true
|
|
params := audioParams
|
|
c.AudioEncoderBuilders = []codec.AudioEncoderBuilder{¶ms}
|
|
},
|
|
}
|
|
|
|
// GetUserMedia with broken parameters
|
|
ms, err := md.GetUserMedia(constraintsWrong)
|
|
if err == nil {
|
|
t.Fatal("Expected error, but got nil")
|
|
}
|
|
|
|
// GetUserMedia with correct parameters
|
|
ms, err = md.GetUserMedia(constraints)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
tracks := ms.GetTracks()
|
|
if l := len(tracks); l != 2 {
|
|
t.Fatalf("Number of the tracks is expected to be 2, got %d", l)
|
|
}
|
|
for _, track := range tracks {
|
|
track.OnEnded(func(err error) {
|
|
if err != io.EOF {
|
|
t.Errorf("OnEnded called: %v", err)
|
|
}
|
|
})
|
|
}
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
for _, track := range tracks {
|
|
track.Stop()
|
|
}
|
|
|
|
// Stop and retry GetUserMedia
|
|
ms, err = md.GetUserMedia(constraints)
|
|
if err != nil {
|
|
t.Fatalf("Failed to GetUserMedia after the previsous tracks stopped: %v", err)
|
|
}
|
|
tracks = ms.GetTracks()
|
|
if l := len(tracks); l != 2 {
|
|
t.Fatalf("Number of the tracks is expected to be 2, got %d", l)
|
|
}
|
|
for _, track := range tracks {
|
|
track.OnEnded(func(err error) {
|
|
if err != io.EOF {
|
|
t.Errorf("OnEnded called: %v", err)
|
|
}
|
|
})
|
|
}
|
|
time.Sleep(50 * time.Millisecond)
|
|
for _, track := range tracks {
|
|
track.Stop()
|
|
}
|
|
}
|
|
|
|
type mockTrack struct {
|
|
codec *webrtc.RTPCodec
|
|
id string
|
|
}
|
|
|
|
func newMockTrack(codec *webrtc.RTPCodec, id string) *mockTrack {
|
|
return &mockTrack{
|
|
codec: codec,
|
|
id: id,
|
|
}
|
|
}
|
|
|
|
func (t *mockTrack) WriteSample(s media.Sample) error {
|
|
return nil
|
|
}
|
|
|
|
func (t *mockTrack) Codec() *webrtc.RTPCodec {
|
|
return t.codec
|
|
}
|
|
|
|
func (t *mockTrack) ID() string {
|
|
return t.id
|
|
}
|
|
|
|
func (t *mockTrack) Kind() webrtc.RTPCodecType {
|
|
return t.codec.Type
|
|
}
|
|
|
|
type mockParams struct {
|
|
codec.BaseParams
|
|
name string
|
|
}
|
|
|
|
func (params *mockParams) RTPCodec() *codec.RTPCodec {
|
|
rtpCodec := codec.NewRTPH264Codec(90000)
|
|
rtpCodec.Name = params.name
|
|
return rtpCodec
|
|
}
|
|
|
|
func (params *mockParams) BuildVideoEncoder(r video.Reader, p prop.Media) (codec.ReadCloser, error) {
|
|
if params.BitRate == 0 {
|
|
// This is a dummy error to test the failure condition.
|
|
return nil, errors.New("wrong codec parameter")
|
|
}
|
|
return &mockVideoCodec{
|
|
r: r,
|
|
closed: make(chan struct{}),
|
|
}, nil
|
|
}
|
|
|
|
func (params *mockParams) BuildAudioEncoder(r audio.Reader, p prop.Media) (codec.ReadCloser, error) {
|
|
return &mockAudioCodec{
|
|
r: r,
|
|
closed: make(chan struct{}),
|
|
}, nil
|
|
}
|
|
|
|
type mockCodec struct{}
|
|
|
|
func (e *mockCodec) SetBitRate(b int) error {
|
|
return nil
|
|
}
|
|
|
|
func (e *mockCodec) ForceKeyFrame() error {
|
|
return nil
|
|
}
|
|
|
|
type mockVideoCodec struct {
|
|
mockCodec
|
|
r video.Reader
|
|
closed chan struct{}
|
|
}
|
|
|
|
func (m *mockVideoCodec) Read() ([]byte, func(), error) {
|
|
if _, _, err := m.r.Read(); err != nil {
|
|
return nil, func() {}, err
|
|
}
|
|
return make([]byte, 20), func() {}, nil
|
|
}
|
|
|
|
func (m *mockVideoCodec) Close() error { return nil }
|
|
|
|
type mockAudioCodec struct {
|
|
mockCodec
|
|
r audio.Reader
|
|
closed chan struct{}
|
|
}
|
|
|
|
func (m *mockAudioCodec) Read() ([]byte, func(), error) {
|
|
if _, _, err := m.r.Read(); err != nil {
|
|
return nil, func() {}, err
|
|
}
|
|
return make([]byte, 20), func() {}, nil
|
|
}
|
|
func (m *mockAudioCodec) Close() error { return nil }
|
|
|
|
func TestSelectBestDriverConstraintsResultIsSetProperly(t *testing.T) {
|
|
filterFn := driver.FilterVideoRecorder()
|
|
drivers := driver.GetManager().Query(filterFn)
|
|
if len(drivers) == 0 {
|
|
t.Fatal("expect to get at least 1 driver")
|
|
}
|
|
|
|
driver := drivers[0]
|
|
err := driver.Open()
|
|
if err != nil {
|
|
t.Fatal("expect to open driver successfully")
|
|
}
|
|
defer driver.Close()
|
|
|
|
if len(driver.Properties()) == 0 {
|
|
t.Fatal("expect to get at least 1 property")
|
|
}
|
|
expectedProp := driver.Properties()[0]
|
|
// Since this is a continuous value, bestConstraints should be set with the value that user specified
|
|
expectedProp.FrameRate = 30.0
|
|
|
|
wantConstraints := MediaTrackConstraints{
|
|
MediaConstraints: prop.MediaConstraints{
|
|
VideoConstraints: prop.VideoConstraints{
|
|
// By reducing the width from the driver by a tiny amount, this property should be chosen.
|
|
// At the same time, we'll be able to find out if the return constraints will be properly set
|
|
// to the best constraints.
|
|
Width: prop.Int(expectedProp.Width - 1),
|
|
Height: prop.Int(expectedProp.Width),
|
|
FrameFormat: prop.FrameFormat(expectedProp.FrameFormat),
|
|
FrameRate: prop.Float(30.0),
|
|
},
|
|
},
|
|
}
|
|
|
|
bestDriver, bestConstraints, err := selectBestDriver(filterFn, wantConstraints)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if driver != bestDriver {
|
|
t.Fatal("best driver is not expected")
|
|
}
|
|
|
|
s := bestConstraints.selectedMedia
|
|
if s.Width != expectedProp.Width ||
|
|
s.Height != expectedProp.Height ||
|
|
s.FrameFormat != expectedProp.FrameFormat ||
|
|
s.FrameRate != expectedProp.FrameRate {
|
|
t.Fatalf("failed to return best constraints\nexpected:\n%v\n\ngot:\n%v", expectedProp, bestConstraints.selectedMedia)
|
|
}
|
|
}
|