mirror of
https://github.com/pion/mediadevices.git
synced 2025-10-30 03:21:55 +08:00
220 lines
4.0 KiB
Go
220 lines
4.0 KiB
Go
package mediadevices
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
"sync/atomic"
|
|
|
|
"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
|
|
Stop()
|
|
OnEnded(func(error))
|
|
}
|
|
|
|
type track struct {
|
|
t *webrtc.Track
|
|
s *sampler
|
|
|
|
onErrorHandler atomic.Value // func(error)
|
|
}
|
|
|
|
func newTrack(codecs []*webrtc.RTPCodec, d driver.Driver, codecName string) (*track, error) {
|
|
var selectedCodec *webrtc.RTPCodec
|
|
for _, c := range codecs {
|
|
if c.Name == codecName {
|
|
selectedCodec = c
|
|
break
|
|
}
|
|
}
|
|
if selectedCodec == nil {
|
|
return nil, fmt.Errorf("track: %s is not registered in media engine", codecName)
|
|
}
|
|
|
|
t, err := webrtc.NewTrack(
|
|
selectedCodec.PayloadType,
|
|
rand.Uint32(),
|
|
selectedCodec.Type.String(),
|
|
d.ID(),
|
|
selectedCodec,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &track{
|
|
t: t,
|
|
s: newSampler(t),
|
|
}, nil
|
|
}
|
|
|
|
func (t *track) OnEnded(handler func(error)) {
|
|
t.onErrorHandler.Store(handler)
|
|
}
|
|
|
|
func (t *track) onError(err error) {
|
|
handler := t.onErrorHandler.Load()
|
|
if handler != nil {
|
|
handler.(func(error))(err)
|
|
}
|
|
}
|
|
|
|
func (t *track) Track() *webrtc.Track {
|
|
return t.t
|
|
}
|
|
|
|
type videoTrack struct {
|
|
*track
|
|
d driver.Driver
|
|
constraints MediaTrackConstraints
|
|
encoder io.ReadCloser
|
|
}
|
|
|
|
var _ Tracker = &videoTrack{}
|
|
|
|
func newVideoTrack(codecs []*webrtc.RTPCodec, d driver.Driver, constraints MediaTrackConstraints) (*videoTrack, error) {
|
|
codecName := constraints.CodecName
|
|
t, err := newTrack(codecs, d, codecName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = d.Open()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
vr := d.(driver.VideoRecorder)
|
|
r, err := vr.VideoRecord(constraints.Media)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if constraints.VideoTransform != nil {
|
|
r = constraints.VideoTransform(r)
|
|
}
|
|
|
|
encoder, err := codec.BuildVideoEncoder(r, constraints.Media)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
vt := videoTrack{
|
|
track: t,
|
|
d: d,
|
|
constraints: constraints,
|
|
encoder: encoder,
|
|
}
|
|
|
|
go vt.start()
|
|
return &vt, nil
|
|
}
|
|
|
|
func (vt *videoTrack) start() {
|
|
var n int
|
|
var err error
|
|
buff := make([]byte, 1024)
|
|
for {
|
|
n, err = vt.encoder.Read(buff)
|
|
if err != nil {
|
|
if e, ok := err.(*mio.InsufficientBufferError); ok {
|
|
buff = make([]byte, 2*e.RequiredSize)
|
|
continue
|
|
}
|
|
|
|
vt.track.onError(err)
|
|
return
|
|
}
|
|
|
|
if err := vt.s.sample(buff[:n]); err != nil {
|
|
vt.track.onError(err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (vt *videoTrack) Stop() {
|
|
vt.d.Close()
|
|
vt.encoder.Close()
|
|
}
|
|
|
|
type audioTrack struct {
|
|
*track
|
|
d driver.Driver
|
|
constraints MediaTrackConstraints
|
|
encoder io.ReadCloser
|
|
}
|
|
|
|
var _ Tracker = &audioTrack{}
|
|
|
|
func newAudioTrack(codecs []*webrtc.RTPCodec, d driver.Driver, constraints MediaTrackConstraints) (*audioTrack, error) {
|
|
codecName := constraints.CodecName
|
|
t, err := newTrack(codecs, d, codecName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = d.Open()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ar := d.(driver.AudioRecorder)
|
|
reader, err := ar.AudioRecord(constraints.Media)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if constraints.AudioTransform != nil {
|
|
reader = constraints.AudioTransform(reader)
|
|
}
|
|
|
|
encoder, err := codec.BuildAudioEncoder(reader, constraints.Media)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
at := audioTrack{
|
|
track: t,
|
|
d: d,
|
|
constraints: constraints,
|
|
encoder: encoder,
|
|
}
|
|
go at.start()
|
|
return &at, nil
|
|
}
|
|
|
|
func (t *audioTrack) start() {
|
|
buff := make([]byte, 1024)
|
|
sampleSize := uint32(float64(t.constraints.SampleRate) * t.constraints.Latency.Seconds())
|
|
for {
|
|
n, err := t.encoder.Read(buff)
|
|
if err != nil {
|
|
t.track.onError(err)
|
|
return
|
|
}
|
|
|
|
if err := t.t.WriteSample(media.Sample{
|
|
Data: buff[:n],
|
|
Samples: sampleSize,
|
|
}); err != nil {
|
|
t.track.onError(err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t *audioTrack) Stop() {
|
|
t.d.Close()
|
|
t.encoder.Close()
|
|
}
|