mirror of
https://github.com/pion/mediadevices.git
synced 2025-09-26 20:41:46 +08:00
239 lines
5.8 KiB
Go
239 lines
5.8 KiB
Go
package mediadevices
|
|
|
|
import (
|
|
"errors"
|
|
"math/rand"
|
|
"sync"
|
|
|
|
"github.com/pion/mediadevices/pkg/codec"
|
|
"github.com/pion/mediadevices/pkg/driver"
|
|
mio "github.com/pion/mediadevices/pkg/io"
|
|
"github.com/pion/webrtc/v2"
|
|
"github.com/pion/webrtc/v2/pkg/media"
|
|
)
|
|
|
|
// Tracker is an interface that represent MediaStreamTrack
|
|
// Reference: https://w3c.github.io/mediacapture-main/#mediastreamtrack
|
|
type Tracker interface {
|
|
Track() *webrtc.Track
|
|
LocalTrack() LocalTrack
|
|
Stop()
|
|
// OnEnded registers a handler to receive an error from the media stream track.
|
|
// If the error is already occured before registering, the handler will be
|
|
// immediately called.
|
|
OnEnded(func(error))
|
|
}
|
|
|
|
type LocalTrack interface {
|
|
WriteSample(s media.Sample) error
|
|
Codec() *webrtc.RTPCodec
|
|
ID() string
|
|
Kind() webrtc.RTPCodecType
|
|
}
|
|
|
|
type track struct {
|
|
localTrack LocalTrack
|
|
d driver.Driver
|
|
sample samplerFunc
|
|
encoder codec.ReadCloser
|
|
|
|
onErrorHandler func(error)
|
|
err error
|
|
mu sync.Mutex
|
|
endOnce sync.Once
|
|
}
|
|
|
|
func newTrack(opts *MediaDevicesOptions, d driver.Driver, constraints MediaTrackConstraints) (*track, error) {
|
|
var encoderBuilders []encoderBuilder
|
|
var rtpCodecs []*webrtc.RTPCodec
|
|
var buildSampler func(t LocalTrack) samplerFunc
|
|
var err error
|
|
|
|
err = d.Open()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch r := d.(type) {
|
|
case driver.VideoRecorder:
|
|
rtpCodecs = opts.codecs[webrtc.RTPCodecTypeVideo]
|
|
buildSampler = newVideoSampler
|
|
encoderBuilders, err = newVideoEncoderBuilders(r, constraints)
|
|
case driver.AudioRecorder:
|
|
rtpCodecs = opts.codecs[webrtc.RTPCodecTypeAudio]
|
|
buildSampler = func(t LocalTrack) samplerFunc {
|
|
return newAudioSampler(t, constraints.selectedMedia.Latency)
|
|
}
|
|
encoderBuilders, err = newAudioEncoderBuilders(r, constraints)
|
|
default:
|
|
err = errors.New("newTrack: invalid driver type")
|
|
}
|
|
|
|
if err != nil {
|
|
d.Close()
|
|
return nil, err
|
|
}
|
|
|
|
for _, builder := range encoderBuilders {
|
|
var matchedRTPCodec *webrtc.RTPCodec
|
|
for _, rtpCodec := range rtpCodecs {
|
|
if rtpCodec.Name == builder.name {
|
|
matchedRTPCodec = rtpCodec
|
|
break
|
|
}
|
|
}
|
|
|
|
if matchedRTPCodec == nil {
|
|
continue
|
|
}
|
|
|
|
localTrack, err := opts.trackGenerator(
|
|
matchedRTPCodec.PayloadType,
|
|
rand.Uint32(),
|
|
d.ID(),
|
|
matchedRTPCodec.Type.String(),
|
|
matchedRTPCodec,
|
|
)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
encoder, err := builder.build()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
t := track{
|
|
localTrack: localTrack,
|
|
sample: buildSampler(localTrack),
|
|
d: d,
|
|
encoder: encoder,
|
|
}
|
|
go t.start()
|
|
return &t, nil
|
|
}
|
|
|
|
d.Close()
|
|
return nil, errors.New("newTrack: failed to find a matching codec")
|
|
}
|
|
|
|
// OnEnded sets an error handler. When a track has been created and started, if an
|
|
// error occurs, handler will get called with the error given to the parameter.
|
|
func (t *track) OnEnded(handler func(error)) {
|
|
t.mu.Lock()
|
|
t.onErrorHandler = handler
|
|
err := t.err
|
|
t.mu.Unlock()
|
|
|
|
if err != nil && handler != nil {
|
|
// Already errored.
|
|
t.endOnce.Do(func() {
|
|
handler(err)
|
|
})
|
|
}
|
|
}
|
|
|
|
// onError is a callback when an error occurs
|
|
func (t *track) onError(err error) {
|
|
t.mu.Lock()
|
|
t.err = err
|
|
handler := t.onErrorHandler
|
|
t.mu.Unlock()
|
|
|
|
if handler != nil {
|
|
t.endOnce.Do(func() {
|
|
handler(err)
|
|
})
|
|
}
|
|
}
|
|
|
|
// start starts the data flow from the driver all the way to the localTrack
|
|
func (t *track) start() {
|
|
var n int
|
|
var err error
|
|
buff := make([]byte, 1024)
|
|
for {
|
|
n, err = t.encoder.Read(buff)
|
|
if err != nil {
|
|
if e, ok := err.(*mio.InsufficientBufferError); ok {
|
|
buff = make([]byte, 2*e.RequiredSize)
|
|
continue
|
|
}
|
|
|
|
t.onError(err)
|
|
return
|
|
}
|
|
|
|
if err := t.sample(buff[:n]); err != nil {
|
|
t.onError(err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stop stops the underlying driver and encoder
|
|
func (t *track) Stop() {
|
|
t.d.Close()
|
|
t.encoder.Close()
|
|
}
|
|
|
|
func (t *track) Track() *webrtc.Track {
|
|
return t.localTrack.(*webrtc.Track)
|
|
}
|
|
|
|
func (t *track) LocalTrack() LocalTrack {
|
|
return t.localTrack
|
|
}
|
|
|
|
// encoderBuilder is a generic encoder builder that acts as a delegator for codec.VideoEncoderBuilder and
|
|
// codec.AudioEncoderBuilder. The idea of having a delegator is to reduce redundant codes that are being
|
|
// duplicated for managing video and audio.
|
|
type encoderBuilder struct {
|
|
name string
|
|
build func() (codec.ReadCloser, error)
|
|
}
|
|
|
|
// newVideoEncoderBuilders transforms video given by VideoRecorder with the video transformer that is passed through
|
|
// constraints and create a list of generic encoder builders
|
|
func newVideoEncoderBuilders(vr driver.VideoRecorder, constraints MediaTrackConstraints) ([]encoderBuilder, error) {
|
|
r, err := vr.VideoRecord(constraints.selectedMedia)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if constraints.VideoTransform != nil {
|
|
r = constraints.VideoTransform(r)
|
|
}
|
|
|
|
encoderBuilders := make([]encoderBuilder, len(constraints.VideoEncoderBuilders))
|
|
for i, b := range constraints.VideoEncoderBuilders {
|
|
encoderBuilders[i].name = b.Name()
|
|
encoderBuilders[i].build = func() (codec.ReadCloser, error) {
|
|
return b.BuildVideoEncoder(r, constraints.selectedMedia)
|
|
}
|
|
}
|
|
return encoderBuilders, nil
|
|
}
|
|
|
|
// newAudioEncoderBuilders transforms audio given by AudioRecorder with the audio transformer that is passed through
|
|
// constraints and create a list of generic encoder builders
|
|
func newAudioEncoderBuilders(ar driver.AudioRecorder, constraints MediaTrackConstraints) ([]encoderBuilder, error) {
|
|
r, err := ar.AudioRecord(constraints.selectedMedia)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if constraints.AudioTransform != nil {
|
|
r = constraints.AudioTransform(r)
|
|
}
|
|
|
|
encoderBuilders := make([]encoderBuilder, len(constraints.AudioEncoderBuilders))
|
|
for i, b := range constraints.AudioEncoderBuilders {
|
|
encoderBuilders[i].name = b.Name()
|
|
encoderBuilders[i].build = func() (codec.ReadCloser, error) {
|
|
return b.BuildAudioEncoder(r, constraints.selectedMedia)
|
|
}
|
|
}
|
|
return encoderBuilders, nil
|
|
}
|