mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-10-04 07:56:33 +08:00
168 lines
3.4 KiB
Go
168 lines
3.4 KiB
Go
package rtmp
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"github.com/AlexxIT/go2rtc/pkg/h264"
|
|
"github.com/AlexxIT/go2rtc/pkg/rtmpt"
|
|
"github.com/AlexxIT/go2rtc/pkg/streamer"
|
|
"github.com/deepch/vdk/av"
|
|
"github.com/deepch/vdk/codec/aacparser"
|
|
"github.com/deepch/vdk/codec/h264parser"
|
|
"github.com/deepch/vdk/format/rtmp"
|
|
"github.com/pion/rtp"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Conn for RTMP and RTMPT (flv over HTTP)
|
|
type Conn interface {
|
|
Streams() (streams []av.CodecData, err error)
|
|
ReadPacket() (pkt av.Packet, err error)
|
|
Close() (err error)
|
|
}
|
|
|
|
type Client struct {
|
|
streamer.Element
|
|
|
|
URI string
|
|
|
|
medias []*streamer.Media
|
|
tracks []*streamer.Track
|
|
|
|
conn Conn
|
|
closed bool
|
|
|
|
receive int
|
|
}
|
|
|
|
func NewClient(uri string) *Client {
|
|
return &Client{URI: uri}
|
|
}
|
|
|
|
func (c *Client) Dial() (err error) {
|
|
if strings.HasPrefix(c.URI, "http") {
|
|
c.conn, err = rtmpt.Dial(c.URI)
|
|
} else {
|
|
c.conn, err = rtmp.Dial(c.URI)
|
|
}
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// important to get SPS/PPS
|
|
streams, err := c.conn.Streams()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
for _, stream := range streams {
|
|
switch stream.Type() {
|
|
case av.H264:
|
|
info := stream.(h264parser.CodecData).RecordInfo
|
|
|
|
fmtp := fmt.Sprintf(
|
|
"profile-level-id=%02X%02X%02X;sprop-parameter-sets=%s,%s",
|
|
info.AVCProfileIndication, info.ProfileCompatibility, info.AVCLevelIndication,
|
|
base64.StdEncoding.EncodeToString(info.SPS[0]),
|
|
base64.StdEncoding.EncodeToString(info.PPS[0]),
|
|
)
|
|
|
|
codec := &streamer.Codec{
|
|
Name: streamer.CodecH264,
|
|
ClockRate: 90000,
|
|
FmtpLine: fmtp,
|
|
PayloadType: h264.PayloadTypeAVC,
|
|
}
|
|
|
|
media := &streamer.Media{
|
|
Kind: streamer.KindVideo,
|
|
Direction: streamer.DirectionSendonly,
|
|
Codecs: []*streamer.Codec{codec},
|
|
}
|
|
c.medias = append(c.medias, media)
|
|
|
|
track := &streamer.Track{
|
|
Codec: codec, Direction: media.Direction,
|
|
}
|
|
c.tracks = append(c.tracks, track)
|
|
|
|
case av.AAC:
|
|
// TODO: fix support
|
|
cd := stream.(aacparser.CodecData)
|
|
|
|
// a=fmtp:97 streamtype=5;profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1588
|
|
fmtp := fmt.Sprintf(
|
|
"config=%s",
|
|
hex.EncodeToString(cd.ConfigBytes),
|
|
)
|
|
|
|
codec := &streamer.Codec{
|
|
Name: streamer.CodecAAC,
|
|
ClockRate: uint32(cd.Config.SampleRate),
|
|
Channels: uint16(cd.Config.ChannelConfig),
|
|
FmtpLine: fmtp,
|
|
}
|
|
|
|
media := &streamer.Media{
|
|
Kind: streamer.KindAudio,
|
|
Direction: streamer.DirectionSendonly,
|
|
Codecs: []*streamer.Codec{codec},
|
|
}
|
|
c.medias = append(c.medias, media)
|
|
|
|
track := &streamer.Track{
|
|
Codec: codec, Direction: media.Direction,
|
|
}
|
|
c.tracks = append(c.tracks, track)
|
|
|
|
default:
|
|
fmt.Printf("[rtmp] unsupported codec %+v\n", stream)
|
|
}
|
|
}
|
|
|
|
c.Fire(streamer.StateReady)
|
|
|
|
return
|
|
}
|
|
|
|
func (c *Client) Handle() (err error) {
|
|
defer c.Fire(streamer.StateNull)
|
|
|
|
c.Fire(streamer.StatePlaying)
|
|
|
|
for {
|
|
var pkt av.Packet
|
|
pkt, err = c.conn.ReadPacket()
|
|
if err != nil {
|
|
if c.closed {
|
|
return nil
|
|
}
|
|
return
|
|
}
|
|
|
|
c.receive += len(pkt.Data)
|
|
|
|
track := c.tracks[int(pkt.Idx)]
|
|
|
|
// convert seconds to RTP timestamp
|
|
timestamp := uint32(pkt.Time * time.Duration(track.Codec.ClockRate) / time.Second)
|
|
|
|
packet := &rtp.Packet{
|
|
Header: rtp.Header{Timestamp: timestamp},
|
|
Payload: pkt.Data,
|
|
}
|
|
_ = track.WriteRTP(packet)
|
|
}
|
|
}
|
|
|
|
func (c *Client) Close() error {
|
|
if c.conn == nil {
|
|
return nil
|
|
}
|
|
c.closed = true
|
|
return c.conn.Close()
|
|
}
|