Total rework RTMP client
This commit is contained in:
+10
-10
@@ -12,9 +12,9 @@ import (
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
URL string
|
||||
Transport
|
||||
|
||||
rd io.Reader
|
||||
URL string
|
||||
|
||||
medias []*core.Media
|
||||
receivers []*core.Receiver
|
||||
@@ -22,15 +22,15 @@ type Client struct {
|
||||
recv int
|
||||
}
|
||||
|
||||
func NewClient(rd io.Reader) *Client {
|
||||
return &Client{rd: rd}
|
||||
func NewClient(rd io.Reader) (*Client, error) {
|
||||
tr, err := NewTransport(rd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Client{Transport: tr}, nil
|
||||
}
|
||||
|
||||
func (c *Client) Describe() error {
|
||||
if err := c.ReadHeader(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Normal software sends:
|
||||
// 1. Video/audio flag in header
|
||||
// 2. MetaData as first tag (with video/audio codec info)
|
||||
@@ -45,7 +45,7 @@ func (c *Client) Describe() error {
|
||||
timeout := time.Now().Add(core.ProbeTimeout)
|
||||
|
||||
for (waitVideo || waitAudio) && time.Now().Before(timeout) {
|
||||
tagType, _, b, err := c.ReadTag()
|
||||
tagType, _, b, err := c.Transport.ReadTag()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -123,7 +123,7 @@ func (c *Client) Play() error {
|
||||
video, audio := core.VA(c.receivers)
|
||||
|
||||
for {
|
||||
tagType, timeMS, b, err := c.ReadTag()
|
||||
tagType, timeMS, b, err := c.Transport.ReadTag()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+45
-20
@@ -15,28 +15,25 @@ const (
|
||||
CodecAVC = 7
|
||||
)
|
||||
|
||||
func (c *Client) ReadHeader() error {
|
||||
b := make([]byte, 9)
|
||||
if _, err := io.ReadFull(c.rd, b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if string(b[:3]) != "FLV" {
|
||||
return errors.New("flv: wrong header")
|
||||
}
|
||||
|
||||
_ = b[4] // flags (skip because unsupported by Reolink cameras)
|
||||
|
||||
if skip := binary.BigEndian.Uint32(b[5:]) - 9; skip > 0 {
|
||||
if _, err := io.ReadFull(c.rd, make([]byte, skip)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
// Transport - it is recommended to implement io.Closer
|
||||
type Transport interface {
|
||||
ReadTag() (byte, uint32, []byte, error)
|
||||
}
|
||||
|
||||
func (c *Client) ReadTag() (byte, uint32, []byte, error) {
|
||||
// NewTransport - it is recommended to use bufio.Reader
|
||||
func NewTransport(rd io.Reader) (Transport, error) {
|
||||
c := &flv{rd: rd}
|
||||
if err := c.readHeader(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type flv struct {
|
||||
rd io.Reader
|
||||
}
|
||||
|
||||
func (c *flv) ReadTag() (byte, uint32, []byte, error) {
|
||||
// https://rtmp.veriskope.com/pdf/video_file_format_spec_v10.pdf
|
||||
b := make([]byte, 4+11)
|
||||
if _, err := io.ReadFull(c.rd, b); err != nil {
|
||||
@@ -57,6 +54,34 @@ func (c *Client) ReadTag() (byte, uint32, []byte, error) {
|
||||
return tagType, timeMS, b, nil
|
||||
}
|
||||
|
||||
func (c *flv) Close() error {
|
||||
if closer, ok := c.rd.(io.Closer); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *flv) readHeader() error {
|
||||
b := make([]byte, 9)
|
||||
if _, err := io.ReadFull(c.rd, b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if string(b[:3]) != "FLV" {
|
||||
return errors.New("flv: wrong header")
|
||||
}
|
||||
|
||||
_ = b[4] // flags (skip because unsupported by Reolink cameras)
|
||||
|
||||
if skip := binary.BigEndian.Uint32(b[5:]) - 9; skip > 0 {
|
||||
if _, err := io.ReadFull(c.rd, make([]byte, skip)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TimeToRTP(timeMS uint32, clockRate uint32) uint32 {
|
||||
return timeMS * clockRate / 1000
|
||||
}
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ func (c *Client) Start() error {
|
||||
}
|
||||
|
||||
func (c *Client) Stop() error {
|
||||
if closer, ok := c.rd.(io.Closer); ok {
|
||||
if closer, ok := c.Transport.(io.Closer); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user