install go2rtc on bob
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
package miss
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/AlexxIT/go2rtc/pkg/h264"
|
||||
"github.com/AlexxIT/go2rtc/pkg/h264/annexb"
|
||||
"github.com/AlexxIT/go2rtc/pkg/h265"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
type Producer struct {
|
||||
core.Connection
|
||||
client *Client
|
||||
}
|
||||
|
||||
func Dial(rawURL string) (core.Producer, error) {
|
||||
client, err := NewClient(rawURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u, _ := url.Parse(rawURL)
|
||||
query := u.Query()
|
||||
|
||||
err = client.StartMedia(query.Get("channel"), query.Get("subtype"), query.Get("audio"))
|
||||
if err != nil {
|
||||
_ = client.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
medias, err := probe(client, query.Get("audio") != "0")
|
||||
if err != nil {
|
||||
_ = client.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Producer{
|
||||
Connection: core.Connection{
|
||||
ID: core.NewID(),
|
||||
FormatName: "xiaomi/miss",
|
||||
Protocol: client.Protocol(),
|
||||
RemoteAddr: client.RemoteAddr().String(),
|
||||
UserAgent: client.Version(),
|
||||
Medias: medias,
|
||||
Transport: client,
|
||||
},
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func probe(client *Client, audio bool) ([]*core.Media, error) {
|
||||
_ = client.SetDeadline(time.Now().Add(15 * time.Second))
|
||||
|
||||
var vcodec, acodec *core.Codec
|
||||
|
||||
for {
|
||||
pkt, err := client.ReadPacket()
|
||||
if err != nil {
|
||||
if vcodec != nil {
|
||||
err = fmt.Errorf("no audio")
|
||||
} else if acodec != nil {
|
||||
err = fmt.Errorf("no video")
|
||||
}
|
||||
return nil, fmt.Errorf("xiaomi: probe: %w", err)
|
||||
}
|
||||
|
||||
switch pkt.CodecID {
|
||||
case codecH264:
|
||||
if vcodec == nil {
|
||||
buf := annexb.EncodeToAVCC(pkt.Payload)
|
||||
if h264.NALUType(buf) == h264.NALUTypeSPS {
|
||||
vcodec = h264.AVCCToCodec(buf)
|
||||
}
|
||||
}
|
||||
case codecH265:
|
||||
if vcodec == nil {
|
||||
buf := annexb.EncodeToAVCC(pkt.Payload)
|
||||
if h265.NALUType(buf) == h265.NALUTypeVPS {
|
||||
vcodec = h265.AVCCToCodec(buf)
|
||||
}
|
||||
}
|
||||
case codecPCMA:
|
||||
if acodec == nil {
|
||||
acodec = &core.Codec{Name: core.CodecPCMA, ClockRate: pkt.SampleRate()}
|
||||
}
|
||||
case codecOPUS:
|
||||
if acodec == nil {
|
||||
acodec = &core.Codec{Name: core.CodecOpus, ClockRate: 48000, Channels: 2}
|
||||
}
|
||||
}
|
||||
|
||||
if vcodec != nil && (acodec != nil || !audio) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
_ = client.SetDeadline(time.Time{})
|
||||
|
||||
medias := []*core.Media{
|
||||
{
|
||||
Kind: core.KindVideo,
|
||||
Direction: core.DirectionRecvonly,
|
||||
Codecs: []*core.Codec{vcodec},
|
||||
},
|
||||
}
|
||||
|
||||
if acodec != nil {
|
||||
medias = append(medias, &core.Media{
|
||||
Kind: core.KindAudio,
|
||||
Direction: core.DirectionRecvonly,
|
||||
Codecs: []*core.Codec{acodec},
|
||||
})
|
||||
|
||||
medias = append(medias, &core.Media{
|
||||
Kind: core.KindAudio,
|
||||
Direction: core.DirectionSendonly,
|
||||
Codecs: []*core.Codec{acodec.Clone()},
|
||||
})
|
||||
}
|
||||
|
||||
return medias, nil
|
||||
}
|
||||
|
||||
const timestamp40ms = 48000 * 0.040
|
||||
|
||||
func (p *Producer) Start() error {
|
||||
var audioTS uint32
|
||||
|
||||
for {
|
||||
_ = p.client.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
pkt, err := p.client.ReadPacket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.Recv += len(pkt.Payload)
|
||||
|
||||
// TODO: rewrite this
|
||||
var name string
|
||||
var pkt2 *core.Packet
|
||||
|
||||
switch pkt.CodecID {
|
||||
case codecH264, codecH265:
|
||||
pkt2 = &core.Packet{
|
||||
Header: rtp.Header{
|
||||
SequenceNumber: uint16(pkt.Sequence),
|
||||
Timestamp: TimeToRTP(pkt.Timestamp, 90000),
|
||||
},
|
||||
Payload: annexb.EncodeToAVCC(pkt.Payload),
|
||||
}
|
||||
if pkt.CodecID == codecH264 {
|
||||
name = core.CodecH264
|
||||
} else {
|
||||
name = core.CodecH265
|
||||
}
|
||||
case codecPCMA:
|
||||
name = core.CodecPCMA
|
||||
pkt2 = &core.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
SequenceNumber: uint16(pkt.Sequence),
|
||||
Timestamp: audioTS,
|
||||
},
|
||||
Payload: pkt.Payload,
|
||||
}
|
||||
audioTS += uint32(len(pkt.Payload))
|
||||
case codecOPUS:
|
||||
name = core.CodecOpus
|
||||
pkt2 = &core.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
SequenceNumber: uint16(pkt.Sequence),
|
||||
Timestamp: audioTS,
|
||||
},
|
||||
Payload: pkt.Payload,
|
||||
}
|
||||
// known cameras sends packets with 40ms long
|
||||
audioTS += timestamp40ms
|
||||
}
|
||||
|
||||
for _, recv := range p.Receivers {
|
||||
if recv.Codec.Name == name {
|
||||
recv.WriteRTP(pkt2)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Producer) Stop() error {
|
||||
_ = p.client.StopMedia()
|
||||
return p.Connection.Stop()
|
||||
}
|
||||
|
||||
// TimeToRTP convert time in milliseconds to RTP time
|
||||
func TimeToRTP(timeMS, clockRate uint64) uint32 {
|
||||
return uint32(timeMS * clockRate / 1000)
|
||||
}
|
||||
Reference in New Issue
Block a user