Add RTP/MJPEG decoder and encoder (#165)

* rename format.JPEG into format.MJPEG

* add examples/client-publish-format-mjpeg

* add rtp/mjpeg decoder and encoder
This commit is contained in:
Alessandro Ros
2022-12-27 13:43:23 +01:00
committed by GitHub
parent cd8cc2cdb1
commit 91f18ab6d9
53 changed files with 2527 additions and 174 deletions

View File

@@ -46,7 +46,7 @@ Features:
* Generate RTCP sender reports * Generate RTCP sender reports
* Utilities * Utilities
* Encode/decode format-specific frames into/from RTP packets. The following formats are supported: * Encode/decode format-specific frames into/from RTP packets. The following formats are supported:
* Video: H264, H265, VP8, VP9 * Video: H264, H265, MJPEG, VP8, VP9
* Audio: G711 (PCMA, PCMU), G722, LPCM, MPEG4 Audio (AAC), Opus * Audio: G711 (PCMA, PCMU), G722, LPCM, MPEG4 Audio (AAC), Opus
* Parse RTSP elements: requests, responses, SDP * Parse RTSP elements: requests, responses, SDP
* Parse H264 elements and formats: Annex-B, AVCC, anti-competition, DTS * Parse H264 elements and formats: Annex-B, AVCC, anti-competition, DTS
@@ -72,6 +72,7 @@ Features:
* [client-read-format-h264-save-to-disk](examples/client-read-format-h264-save-to-disk/main.go) * [client-read-format-h264-save-to-disk](examples/client-read-format-h264-save-to-disk/main.go)
* [client-read-format-h265](examples/client-read-format-h265/main.go) * [client-read-format-h265](examples/client-read-format-h265/main.go)
* [client-read-format-lpcm](examples/client-read-format-lpcm/main.go) * [client-read-format-lpcm](examples/client-read-format-lpcm/main.go)
* [client-read-format-mjpeg](examples/client-read-format-mjpeg/main.go)
* [client-read-format-mpeg4audio](examples/client-read-format-mpeg4audio/main.go) * [client-read-format-mpeg4audio](examples/client-read-format-mpeg4audio/main.go)
* [client-read-format-opus](examples/client-read-format-opus/main.go) * [client-read-format-opus](examples/client-read-format-opus/main.go)
* [client-read-format-vp8](examples/client-read-format-vp8/main.go) * [client-read-format-vp8](examples/client-read-format-vp8/main.go)
@@ -83,6 +84,7 @@ Features:
* [client-publish-format-h264](examples/client-publish-format-h264/main.go) * [client-publish-format-h264](examples/client-publish-format-h264/main.go)
* [client-publish-format-h265](examples/client-publish-format-h265/main.go) * [client-publish-format-h265](examples/client-publish-format-h265/main.go)
* [client-publish-format-lpcm](examples/client-publish-format-lpcm/main.go) * [client-publish-format-lpcm](examples/client-publish-format-lpcm/main.go)
* [client-publish-format-mjpeg](examples/client-publish-format-mjpeg/main.go)
* [client-publish-format-mpeg4audio](examples/client-publish-format-mpeg4audio/main.go) * [client-publish-format-mpeg4audio](examples/client-publish-format-mpeg4audio/main.go)
* [client-publish-format-opus](examples/client-publish-format-opus/main.go) * [client-publish-format-opus](examples/client-publish-format-opus/main.go)
* [client-publish-format-vp8](examples/client-publish-format-vp8/main.go) * [client-publish-format-vp8](examples/client-publish-format-vp8/main.go)

View File

@@ -0,0 +1,72 @@
package main
import (
"log"
"net"
"github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/media"
"github.com/pion/rtp"
)
// This example shows how to
// 1. generate RTP/MJPEG packets with GStreamer
// 2. connect to a RTSP server, announce an MJPEG media
// 3. route the packets from GStreamer to the server
func main() {
// open a listener to receive RTP/MJPEG packets
pc, err := net.ListenPacket("udp", "localhost:9000")
if err != nil {
panic(err)
}
defer pc.Close()
log.Println("Waiting for a RTP/MJPEG stream on UDP port 9000 - you can send one with GStreamer:\n" +
"gst-launch-1.0 videotestsrc ! video/x-raw,width=1920,height=1080,format=I420" +
" ! jpegenc ! rtpjpegpay ! udpsink host=127.0.0.1 port=9000")
// wait for first packet
buf := make([]byte, 2048)
n, _, err := pc.ReadFrom(buf)
if err != nil {
panic(err)
}
log.Println("stream connected")
// create a media that contains a MJPEG format
medias := media.Medias{&media.Media{
Type: media.TypeVideo,
Formats: []format.Format{&format.MJPEG{}},
}}
// connect to the server and start recording the media
c := gortsplib.Client{}
err = c.StartRecording("rtsp://localhost:8554/mystream", medias)
if err != nil {
panic(err)
}
defer c.Close()
var pkt rtp.Packet
for {
// parse RTP packet
err = pkt.Unmarshal(buf[:n])
if err != nil {
panic(err)
}
// route RTP packet to the server
err = c.WritePacketRTP(medias[0], &pkt)
if err != nil {
panic(err)
}
// read another RTP packet from source
n, _, err = pc.ReadFrom(buf)
if err != nil {
panic(err)
}
}
}

View File

@@ -57,6 +57,7 @@ func main() {
// decode a G711 packet from the RTP packet // decode a G711 packet from the RTP packet
op, _, err := rtpDec.Decode(pkt) op, _, err := rtpDec.Decode(pkt)
if err != nil { if err != nil {
log.Printf("ERR: %v", err)
return return
} }

View File

@@ -57,6 +57,7 @@ func main() {
// decode a G722 packet from the RTP packet // decode a G722 packet from the RTP packet
op, _, err := rtpDec.Decode(pkt) op, _, err := rtpDec.Decode(pkt)
if err != nil { if err != nil {
log.Printf("ERR: %v", err)
return return
} }

View File

@@ -10,6 +10,7 @@ import (
"github.com/aler9/gortsplib/v2" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format" "github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/formatdecenc/rtph264"
"github.com/aler9/gortsplib/v2/pkg/url" "github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -101,6 +102,9 @@ func main() {
// convert RTP packets into NALUs // convert RTP packets into NALUs
nalus, _, err := rtpDec.Decode(pkt) nalus, _, err := rtpDec.Decode(pkt)
if err != nil { if err != nil {
if err != rtph264.ErrNonStartingPacketAndNoPrevious && err != rtph264.ErrMorePacketsNeeded {
log.Printf("ERR: %v", err)
}
return return
} }

View File

@@ -1,8 +1,11 @@
package main package main
import ( import (
"log"
"github.com/aler9/gortsplib/v2" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format" "github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/formatdecenc/rtph264"
"github.com/aler9/gortsplib/v2/pkg/url" "github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -61,6 +64,9 @@ func main() {
// convert RTP packets into NALUs // convert RTP packets into NALUs
nalus, pts, err := rtpDec.Decode(pkt) nalus, pts, err := rtpDec.Decode(pkt)
if err != nil { if err != nil {
if err != rtph264.ErrNonStartingPacketAndNoPrevious && err != rtph264.ErrMorePacketsNeeded {
log.Printf("ERR: %v", err)
}
return return
} }

View File

@@ -5,6 +5,7 @@ import (
"github.com/aler9/gortsplib/v2" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format" "github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/formatdecenc/rtph264"
"github.com/aler9/gortsplib/v2/pkg/url" "github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -75,8 +76,11 @@ func main() {
// called when a RTP packet arrives // called when a RTP packet arrives
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) { c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
// convert RTP packets into NALUs // convert RTP packets into NALUs
nalus, _, err := rtpDec.Decode(pkt) nalus, pts, err := rtpDec.Decode(pkt)
if err != nil { if err != nil {
if err != rtph264.ErrNonStartingPacketAndNoPrevious && err != rtph264.ErrMorePacketsNeeded {
log.Printf("ERR: %v", err)
}
return return
} }
@@ -92,7 +96,7 @@ func main() {
continue continue
} }
log.Printf("decoded frame with size %v", img.Bounds().Max) log.Printf("decoded frame with size %v and pts %v", img.Bounds().Max, pts)
} }
}) })

View File

@@ -5,6 +5,7 @@ import (
"github.com/aler9/gortsplib/v2" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format" "github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/formatdecenc/rtph265"
"github.com/aler9/gortsplib/v2/pkg/url" "github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -57,6 +58,9 @@ func main() {
// convert RTP packets into NALUs // convert RTP packets into NALUs
nalus, pts, err := rtpDec.Decode(pkt) nalus, pts, err := rtpDec.Decode(pkt)
if err != nil { if err != nil {
if err != rtph265.ErrNonStartingPacketAndNoPrevious && err != rtph265.ErrMorePacketsNeeded {
log.Printf("ERR: %v", err)
}
return return
} }

View File

@@ -57,6 +57,7 @@ func main() {
// decode LPCM samples from the RTP packet // decode LPCM samples from the RTP packet
op, _, err := rtpDec.Decode(pkt) op, _, err := rtpDec.Decode(pkt)
if err != nil { if err != nil {
log.Printf("ERR: %v", err)
return return
} }

View File

@@ -0,0 +1,87 @@
package main
import (
"bytes"
"image/jpeg"
"log"
"github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/formatdecenc/rtpmjpeg"
"github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtp"
)
// This example shows how to
// 1. connect to a RTSP server
// 2. check if there's a MJPEG media
// 3. get JPEG images of that media
// 4. decode JPEG images into raw images
func main() {
c := gortsplib.Client{}
// parse URL
u, err := url.Parse("rtsp://localhost:8554/mystream")
if err != nil {
panic(err)
}
// connect to the server
err = c.Start(u.Scheme, u.Host)
if err != nil {
panic(err)
}
defer c.Close()
// find published medias
medias, baseURL, _, err := c.Describe(u)
if err != nil {
panic(err)
}
// find the MJPEG media and format
var forma *format.MJPEG
medi := medias.FindFormat(&forma)
if medi == nil {
panic("media not found")
}
// setup RTP/MJPEG->MJPEG decoder
rtpDec := forma.CreateDecoder()
// setup the chosen media only
_, err = c.Setup(medi, baseURL, 0, 0)
if err != nil {
panic(err)
}
// called when a RTP packet arrives
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
// convert RTP packets into JPEG images
enc, pts, err := rtpDec.Decode(pkt)
if err != nil {
if err != rtpmjpeg.ErrNonStartingPacketAndNoPrevious && err != rtpmjpeg.ErrMorePacketsNeeded {
log.Printf("ERR: %v", err)
}
return
}
// convert JPEG images into raw images
image, err := jpeg.Decode(bytes.NewReader(enc))
if err != nil {
panic(err)
}
log.Printf("decoded image with size %v and pts %v", image.Bounds().Max, pts)
})
// start playing
_, err = c.Play(nil)
if err != nil {
panic(err)
}
// wait until a fatal error
panic(c.Wait())
}

View File

@@ -57,6 +57,7 @@ func main() {
// decode MPEG4-audio AUs from the RTP packet // decode MPEG4-audio AUs from the RTP packet
aus, _, err := rtpDec.Decode(pkt) aus, _, err := rtpDec.Decode(pkt)
if err != nil { if err != nil {
log.Printf("ERR: %v", err)
return return
} }

View File

@@ -57,6 +57,7 @@ func main() {
// decode an Opus packet from the RTP packet // decode an Opus packet from the RTP packet
op, _, err := rtpDec.Decode(pkt) op, _, err := rtpDec.Decode(pkt)
if err != nil { if err != nil {
log.Printf("ERR: %v", err)
return return
} }

View File

@@ -5,6 +5,7 @@ import (
"github.com/aler9/gortsplib/v2" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format" "github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/formatdecenc/rtpvp8"
"github.com/aler9/gortsplib/v2/pkg/url" "github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -57,6 +58,9 @@ func main() {
// decode a VP8 frame from the RTP packet // decode a VP8 frame from the RTP packet
vf, _, err := rtpDec.Decode(pkt) vf, _, err := rtpDec.Decode(pkt)
if err != nil { if err != nil {
if err != rtpvp8.ErrNonStartingPacketAndNoPrevious && err != rtpvp8.ErrMorePacketsNeeded {
log.Printf("ERR: %v", err)
}
return return
} }

View File

@@ -5,6 +5,7 @@ import (
"github.com/aler9/gortsplib/v2" "github.com/aler9/gortsplib/v2"
"github.com/aler9/gortsplib/v2/pkg/format" "github.com/aler9/gortsplib/v2/pkg/format"
"github.com/aler9/gortsplib/v2/pkg/formatdecenc/rtpvp9"
"github.com/aler9/gortsplib/v2/pkg/url" "github.com/aler9/gortsplib/v2/pkg/url"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -57,6 +58,9 @@ func main() {
// decode a VP9 frame from the RTP packet // decode a VP9 frame from the RTP packet
vf, _, err := rtpDec.Decode(pkt) vf, _, err := rtpDec.Decode(pkt)
if err != nil { if err != nil {
if err != rtpvp9.ErrNonStartingPacketAndNoPrevious && err != rtpvp9.ErrMorePacketsNeeded {
log.Printf("ERR: %v", err)
}
return return
} }

View File

@@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
var casesHeader = []struct { var cases = []struct {
name string name string
dec []byte dec []byte
enc []byte enc []byte
@@ -106,7 +106,7 @@ var casesHeader = []struct {
} }
func TestHeaderRead(t *testing.T) { func TestHeaderRead(t *testing.T) {
for _, ca := range casesHeader { for _, ca := range cases {
t.Run(ca.name, func(t *testing.T) { t.Run(ca.name, func(t *testing.T) {
h := make(Header) h := make(Header)
err := h.read(bufio.NewReader(bytes.NewBuffer(ca.dec))) err := h.read(bufio.NewReader(bytes.NewBuffer(ca.dec)))
@@ -174,7 +174,7 @@ func TestHeaderReadErrors(t *testing.T) {
} }
func TestHeaderWrite(t *testing.T) { func TestHeaderWrite(t *testing.T) {
for _, ca := range casesHeader { for _, ca := range cases {
t.Run(ca.name, func(t *testing.T) { t.Run(ca.name, func(t *testing.T) {
buf := ca.header.marshal() buf := ca.header.marshal()
require.Equal(t, ca.enc, buf) require.Equal(t, ca.enc, buf)

View File

@@ -70,7 +70,7 @@ func Unmarshal(md *psdp.MediaDescription, payloadTypeStr string) (Format, error)
case md.MediaName.Media == "video": case md.MediaName.Media == "video":
switch { switch {
case payloadType == 26: case payloadType == 26:
return &JPEG{} return &MJPEG{}
case payloadType == 32: case payloadType == 32:
return &MPEG2Video{} return &MPEG2Video{}

View File

@@ -310,7 +310,7 @@ func TestNewFromMediaDescription(t *testing.T) {
Formats: []string{"26"}, Formats: []string{"26"},
}, },
}, },
&JPEG{}, &MJPEG{},
}, },
{ {
"video mpeg2 video", "video mpeg2 video",

View File

@@ -1,37 +0,0 @@
package format
import (
"github.com/pion/rtp"
)
// JPEG is a JPEG format.
type JPEG struct{}
// String implements Format.
func (t *JPEG) String() string {
return "JPEG"
}
// ClockRate implements Format.
func (t *JPEG) ClockRate() int {
return 90000
}
// PayloadType implements Format.
func (t *JPEG) PayloadType() uint8 {
return 26
}
func (t *JPEG) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
return nil
}
// Marshal implements Format.
func (t *JPEG) Marshal() (string, string) {
return "JPEG/90000", ""
}
// PTSEqualsDTS implements Format.
func (t *JPEG) PTSEqualsDTS(*rtp.Packet) bool {
return true
}

View File

@@ -1,24 +0,0 @@
package format
import (
"testing"
"github.com/pion/rtp"
"github.com/stretchr/testify/require"
)
func TestJPEGAttributes(t *testing.T) {
format := &JPEG{}
require.Equal(t, "JPEG", format.String())
require.Equal(t, 90000, format.ClockRate())
require.Equal(t, uint8(26), format.PayloadType())
require.Equal(t, true, format.PTSEqualsDTS(&rtp.Packet{}))
}
func TestJPEGMediaDescription(t *testing.T) {
format := &JPEG{}
rtpmap, fmtp := format.Marshal()
require.Equal(t, "JPEG/90000", rtpmap)
require.Equal(t, "", fmtp)
}

53
pkg/format/mjpeg.go Normal file
View File

@@ -0,0 +1,53 @@
package format
import (
"github.com/pion/rtp"
"github.com/aler9/gortsplib/v2/pkg/formatdecenc/rtpmjpeg"
)
// MJPEG is a Motion-JPEG format.
type MJPEG struct{}
// String implements Format.
func (t *MJPEG) String() string {
return "MJPEG"
}
// ClockRate implements Format.
func (t *MJPEG) ClockRate() int {
return 90000
}
// PayloadType implements Format.
func (t *MJPEG) PayloadType() uint8 {
return 26
}
func (t *MJPEG) unmarshal(payloadType uint8, clock string, codec string, rtpmap string, fmtp string) error {
return nil
}
// Marshal implements Format.
func (t *MJPEG) Marshal() (string, string) {
return "JPEG/90000", ""
}
// PTSEqualsDTS implements Format.
func (t *MJPEG) PTSEqualsDTS(*rtp.Packet) bool {
return true
}
// CreateDecoder creates a decoder able to decode the content of the format.
func (t *MJPEG) CreateDecoder() *rtpmjpeg.Decoder {
d := &rtpmjpeg.Decoder{}
d.Init()
return d
}
// CreateEncoder creates an encoder able to encode the content of the format.
func (t *MJPEG) CreateEncoder() *rtpmjpeg.Encoder {
e := &rtpmjpeg.Encoder{}
e.Init()
return e
}

301
pkg/format/mjpeg_test.go Normal file
View File

@@ -0,0 +1,301 @@
package format
import (
"testing"
"github.com/pion/rtp"
"github.com/stretchr/testify/require"
)
func TestMJPEGAttributes(t *testing.T) {
format := &MJPEG{}
require.Equal(t, "MJPEG", format.String())
require.Equal(t, 90000, format.ClockRate())
require.Equal(t, uint8(26), format.PayloadType())
require.Equal(t, true, format.PTSEqualsDTS(&rtp.Packet{}))
}
func TestMJPEGMediaDescription(t *testing.T) {
format := &MJPEG{}
rtpmap, fmtp := format.Marshal()
require.Equal(t, "JPEG/90000", rtpmap)
require.Equal(t, "", fmtp)
}
func TestMJPEGDecEncoder(t *testing.T) {
format := &MJPEG{}
b := []byte{
0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0d,
0x09, 0x0a, 0x0b, 0x0a, 0x08, 0x0d, 0x0b, 0x0a,
0x0b, 0x0e, 0x0e, 0x0d, 0x0f, 0x13, 0x20, 0x15,
0x13, 0x12, 0x12, 0x13, 0x27, 0x1c, 0x1e, 0x17,
0x20, 0x2e, 0x29, 0x31, 0x30, 0x2e, 0x29, 0x2d,
0x2c, 0x33, 0x3a, 0x4a, 0x3e, 0x33, 0x36, 0x46,
0x37, 0x2c, 0x2d, 0x40, 0x57, 0x41, 0x46, 0x4c,
0x4e, 0x52, 0x53, 0x52, 0x32, 0x3e, 0x5a, 0x61,
0x5a, 0x50, 0x60, 0x4a, 0x51, 0x52, 0x4f, 0x01,
0x0e, 0x0e, 0x0e, 0x13, 0x11, 0x13, 0x26, 0x15,
0x15, 0x26, 0x4f, 0x35, 0x2d, 0x35, 0x4f, 0x4f,
0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f,
0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f,
0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f,
0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f,
0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f,
0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f,
0xff, 0xc0, 0x00, 0x11, 0x08, 0x04, 0x38, 0x07,
0x80, 0x03, 0x00, 0x22, 0x00, 0x01, 0x11, 0x01,
0x02, 0x11, 0x01, 0xff, 0xc4, 0x00, 0x1f, 0x00,
0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5,
0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04,
0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01,
0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05,
0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61,
0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1,
0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1,
0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a,
0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27,
0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38,
0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5,
0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4,
0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3,
0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1,
0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
0xf8, 0xf9, 0xfa, 0xff, 0xc4, 0x00, 0x1f, 0x01,
0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5,
0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03,
0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02,
0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05,
0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61,
0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42,
0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52,
0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24,
0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a,
0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37,
0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47,
0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57,
0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67,
0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77,
0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86,
0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95,
0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4,
0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3,
0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2,
0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca,
0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8,
0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03,
0x00, 0x00, 0x01, 0x11, 0x02, 0x11, 0x00, 0x3f,
0x00, 0x92, 0x8a, 0x28, 0xaf, 0x54, 0xf2, 0x42,
0x8a, 0x28, 0xa0, 0x02, 0x96, 0x92, 0x96, 0x80,
0x0a, 0x4a, 0x75, 0x25, 0x02, 0x12, 0x8a, 0x5a,
0x28, 0x18, 0x94, 0x52, 0xd1, 0x40, 0x09, 0x45,
0x2d, 0x14, 0x08, 0x29, 0x69, 0x29, 0x68, 0x00,
0xa5, 0xa4, 0xa5, 0xa0, 0x02, 0x8a, 0x28, 0xa0,
0x02, 0x8a, 0x28, 0xa0, 0x04, 0xa5, 0xa2, 0x8a,
0x00, 0x5a, 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2,
0x80, 0x0a, 0x28, 0xa2, 0x80, 0x12, 0x8a, 0x5a,
0x28, 0x24, 0x29, 0x69, 0x29, 0x68, 0x00, 0xa2,
0x8a, 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2,
0x8a, 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2,
0x8a, 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2,
0x8a, 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2,
0x8a, 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2,
0x8a, 0x28, 0x00, 0xa4, 0xa5, 0xa4, 0xa0, 0x02,
0x8a, 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02,
0x8a, 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02,
0x8a, 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02,
0x8a, 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02,
0x96, 0x92, 0x96, 0x80, 0x0a, 0x28, 0xa2, 0x80,
0x0a, 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80,
0x0a, 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80,
0x0a, 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80,
0x0a, 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x81,
0x85, 0x14, 0x51, 0x40, 0x05, 0x14, 0x51, 0x40,
0x05, 0x14, 0x51, 0x40, 0x05, 0x14, 0x52, 0xd0,
0x01, 0x45, 0x14, 0x50, 0x01, 0x45, 0x14, 0x50,
0x01, 0x45, 0x14, 0x50, 0x01, 0x45, 0x2d, 0x14,
0x00, 0x94, 0xb4, 0x51, 0x40, 0x05, 0x14, 0x52,
0xd0, 0x02, 0x51, 0x4b, 0x45, 0x00, 0x25, 0x2d,
0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45,
0x14, 0x50, 0x20, 0xa5, 0xa4, 0xa5, 0xa0, 0x02,
0x8a, 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02,
0x8a, 0x5a, 0x28, 0x18, 0x94, 0xb4, 0x51, 0x40,
0xc2, 0x8a, 0x28, 0xa0, 0x05, 0xa2, 0x92, 0x9d,
0x40, 0x05, 0x14, 0x51, 0x48, 0x02, 0x8a, 0x28,
0xa4, 0x01, 0x4b, 0x49, 0x4b, 0x40, 0x05, 0x14,
0x51, 0x40, 0x05, 0x14, 0xb4, 0x50, 0x02, 0x51,
0x4b, 0x45, 0x00, 0x25, 0x2d, 0x14, 0x50, 0x03,
0xa8, 0xa2, 0x8a, 0x00, 0x28, 0xa2, 0x8a, 0x00,
0x5a, 0x29, 0x29, 0x68, 0x00, 0xa2, 0x8a, 0x28,
0x00, 0xa2, 0x96, 0x8a, 0x06, 0x25, 0x14, 0xb4,
0x50, 0x01, 0x45, 0x14, 0x50, 0x02, 0xd2, 0xd2,
0x52, 0xd0, 0x20, 0xa2, 0x8a, 0x28, 0x01, 0x68,
0xa2, 0x8a, 0x40, 0x14, 0x51, 0x45, 0x30, 0x0a,
0x5a, 0x4a, 0x5a, 0x06, 0x14, 0x51, 0x45, 0x02,
0x0a, 0x28, 0xa5, 0xa0, 0x62, 0x51, 0x4b, 0x45,
0x00, 0x2d, 0x14, 0x51, 0x48, 0x02, 0x8a, 0x28,
0xa0, 0x61, 0x45, 0x14, 0x50, 0x03, 0xa8, 0xa2,
0x8a, 0x06, 0x2d, 0x14, 0x51, 0x48, 0x02, 0x8a,
0x28, 0xa4, 0x30, 0xa2, 0x8a, 0x2a, 0x80, 0x28,
0xa2, 0x8a, 0x00, 0x28, 0xa2, 0x8a, 0x92, 0x45,
0xa5, 0xa2, 0x96, 0x82, 0x82, 0x8a, 0x28, 0xa0,
0x02, 0x8a, 0x28, 0xa0, 0x05, 0xa2, 0x8a, 0x29,
0x80, 0x52, 0xd2, 0x52, 0xd0, 0x01, 0x45, 0x14,
0x50, 0x01, 0x4e, 0xa2, 0x8a, 0x43, 0x0a, 0x28,
0xa2, 0x80, 0x0a, 0x28, 0xa4, 0xa4, 0x31, 0x68,
0xa4, 0xf3, 0x62, 0xff, 0x00, 0x9e, 0xd1, 0x7e,
0xea, 0x9f, 0xfe, 0xb6, 0xa4, 0x62, 0x52, 0x53,
0xa9, 0x28, 0x01, 0x28, 0xa2, 0x6f, 0xdd, 0x7f,
0xaf, 0xa5, 0xa0, 0x62, 0x51, 0x4b, 0x45, 0x00,
0x25, 0x14, 0xbf, 0xba, 0xff, 0x00, 0xae, 0xb4,
0xea, 0x60, 0x36, 0x9d, 0x49, 0x49, 0x34, 0xb1,
0x45, 0xfe, 0xbe, 0x6f, 0x2a, 0x98, 0x0f, 0xa2,
0xb9, 0xbd, 0x0f, 0x59, 0x97, 0x54, 0xf1, 0x2d,
0xd7, 0xfc, 0xf9, 0xd7, 0x49, 0x52, 0x30, 0xac,
0x7d, 0x5b, 0x54, 0xd5, 0x74, 0xbf, 0xdf, 0x41,
0x67, 0x15, 0xd4, 0x35, 0x63, 0x56, 0xd5, 0x22,
0xd2, 0xe0, 0xf3, 0xbc, 0x99, 0x65, 0xae, 0x4b,
0x51, 0xf1, 0x44, 0x52, 0xff, 0x00, 0xc7, 0x97,
0x9b, 0xe4, 0xd0, 0x69, 0x4c, 0x76, 0xa3, 0xe2,
0xd9, 0xa5, 0x82, 0x29, 0xb4, 0xbf, 0xdd, 0x79,
0xbf, 0xeb, 0x22, 0xaa, 0x7e, 0x1e, 0xd5, 0x3e,
0xc1, 0x3f, 0xfa, 0x9f, 0xfa, 0xe9, 0x25, 0x61,
0xd6, 0x9e, 0x87, 0xf6, 0xbf, 0xed, 0x68, 0xbc,
0x8a, 0x82, 0xcf, 0x4e, 0x86, 0x5f, 0x36, 0x0a,
0x92, 0x9b, 0xff, 0x00, 0x5d, 0xeb, 0x3a, 0x6d,
0x53, 0xfd, 0x3f, 0xec, 0x76, 0x3f, 0xbd, 0xff,
0x00, 0xa6, 0xbf, 0xc1, 0x54, 0x66, 0x69, 0x51,
0x50, 0x4b, 0x2c, 0x51, 0x7e, 0xe7, 0xce, 0xf3,
0x66, 0xff, 0x00, 0xa6, 0x75, 0x35, 0x00, 0x2d,
0x65, 0x6a, 0xde, 0x23, 0xd3, 0xec, 0x3f, 0xe5,
0xb7, 0x9b, 0x34, 0x5f, 0xf2, 0xce, 0xb4, 0x7e,
0xd5, 0x69, 0x2f, 0xfc, 0xbe, 0x45, 0x5e, 0x7b,
0xe2, 0x7d, 0x07, 0xec, 0x1e, 0x6e, 0xa5, 0xf6,
0xc8, 0xa5, 0xf3, 0x6a, 0xc0, 0xdf, 0xa2, 0x9d,
0x45, 0x6c, 0x72, 0x0d, 0xa5, 0xa5, 0xa2, 0x80,
0x12, 0x96, 0x8a, 0x28, 0x10, 0x51, 0x4b, 0x45,
0x00, 0x25, 0x14, 0xb4, 0x50, 0x02, 0x51, 0x4b,
0x45, 0x03, 0x12, 0x8a, 0x5a, 0x28, 0x10, 0x94,
0xb4, 0x51, 0x40, 0x05, 0x14, 0x51, 0x40, 0x05,
0x14, 0xb4, 0x50, 0x02, 0x51, 0x4b, 0x45, 0x00,
0x14, 0x51, 0x4b, 0x40, 0x84, 0xa2, 0x8a, 0x28,
0x00, 0xa2, 0x96, 0x8a, 0x00, 0x4a, 0x29, 0x68,
0xa0, 0x04, 0xa2, 0x96, 0x8a, 0x00, 0x4a, 0x29,
0x68, 0xa0, 0x41, 0x45, 0x14, 0x50, 0x01, 0x45,
0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45,
0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45,
0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45,
0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45,
0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x49,
0x4b, 0x45, 0x00, 0x25, 0x14, 0xb4, 0x50, 0x01,
0x45, 0x14, 0x50, 0x30, 0xa4, 0xa5, 0xa2, 0x81,
0x09, 0x45, 0x2d, 0x14, 0x00, 0x94, 0x52, 0xd1,
0x40, 0x09, 0x45, 0x2d, 0x14, 0x00, 0x94, 0x52,
0xd1, 0x40, 0x05, 0x14, 0xb4, 0x94, 0x0c, 0x28,
0xa2, 0x8a, 0x04, 0x14, 0x51, 0x45, 0x00, 0x14,
0x51, 0x45, 0x00, 0x14, 0x51, 0x45, 0x00, 0x14,
0x51, 0x45, 0x00, 0x14, 0x52, 0xd1, 0x40, 0xc4,
0xa2, 0x96, 0x8a, 0x04, 0x25, 0x14, 0xb4, 0x50,
0x31, 0x28, 0xa5, 0xa2, 0x80, 0x12, 0x8a, 0x5a,
0x4a, 0x00, 0x5a, 0x28, 0xa2, 0x80, 0x0a, 0x29,
0x68, 0xa0, 0x04, 0xa2, 0x96, 0x8a, 0x00, 0x4a,
0x29, 0x68, 0xa0, 0x04, 0xa2, 0x9d, 0x45, 0x03,
0x1b, 0x45, 0x3a, 0x8a, 0x00, 0x6d, 0x14, 0xea,
0x28, 0x10, 0x94, 0x52, 0xd1, 0x40, 0x09, 0x4b,
0x45, 0x14, 0x08, 0x28, 0xa2, 0x8a, 0x06, 0x14,
0x52, 0xd1, 0x40, 0x09, 0x45, 0x2d, 0x2d, 0x00,
0x25, 0x14, 0xb4, 0x50, 0x21, 0x28, 0xa5, 0xa2,
0x81, 0x89, 0x4b, 0x45, 0x14, 0x00, 0x51, 0x45,
0x14, 0x00, 0x51, 0x4b, 0x45, 0x00, 0x25, 0x14,
0xb4, 0x50, 0x01, 0x45, 0x2d, 0x14, 0x0c, 0x28,
0xa2, 0x8a, 0x00, 0x4a, 0x5a, 0x5a, 0x28, 0x01,
0x28, 0xa5, 0xa2, 0x90, 0x05, 0x14, 0x51, 0x40,
0x05, 0x14, 0xb4, 0x52, 0x01, 0x28, 0xa5, 0xa2,
0x80, 0x0a, 0x28, 0xa7, 0x50, 0x03, 0x68, 0xa7,
0x51, 0x40, 0x0d, 0xa7, 0x51, 0x45, 0x00, 0x14,
0x51, 0x4b, 0x40, 0x09, 0x45, 0x2d, 0x2d, 0x00,
0x25, 0x14, 0xb4, 0x50, 0x02, 0x51, 0x4b, 0x45,
0x00, 0x14, 0x51, 0x45, 0x00, 0x14, 0x51, 0x45,
0x00, 0x14, 0xb4, 0x53, 0xa8, 0x18, 0xda, 0x5a,
0x5a, 0x28, 0x10, 0x94, 0xb4, 0x51, 0x40, 0xc2,
0x8a, 0x5a, 0x29, 0x00, 0x94, 0x52, 0xd1, 0x40,
0x05, 0x14, 0x51, 0x4c, 0x02, 0x96, 0x8a, 0x29,
0x00, 0x52, 0xd2, 0x52, 0xd0, 0x01, 0x45, 0x14,
0x50, 0x02, 0xd1, 0x45, 0x14, 0x0c, 0x28, 0xa2,
0x96, 0x80, 0x0a, 0x28, 0xa2, 0x81, 0x85, 0x2d,
0x14, 0x50, 0x01, 0x45, 0x14, 0xb4, 0x80, 0x4a,
0x29, 0x68, 0xa0, 0x62, 0xd1, 0x45, 0x14, 0xc0,
0x28, 0xa5, 0xa2, 0x90, 0x0d, 0xa5, 0xa5, 0xa2,
0x80, 0x0a, 0x5a, 0x28, 0xa4, 0x01, 0x45, 0x14,
0xb4, 0x00, 0x94, 0x52, 0xd1, 0x40, 0x05, 0x14,
0x51, 0x4c, 0x02, 0x8a, 0x29, 0x68, 0x01, 0x28,
0xa5, 0xa2, 0x80, 0x16, 0x8a, 0x28, 0xa4, 0x30,
0xa7, 0x55, 0x7b, 0xbb, 0x5f, 0xb7, 0xc1, 0xe4,
0xff, 0x00, 0xaa, 0xac, 0x49, 0xb4, 0x6d, 0x57,
0x4b, 0xf3, 0x66, 0xd2, 0xef, 0x3c, 0xd8, 0x69,
0x17, 0x4c, 0xe9, 0x2b, 0x07, 0xc4, 0x32, 0xcb,
0x75, 0xa4, 0xff, 0x00, 0xc4, 0xae, 0x68, 0xbf,
0x75, 0xfe, 0xb2, 0xb9, 0x09, 0xb5, 0x9d, 0x42,
0x5f, 0x36, 0xcf, 0xce, 0xff, 0x00, 0x5b, 0x56,
0x3c, 0x3d, 0x6b, 0xfd, 0xa9, 0xab, 0x4b, 0x67,
0x3f, 0xfc, 0xf3, 0xff, 0x00, 0x96, 0x74, 0x8d,
0xbd, 0x99, 0x9b, 0x0d, 0xfc, 0xb2, 0xf9, 0xbf,
0xeb, 0x7f, 0x7b, 0x5a, 0xf0, 0xf8, 0x8e, 0xee,
0x2d, 0x27, 0xec, 0x70, 0x7f, 0xdf, 0xca, 0x66,
0xa3, 0xe1, 0x7f, 0xb0, 0x79, 0xbe, 0x46, 0xa5,
0x17, 0xfd, 0x73, 0x92, 0xb0, 0xe1, 0xa8, 0x0f,
0x66, 0x76, 0x90, 0xf8, 0xa2, 0x29, 0x6c, 0x3c,
0x9f, 0xde, 0xf9, 0xde, 0x5f, 0xef, 0x24, 0xaa,
0x76, 0x9e, 0x2d, 0xbb, 0x8b, 0x49, 0xf2, 0x7f,
0xe5, 0xb5, 0x62, 0x5a, 0x5f, 0xcb, 0x6b, 0xfe,
0xa3, 0xfe, 0x5a, 0xd1, 0xe5, 0x45, 0xff, 0x00,
0x2c, 0x29, 0x0f, 0xd9, 0x97, 0xff, 0x00, 0xb6,
0x65, 0xba, 0x82, 0x5f, 0xb7, 0x4d, 0xe6, 0xcd,
0xff, 0x00, 0x2c, 0xe2, 0xae, 0xa7, 0x49, 0xd5,
0x3f, 0xe2, 0x9a, 0xf3, 0xa7, 0xff, 0x00, 0x5d,
0x15, 0x70, 0xb4, 0x43, 0xfb, 0xdf, 0xf5, 0xf3,
0x4b, 0xff, 0x00, 0x5c, 0xe8, 0xf6, 0x83, 0xf6,
0x67, 0xa1, 0x68, 0x7a, 0xcc, 0x5a, 0xcf, 0xfd,
0x32, 0x9b, 0xfe, 0x79, 0xd5, 0xeb, 0xbb, 0xab,
0x4b, 0x08, 0x3f, 0xd3, 0xab, 0xcd, 0x66, 0xba,
0xfb, 0x57, 0xfd, 0x32, 0x9a, 0x2a, 0x96, 0xd2,
0xff, 0x00, 0xcd, 0xbf, 0xf3, 0xb5, 0x49, 0xbc,
0xdf, 0x2b, 0xfe, 0x59, 0xd3, 0x27, 0xd9, 0x9a,
0x30, 0xdd, 0x7d, 0xab, 0x5e, 0xfb, 0x1d, 0x8c,
0xde, 0x55, 0x9c, 0xb2, 0x79, 0x95, 0xd3, 0x6a,
0x3a, 0xa7, 0xd9, 0x75, 0xdb, 0x5b, 0x39, 0xff,
0x00, 0xd4, 0xcb, 0xff, 0x00, 0x2d, 0x2b, 0xcf,
0xbe, 0xdf, 0xe5, 0x6a, 0xdf, 0x6c, 0x83, 0xfe,
0x59, 0x54, 0xda, 0xb6, 0xb3, 0x2e, 0xb3, 0x7f,
0xe7, 0x7f, 0xaa, 0xff, 0xff, 0xd9,
}
enc := format.CreateEncoder()
pkts, err := enc.Encode(b, 0)
require.NoError(t, err)
require.Equal(t, format.PayloadType(), pkts[0].PayloadType)
dec := format.CreateDecoder()
var byts []byte
for _, pkt := range pkts {
byts, _, _ = dec.Decode(pkt)
}
require.Equal(t, b, byts)
}

View File

@@ -20,7 +20,7 @@ var ErrMorePacketsNeeded = errors.New("need more packets")
// It's normal to receive this when we are decoding a stream that has been already // It's normal to receive this when we are decoding a stream that has been already
// running for some time. // running for some time.
var ErrNonStartingPacketAndNoPrevious = errors.New( var ErrNonStartingPacketAndNoPrevious = errors.New(
"received a non-starting FU-A packet without any previous FU-A starting packet") "received a non-starting fragment without any previous starting fragment")
// Decoder is a RTP/H264 decoder. // Decoder is a RTP/H264 decoder.
type Decoder struct { type Decoder struct {
@@ -190,8 +190,8 @@ func (d *Decoder) DecodeUntilMarker(pkt *rtp.Packet) ([][]byte, time.Duration, e
return ret, pts, nil return ret, pts, nil
} }
// some cameras / servers wrap NALUs into Annex-B
func (d *Decoder) removeAnnexB(nalus [][]byte) ([][]byte, error) { func (d *Decoder) removeAnnexB(nalus [][]byte) ([][]byte, error) {
// some cameras / servers wrap NALUs into Annex-B
if !d.firstNALUParsed { if !d.firstNALUParsed {
d.firstNALUParsed = true d.firstNALUParsed = true

View File

@@ -574,7 +574,7 @@ func TestDecodeErrors(t *testing.T) {
Payload: []byte{0x1c, 0b01000000}, Payload: []byte{0x1c, 0b01000000},
}, },
}, },
"received a non-starting FU-A packet without any previous FU-A starting packet", "received a non-starting fragment without any previous starting fragment",
}, },
{ {
"FU-A non-starting 2", "FU-A non-starting 2",

View File

@@ -19,7 +19,7 @@ var ErrMorePacketsNeeded = errors.New("need more packets")
// It's normal to receive this when we are decoding a stream that has been already // It's normal to receive this when we are decoding a stream that has been already
// running for some time. // running for some time.
var ErrNonStartingPacketAndNoPrevious = errors.New( var ErrNonStartingPacketAndNoPrevious = errors.New(
"received a non-starting fragmentation unit without any previous fragmentation units") "received a non-starting fragment without any previous starting fragment")
// Decoder is a RTP/H265 decoder. // Decoder is a RTP/H265 decoder.
type Decoder struct { type Decoder struct {

View File

@@ -296,7 +296,7 @@ func TestDecodeErrors(t *testing.T) {
Payload: []byte{49 << 1, 0x00, 0b01000000}, Payload: []byte{49 << 1, 0x00, 0b01000000},
}, },
}, },
"received a non-starting fragmentation unit without any previous fragmentation units", "received a non-starting fragment without any previous starting fragment",
}, },
{ {
"fragmentation unit non-starting 2", "fragmentation unit non-starting 2",

View File

@@ -0,0 +1,238 @@
package rtpmjpeg
import (
"errors"
"fmt"
"time"
"github.com/pion/rtp"
"github.com/aler9/gortsplib/v2/pkg/formatdecenc/rtpmjpeg/headers"
"github.com/aler9/gortsplib/v2/pkg/formatdecenc/rtpmjpeg/jpeg"
"github.com/aler9/gortsplib/v2/pkg/rtptimedec"
)
// ErrMorePacketsNeeded is returned when more packets are needed.
var ErrMorePacketsNeeded = errors.New("need more packets")
// ErrNonStartingPacketAndNoPrevious is returned when we received a non-starting
// fragment of an image and we didn't received anything before.
// It's normal to receive this when we are decoding a stream that has been already
// running for some time.
var ErrNonStartingPacketAndNoPrevious = errors.New(
"received a non-starting fragment without any previous starting fragment")
var lumDcCodeLens = []byte{
0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
}
var lumDcSymbols = []byte{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
}
var lumAcCodelens = []byte{
0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d,
}
var lumAcSymbols = []byte{ //nolint:dupl
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,
0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0xfa,
}
var chmDcCodelens = []byte{
0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
}
var chmDcSymbols = []byte{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
}
var chmAcCodelens = []byte{
0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77,
}
var chmAcSymbols = []byte{ //nolint:dupl
0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21,
0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34,
0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38,
0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2,
0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0xfa,
}
// Decoder is a RTP/MJPEG decoder.
type Decoder struct {
timeDecoder *rtptimedec.Decoder
firstPacketReceived bool
fragmentedSize int
fragments [][]byte
firstJpegHeader *headers.JPEG
firstQTHeader *headers.QuantizationTable
}
// Init initializes the decoder.
func (d *Decoder) Init() {
d.timeDecoder = rtptimedec.New(rtpClockRate)
}
// Decode decodes an image from a RTP/MJPEG packet.
func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, time.Duration, error) {
byts := pkt.Payload
var jh headers.JPEG
n, err := jh.Unmarshal(byts)
if err != nil {
return nil, 0, err
}
byts = byts[n:]
if jh.Width > maxDimension {
return nil, 0, fmt.Errorf("Width of %d is not supported", jh.Width)
}
if jh.Height > maxDimension {
return nil, 0, fmt.Errorf("Height of %d is not supported", jh.Height)
}
if jh.FragmentOffset == 0 {
if jh.Quantization >= 128 {
d.firstQTHeader = &headers.QuantizationTable{}
n, err := d.firstQTHeader.Unmarshal(byts)
if err != nil {
return nil, 0, err
}
byts = byts[n:]
} else {
d.firstQTHeader = nil
}
d.fragments = d.fragments[:0] // discard pending fragmented packets
d.fragmentedSize = len(byts)
d.fragments = append(d.fragments, byts)
d.firstJpegHeader = &jh
d.firstPacketReceived = true
} else {
if len(d.fragments) == 0 {
if !d.firstPacketReceived {
return nil, 0, ErrNonStartingPacketAndNoPrevious
}
return nil, 0, fmt.Errorf("received a non-starting fragment")
}
if int(jh.FragmentOffset) != d.fragmentedSize {
d.fragments = d.fragments[:0] // discard pending fragmented packets
return nil, 0, fmt.Errorf("received wrong fragment")
}
d.fragmentedSize += len(byts)
d.fragments = append(d.fragments, byts)
}
if !pkt.Marker {
return nil, 0, ErrMorePacketsNeeded
}
data := make([]byte, d.fragmentedSize)
pos := 0
for _, frag := range d.fragments {
pos += copy(data[pos:], frag)
}
d.fragments = d.fragments[:0]
var buf []byte
buf = jpeg.StartOfImage{}.Marshal(buf)
var dqt jpeg.DefineQuantizationTable
id := uint8(0)
for i := 0; i < len(d.firstQTHeader.Tables); i += 64 {
dqt.Tables = append(dqt.Tables, jpeg.QuantizationTable{
ID: id,
Data: d.firstQTHeader.Tables[i : i+64],
})
id++
}
buf = dqt.Marshal(buf)
buf = jpeg.StartOfFrame1{
Type: d.firstJpegHeader.Type,
Width: d.firstJpegHeader.Width,
Height: d.firstJpegHeader.Height,
QuantizationTableCount: id,
}.Marshal(buf)
buf = jpeg.DefineHuffmanTable{
Codes: lumDcCodeLens,
Symbols: lumDcSymbols,
TableNumber: 0,
TableClass: 0,
}.Marshal(buf)
buf = jpeg.DefineHuffmanTable{
Codes: lumAcCodelens,
Symbols: lumAcSymbols,
TableNumber: 0,
TableClass: 1,
}.Marshal(buf)
buf = jpeg.DefineHuffmanTable{
Codes: chmDcCodelens,
Symbols: chmDcSymbols,
TableNumber: 1,
TableClass: 0,
}.Marshal(buf)
buf = jpeg.DefineHuffmanTable{
Codes: chmAcCodelens,
Symbols: chmAcSymbols,
TableNumber: 1,
TableClass: 1,
}.Marshal(buf)
buf = jpeg.StartOfScan{}.Marshal(buf)
buf = append(buf, data...)
if data[len(data)-2] != 0xFF || data[len(data)-1] != jpeg.MarkerEndOfImage {
buf = append(buf, []byte{0xFF, jpeg.MarkerEndOfImage}...)
}
return buf, d.timeDecoder.Decode(pkt.Timestamp), nil
}

View File

@@ -0,0 +1,529 @@
package rtpmjpeg
import (
"testing"
"time"
"github.com/pion/rtp"
"github.com/stretchr/testify/require"
)
var cases = []struct {
name string
image []byte
pts time.Duration
pkts []*rtp.Packet
}{
{
"base",
[]byte{
0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0d,
0x09, 0x0a, 0x0b, 0x0a, 0x08, 0x0d, 0x0b, 0x0a,
0x0b, 0x0e, 0x0e, 0x0d, 0x0f, 0x13, 0x20, 0x15,
0x13, 0x12, 0x12, 0x13, 0x27, 0x1c, 0x1e, 0x17,
0x20, 0x2e, 0x29, 0x31, 0x30, 0x2e, 0x29, 0x2d,
0x2c, 0x33, 0x3a, 0x4a, 0x3e, 0x33, 0x36, 0x46,
0x37, 0x2c, 0x2d, 0x40, 0x57, 0x41, 0x46, 0x4c,
0x4e, 0x52, 0x53, 0x52, 0x32, 0x3e, 0x5a, 0x61,
0x5a, 0x50, 0x60, 0x4a, 0x51, 0x52, 0x4f, 0x01,
0x0e, 0x0e, 0x0e, 0x13, 0x11, 0x13, 0x26, 0x15,
0x15, 0x26, 0x4f, 0x35, 0x2d, 0x35, 0x4f, 0x4f,
0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f,
0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f,
0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f,
0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f,
0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f,
0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f,
0xff, 0xc0, 0x00, 0x11, 0x08, 0x04, 0x38, 0x07,
0x80, 0x03, 0x00, 0x22, 0x00, 0x01, 0x11, 0x01,
0x02, 0x11, 0x01, 0xff, 0xc4, 0x00, 0x1f, 0x00,
0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5,
0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04,
0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01,
0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05,
0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61,
0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1,
0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1,
0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a,
0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27,
0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38,
0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5,
0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4,
0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3,
0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1,
0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
0xf8, 0xf9, 0xfa, 0xff, 0xc4, 0x00, 0x1f, 0x01,
0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5,
0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03,
0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02,
0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05,
0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61,
0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42,
0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52,
0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24,
0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a,
0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37,
0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47,
0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57,
0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67,
0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77,
0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86,
0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95,
0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4,
0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3,
0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2,
0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca,
0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8,
0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03,
0x00, 0x00, 0x01, 0x11, 0x02, 0x11, 0x00, 0x3f,
0x00, 0x92, 0x8a, 0x28, 0xaf, 0x54, 0xf2, 0x42,
0x8a, 0x28, 0xa0, 0x02, 0x96, 0x92, 0x96, 0x80,
0x0a, 0x4a, 0x75, 0x25, 0x02, 0x12, 0x8a, 0x5a,
0x28, 0x18, 0x94, 0x52, 0xd1, 0x40, 0x09, 0x45,
0x2d, 0x14, 0x08, 0x29, 0x69, 0x29, 0x68, 0x00,
0xa5, 0xa4, 0xa5, 0xa0, 0x02, 0x8a, 0x28, 0xa0,
0x02, 0x8a, 0x28, 0xa0, 0x04, 0xa5, 0xa2, 0x8a,
0x00, 0x5a, 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2,
0x80, 0x0a, 0x28, 0xa2, 0x80, 0x12, 0x8a, 0x5a,
0x28, 0x24, 0x29, 0x69, 0x29, 0x68, 0x00, 0xa2,
0x8a, 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2,
0x8a, 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2,
0x8a, 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2,
0x8a, 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2,
0x8a, 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2,
0x8a, 0x28, 0x00, 0xa4, 0xa5, 0xa4, 0xa0, 0x02,
0x8a, 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02,
0x8a, 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02,
0x8a, 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02,
0x8a, 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02,
0x96, 0x92, 0x96, 0x80, 0x0a, 0x28, 0xa2, 0x80,
0x0a, 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80,
0x0a, 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80,
0x0a, 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80,
0x0a, 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x81,
0x85, 0x14, 0x51, 0x40, 0x05, 0x14, 0x51, 0x40,
0x05, 0x14, 0x51, 0x40, 0x05, 0x14, 0x52, 0xd0,
0x01, 0x45, 0x14, 0x50, 0x01, 0x45, 0x14, 0x50,
0x01, 0x45, 0x14, 0x50, 0x01, 0x45, 0x2d, 0x14,
0x00, 0x94, 0xb4, 0x51, 0x40, 0x05, 0x14, 0x52,
0xd0, 0x02, 0x51, 0x4b, 0x45, 0x00, 0x25, 0x2d,
0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45,
0x14, 0x50, 0x20, 0xa5, 0xa4, 0xa5, 0xa0, 0x02,
0x8a, 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02,
0x8a, 0x5a, 0x28, 0x18, 0x94, 0xb4, 0x51, 0x40,
0xc2, 0x8a, 0x28, 0xa0, 0x05, 0xa2, 0x92, 0x9d,
0x40, 0x05, 0x14, 0x51, 0x48, 0x02, 0x8a, 0x28,
0xa4, 0x01, 0x4b, 0x49, 0x4b, 0x40, 0x05, 0x14,
0x51, 0x40, 0x05, 0x14, 0xb4, 0x50, 0x02, 0x51,
0x4b, 0x45, 0x00, 0x25, 0x2d, 0x14, 0x50, 0x03,
0xa8, 0xa2, 0x8a, 0x00, 0x28, 0xa2, 0x8a, 0x00,
0x5a, 0x29, 0x29, 0x68, 0x00, 0xa2, 0x8a, 0x28,
0x00, 0xa2, 0x96, 0x8a, 0x06, 0x25, 0x14, 0xb4,
0x50, 0x01, 0x45, 0x14, 0x50, 0x02, 0xd2, 0xd2,
0x52, 0xd0, 0x20, 0xa2, 0x8a, 0x28, 0x01, 0x68,
0xa2, 0x8a, 0x40, 0x14, 0x51, 0x45, 0x30, 0x0a,
0x5a, 0x4a, 0x5a, 0x06, 0x14, 0x51, 0x45, 0x02,
0x0a, 0x28, 0xa5, 0xa0, 0x62, 0x51, 0x4b, 0x45,
0x00, 0x2d, 0x14, 0x51, 0x48, 0x02, 0x8a, 0x28,
0xa0, 0x61, 0x45, 0x14, 0x50, 0x03, 0xa8, 0xa2,
0x8a, 0x06, 0x2d, 0x14, 0x51, 0x48, 0x02, 0x8a,
0x28, 0xa4, 0x30, 0xa2, 0x8a, 0x2a, 0x80, 0x28,
0xa2, 0x8a, 0x00, 0x28, 0xa2, 0x8a, 0x92, 0x45,
0xa5, 0xa2, 0x96, 0x82, 0x82, 0x8a, 0x28, 0xa0,
0x02, 0x8a, 0x28, 0xa0, 0x05, 0xa2, 0x8a, 0x29,
0x80, 0x52, 0xd2, 0x52, 0xd0, 0x01, 0x45, 0x14,
0x50, 0x01, 0x4e, 0xa2, 0x8a, 0x43, 0x0a, 0x28,
0xa2, 0x80, 0x0a, 0x28, 0xa4, 0xa4, 0x31, 0x68,
0xa4, 0xf3, 0x62, 0xff, 0x00, 0x9e, 0xd1, 0x7e,
0xea, 0x9f, 0xfe, 0xb6, 0xa4, 0x62, 0x52, 0x53,
0xa9, 0x28, 0x01, 0x28, 0xa2, 0x6f, 0xdd, 0x7f,
0xaf, 0xa5, 0xa0, 0x62, 0x51, 0x4b, 0x45, 0x00,
0x25, 0x14, 0xbf, 0xba, 0xff, 0x00, 0xae, 0xb4,
0xea, 0x60, 0x36, 0x9d, 0x49, 0x49, 0x34, 0xb1,
0x45, 0xfe, 0xbe, 0x6f, 0x2a, 0x98, 0x0f, 0xa2,
0xb9, 0xbd, 0x0f, 0x59, 0x97, 0x54, 0xf1, 0x2d,
0xd7, 0xfc, 0xf9, 0xd7, 0x49, 0x52, 0x30, 0xac,
0x7d, 0x5b, 0x54, 0xd5, 0x74, 0xbf, 0xdf, 0x41,
0x67, 0x15, 0xd4, 0x35, 0x63, 0x56, 0xd5, 0x22,
0xd2, 0xe0, 0xf3, 0xbc, 0x99, 0x65, 0xae, 0x4b,
0x51, 0xf1, 0x44, 0x52, 0xff, 0x00, 0xc7, 0x97,
0x9b, 0xe4, 0xd0, 0x69, 0x4c, 0x76, 0xa3, 0xe2,
0xd9, 0xa5, 0x82, 0x29, 0xb4, 0xbf, 0xdd, 0x79,
0xbf, 0xeb, 0x22, 0xaa, 0x7e, 0x1e, 0xd5, 0x3e,
0xc1, 0x3f, 0xfa, 0x9f, 0xfa, 0xe9, 0x25, 0x61,
0xd6, 0x9e, 0x87, 0xf6, 0xbf, 0xed, 0x68, 0xbc,
0x8a, 0x82, 0xcf, 0x4e, 0x86, 0x5f, 0x36, 0x0a,
0x92, 0x9b, 0xff, 0x00, 0x5d, 0xeb, 0x3a, 0x6d,
0x53, 0xfd, 0x3f, 0xec, 0x76, 0x3f, 0xbd, 0xff,
0x00, 0xa6, 0xbf, 0xc1, 0x54, 0x66, 0x69, 0x51,
0x50, 0x4b, 0x2c, 0x51, 0x7e, 0xe7, 0xce, 0xf3,
0x66, 0xff, 0x00, 0xa6, 0x75, 0x35, 0x00, 0x2d,
0x65, 0x6a, 0xde, 0x23, 0xd3, 0xec, 0x3f, 0xe5,
0xb7, 0x9b, 0x34, 0x5f, 0xf2, 0xce, 0xb4, 0x7e,
0xd5, 0x69, 0x2f, 0xfc, 0xbe, 0x45, 0x5e, 0x7b,
0xe2, 0x7d, 0x07, 0xec, 0x1e, 0x6e, 0xa5, 0xf6,
0xc8, 0xa5, 0xf3, 0x6a, 0xc0, 0xdf, 0xa2, 0x9d,
0x45, 0x6c, 0x72, 0x0d, 0xa5, 0xa5, 0xa2, 0x80,
0x12, 0x96, 0x8a, 0x28, 0x10, 0x51, 0x4b, 0x45,
0x00, 0x25, 0x14, 0xb4, 0x50, 0x02, 0x51, 0x4b,
0x45, 0x03, 0x12, 0x8a, 0x5a, 0x28, 0x10, 0x94,
0xb4, 0x51, 0x40, 0x05, 0x14, 0x51, 0x40, 0x05,
0x14, 0xb4, 0x50, 0x02, 0x51, 0x4b, 0x45, 0x00,
0x14, 0x51, 0x4b, 0x40, 0x84, 0xa2, 0x8a, 0x28,
0x00, 0xa2, 0x96, 0x8a, 0x00, 0x4a, 0x29, 0x68,
0xa0, 0x04, 0xa2, 0x96, 0x8a, 0x00, 0x4a, 0x29,
0x68, 0xa0, 0x41, 0x45, 0x14, 0x50, 0x01, 0x45,
0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45,
0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45,
0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45,
0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45,
0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x49,
0x4b, 0x45, 0x00, 0x25, 0x14, 0xb4, 0x50, 0x01,
0x45, 0x14, 0x50, 0x30, 0xa4, 0xa5, 0xa2, 0x81,
0x09, 0x45, 0x2d, 0x14, 0x00, 0x94, 0x52, 0xd1,
0x40, 0x09, 0x45, 0x2d, 0x14, 0x00, 0x94, 0x52,
0xd1, 0x40, 0x05, 0x14, 0xb4, 0x94, 0x0c, 0x28,
0xa2, 0x8a, 0x04, 0x14, 0x51, 0x45, 0x00, 0x14,
0x51, 0x45, 0x00, 0x14, 0x51, 0x45, 0x00, 0x14,
0x51, 0x45, 0x00, 0x14, 0x52, 0xd1, 0x40, 0xc4,
0xa2, 0x96, 0x8a, 0x04, 0x25, 0x14, 0xb4, 0x50,
0x31, 0x28, 0xa5, 0xa2, 0x80, 0x12, 0x8a, 0x5a,
0x4a, 0x00, 0x5a, 0x28, 0xa2, 0x80, 0x0a, 0x29,
0x68, 0xa0, 0x04, 0xa2, 0x96, 0x8a, 0x00, 0x4a,
0x29, 0x68, 0xa0, 0x04, 0xa2, 0x9d, 0x45, 0x03,
0x1b, 0x45, 0x3a, 0x8a, 0x00, 0x6d, 0x14, 0xea,
0x28, 0x10, 0x94, 0x52, 0xd1, 0x40, 0x09, 0x4b,
0x45, 0x14, 0x08, 0x28, 0xa2, 0x8a, 0x06, 0x14,
0x52, 0xd1, 0x40, 0x09, 0x45, 0x2d, 0x2d, 0x00,
0x25, 0x14, 0xb4, 0x50, 0x21, 0x28, 0xa5, 0xa2,
0x81, 0x89, 0x4b, 0x45, 0x14, 0x00, 0x51, 0x45,
0x14, 0x00, 0x51, 0x4b, 0x45, 0x00, 0x25, 0x14,
0xb4, 0x50, 0x01, 0x45, 0x2d, 0x14, 0x0c, 0x28,
0xa2, 0x8a, 0x00, 0x4a, 0x5a, 0x5a, 0x28, 0x01,
0x28, 0xa5, 0xa2, 0x90, 0x05, 0x14, 0x51, 0x40,
0x05, 0x14, 0xb4, 0x52, 0x01, 0x28, 0xa5, 0xa2,
0x80, 0x0a, 0x28, 0xa7, 0x50, 0x03, 0x68, 0xa7,
0x51, 0x40, 0x0d, 0xa7, 0x51, 0x45, 0x00, 0x14,
0x51, 0x4b, 0x40, 0x09, 0x45, 0x2d, 0x2d, 0x00,
0x25, 0x14, 0xb4, 0x50, 0x02, 0x51, 0x4b, 0x45,
0x00, 0x14, 0x51, 0x45, 0x00, 0x14, 0x51, 0x45,
0x00, 0x14, 0xb4, 0x53, 0xa8, 0x18, 0xda, 0x5a,
0x5a, 0x28, 0x10, 0x94, 0xb4, 0x51, 0x40, 0xc2,
0x8a, 0x5a, 0x29, 0x00, 0x94, 0x52, 0xd1, 0x40,
0x05, 0x14, 0x51, 0x4c, 0x02, 0x96, 0x8a, 0x29,
0x00, 0x52, 0xd2, 0x52, 0xd0, 0x01, 0x45, 0x14,
0x50, 0x02, 0xd1, 0x45, 0x14, 0x0c, 0x28, 0xa2,
0x96, 0x80, 0x0a, 0x28, 0xa2, 0x81, 0x85, 0x2d,
0x14, 0x50, 0x01, 0x45, 0x14, 0xb4, 0x80, 0x4a,
0x29, 0x68, 0xa0, 0x62, 0xd1, 0x45, 0x14, 0xc0,
0x28, 0xa5, 0xa2, 0x90, 0x0d, 0xa5, 0xa5, 0xa2,
0x80, 0x0a, 0x5a, 0x28, 0xa4, 0x01, 0x45, 0x14,
0xb4, 0x00, 0x94, 0x52, 0xd1, 0x40, 0x05, 0x14,
0x51, 0x4c, 0x02, 0x8a, 0x29, 0x68, 0x01, 0x28,
0xa5, 0xa2, 0x80, 0x16, 0x8a, 0x28, 0xa4, 0x30,
0xa7, 0x55, 0x7b, 0xbb, 0x5f, 0xb7, 0xc1, 0xe4,
0xff, 0x00, 0xaa, 0xac, 0x49, 0xb4, 0x6d, 0x57,
0x4b, 0xf3, 0x66, 0xd2, 0xef, 0x3c, 0xd8, 0x69,
0x17, 0x4c, 0xe9, 0x2b, 0x07, 0xc4, 0x32, 0xcb,
0x75, 0xa4, 0xff, 0x00, 0xc4, 0xae, 0x68, 0xbf,
0x75, 0xfe, 0xb2, 0xb9, 0x09, 0xb5, 0x9d, 0x42,
0x5f, 0x36, 0xcf, 0xce, 0xff, 0x00, 0x5b, 0x56,
0x3c, 0x3d, 0x6b, 0xfd, 0xa9, 0xab, 0x4b, 0x67,
0x3f, 0xfc, 0xf3, 0xff, 0x00, 0x96, 0x74, 0x8d,
0xbd, 0x99, 0x9b, 0x0d, 0xfc, 0xb2, 0xf9, 0xbf,
0xeb, 0x7f, 0x7b, 0x5a, 0xf0, 0xf8, 0x8e, 0xee,
0x2d, 0x27, 0xec, 0x70, 0x7f, 0xdf, 0xca, 0x66,
0xa3, 0xe1, 0x7f, 0xb0, 0x79, 0xbe, 0x46, 0xa5,
0x17, 0xfd, 0x73, 0x92, 0xb0, 0xe1, 0xa8, 0x0f,
0x66, 0x76, 0x90, 0xf8, 0xa2, 0x29, 0x6c, 0x3c,
0x9f, 0xde, 0xf9, 0xde, 0x5f, 0xef, 0x24, 0xaa,
0x76, 0x9e, 0x2d, 0xbb, 0x8b, 0x49, 0xf2, 0x7f,
0xe5, 0xb5, 0x62, 0x5a, 0x5f, 0xcb, 0x6b, 0xfe,
0xa3, 0xfe, 0x5a, 0xd1, 0xe5, 0x45, 0xff, 0x00,
0x2c, 0x29, 0x0f, 0xd9, 0x97, 0xff, 0x00, 0xb6,
0x65, 0xba, 0x82, 0x5f, 0xb7, 0x4d, 0xe6, 0xcd,
0xff, 0x00, 0x2c, 0xe2, 0xae, 0xa7, 0x49, 0xd5,
0x3f, 0xe2, 0x9a, 0xf3, 0xa7, 0xff, 0x00, 0x5d,
0x15, 0x70, 0xb4, 0x43, 0xfb, 0xdf, 0xf5, 0xf3,
0x4b, 0xff, 0x00, 0x5c, 0xe8, 0xf6, 0x83, 0xf6,
0x67, 0xa1, 0x68, 0x7a, 0xcc, 0x5a, 0xcf, 0xfd,
0x32, 0x9b, 0xfe, 0x79, 0xd5, 0xeb, 0xbb, 0xab,
0x4b, 0x08, 0x3f, 0xd3, 0xab, 0xcd, 0x66, 0xba,
0xfb, 0x57, 0xfd, 0x32, 0x9a, 0x2a, 0x96, 0xd2,
0xff, 0x00, 0xcd, 0xbf, 0xf3, 0xb5, 0x49, 0xbc,
0xdf, 0x2b, 0xfe, 0x59, 0xd3, 0x27, 0xd9, 0x9a,
0x30, 0xdd, 0x7d, 0xab, 0x5e, 0xfb, 0x1d, 0x8c,
0xde, 0x55, 0x9c, 0xb2, 0x79, 0x95, 0xd3, 0x6a,
0x3a, 0xa7, 0xd9, 0x75, 0xdb, 0x5b, 0x39, 0xff,
0x00, 0xd4, 0xcb, 0xff, 0x00, 0x2d, 0x2b, 0xcf,
0xbe, 0xdf, 0xe5, 0x6a, 0xdf, 0x6c, 0x83, 0xfe,
0x59, 0x54, 0xda, 0xb6, 0xb3, 0x2e, 0xb3, 0x7f,
0xe7, 0x7f, 0xaa, 0xff, 0xff, 0xd9,
},
0,
[]*rtp.Packet{
{
Header: rtp.Header{
Version: 2,
Marker: false,
PayloadType: 26,
SequenceNumber: 17645,
Timestamp: 2289528607,
SSRC: 0x9dbb7812,
},
Payload: []byte{
0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xf0, 0x87,
0x00, 0x00, 0x00, 0x80, 0x0d, 0x09, 0x0a, 0x0b,
0x0a, 0x08, 0x0d, 0x0b, 0x0a, 0x0b, 0x0e, 0x0e,
0x0d, 0x0f, 0x13, 0x20, 0x15, 0x13, 0x12, 0x12,
0x13, 0x27, 0x1c, 0x1e, 0x17, 0x20, 0x2e, 0x29,
0x31, 0x30, 0x2e, 0x29, 0x2d, 0x2c, 0x33, 0x3a,
0x4a, 0x3e, 0x33, 0x36, 0x46, 0x37, 0x2c, 0x2d,
0x40, 0x57, 0x41, 0x46, 0x4c, 0x4e, 0x52, 0x53,
0x52, 0x32, 0x3e, 0x5a, 0x61, 0x5a, 0x50, 0x60,
0x4a, 0x51, 0x52, 0x4f, 0x0e, 0x0e, 0x0e, 0x13,
0x11, 0x13, 0x26, 0x15, 0x15, 0x26, 0x4f, 0x35,
0x2d, 0x35, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f,
0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f,
0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f,
0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f,
0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f,
0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f,
0x4f, 0x4f, 0x4f, 0x4f, 0x92, 0x8a, 0x28, 0xaf,
0x54, 0xf2, 0x42, 0x8a, 0x28, 0xa0, 0x02, 0x96,
0x92, 0x96, 0x80, 0x0a, 0x4a, 0x75, 0x25, 0x02,
0x12, 0x8a, 0x5a, 0x28, 0x18, 0x94, 0x52, 0xd1,
0x40, 0x09, 0x45, 0x2d, 0x14, 0x08, 0x29, 0x69,
0x29, 0x68, 0x00, 0xa5, 0xa4, 0xa5, 0xa0, 0x02,
0x8a, 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x04,
0xa5, 0xa2, 0x8a, 0x00, 0x5a, 0x28, 0xa2, 0x80,
0x0a, 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80,
0x12, 0x8a, 0x5a, 0x28, 0x24, 0x29, 0x69, 0x29,
0x68, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x8a,
0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x8a,
0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x8a,
0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x8a,
0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x8a,
0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa4, 0xa5,
0xa4, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02, 0x8a,
0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02, 0x8a,
0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02, 0x8a,
0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02, 0x8a,
0x28, 0xa0, 0x02, 0x96, 0x92, 0x96, 0x80, 0x0a,
0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80, 0x0a,
0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80, 0x0a,
0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80, 0x0a,
0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80, 0x0a,
0x28, 0xa2, 0x81, 0x85, 0x14, 0x51, 0x40, 0x05,
0x14, 0x51, 0x40, 0x05, 0x14, 0x51, 0x40, 0x05,
0x14, 0x52, 0xd0, 0x01, 0x45, 0x14, 0x50, 0x01,
0x45, 0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01,
0x45, 0x2d, 0x14, 0x00, 0x94, 0xb4, 0x51, 0x40,
0x05, 0x14, 0x52, 0xd0, 0x02, 0x51, 0x4b, 0x45,
0x00, 0x25, 0x2d, 0x14, 0x50, 0x01, 0x45, 0x14,
0x50, 0x01, 0x45, 0x14, 0x50, 0x20, 0xa5, 0xa4,
0xa5, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02, 0x8a,
0x28, 0xa0, 0x02, 0x8a, 0x5a, 0x28, 0x18, 0x94,
0xb4, 0x51, 0x40, 0xc2, 0x8a, 0x28, 0xa0, 0x05,
0xa2, 0x92, 0x9d, 0x40, 0x05, 0x14, 0x51, 0x48,
0x02, 0x8a, 0x28, 0xa4, 0x01, 0x4b, 0x49, 0x4b,
0x40, 0x05, 0x14, 0x51, 0x40, 0x05, 0x14, 0xb4,
0x50, 0x02, 0x51, 0x4b, 0x45, 0x00, 0x25, 0x2d,
0x14, 0x50, 0x03, 0xa8, 0xa2, 0x8a, 0x00, 0x28,
0xa2, 0x8a, 0x00, 0x5a, 0x29, 0x29, 0x68, 0x00,
0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x96, 0x8a, 0x06,
0x25, 0x14, 0xb4, 0x50, 0x01, 0x45, 0x14, 0x50,
0x02, 0xd2, 0xd2, 0x52, 0xd0, 0x20, 0xa2, 0x8a,
0x28, 0x01, 0x68, 0xa2, 0x8a, 0x40, 0x14, 0x51,
0x45, 0x30, 0x0a, 0x5a, 0x4a, 0x5a, 0x06, 0x14,
0x51, 0x45, 0x02, 0x0a, 0x28, 0xa5, 0xa0, 0x62,
0x51, 0x4b, 0x45, 0x00, 0x2d, 0x14, 0x51, 0x48,
0x02, 0x8a, 0x28, 0xa0, 0x61, 0x45, 0x14, 0x50,
0x03, 0xa8, 0xa2, 0x8a, 0x06, 0x2d, 0x14, 0x51,
0x48, 0x02, 0x8a, 0x28, 0xa4, 0x30, 0xa2, 0x8a,
0x2a, 0x80, 0x28, 0xa2, 0x8a, 0x00, 0x28, 0xa2,
0x8a, 0x92, 0x45, 0xa5, 0xa2, 0x96, 0x82, 0x82,
0x8a, 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x05,
0xa2, 0x8a, 0x29, 0x80, 0x52, 0xd2, 0x52, 0xd0,
0x01, 0x45, 0x14, 0x50, 0x01, 0x4e, 0xa2, 0x8a,
0x43, 0x0a, 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa4,
0xa4, 0x31, 0x68, 0xa4, 0xf3, 0x62, 0xff, 0x00,
0x9e, 0xd1, 0x7e, 0xea, 0x9f, 0xfe, 0xb6, 0xa4,
0x62, 0x52, 0x53, 0xa9, 0x28, 0x01, 0x28, 0xa2,
0x6f, 0xdd, 0x7f, 0xaf, 0xa5, 0xa0, 0x62, 0x51,
0x4b, 0x45, 0x00, 0x25, 0x14, 0xbf, 0xba, 0xff,
0x00, 0xae, 0xb4, 0xea, 0x60, 0x36, 0x9d, 0x49,
0x49, 0x34, 0xb1, 0x45, 0xfe, 0xbe, 0x6f, 0x2a,
0x98, 0x0f, 0xa2, 0xb9, 0xbd, 0x0f, 0x59, 0x97,
0x54, 0xf1, 0x2d, 0xd7, 0xfc, 0xf9, 0xd7, 0x49,
0x52, 0x30, 0xac, 0x7d, 0x5b, 0x54, 0xd5, 0x74,
0xbf, 0xdf, 0x41, 0x67, 0x15, 0xd4, 0x35, 0x63,
0x56, 0xd5, 0x22, 0xd2, 0xe0, 0xf3, 0xbc, 0x99,
0x65, 0xae, 0x4b, 0x51, 0xf1, 0x44, 0x52, 0xff,
0x00, 0xc7, 0x97, 0x9b, 0xe4, 0xd0, 0x69, 0x4c,
0x76, 0xa3, 0xe2, 0xd9, 0xa5, 0x82, 0x29, 0xb4,
0xbf, 0xdd, 0x79, 0xbf, 0xeb, 0x22, 0xaa, 0x7e,
0x1e, 0xd5, 0x3e, 0xc1, 0x3f, 0xfa, 0x9f, 0xfa,
0xe9, 0x25, 0x61, 0xd6, 0x9e, 0x87, 0xf6, 0xbf,
0xed, 0x68, 0xbc, 0x8a, 0x82, 0xcf, 0x4e, 0x86,
0x5f, 0x36, 0x0a, 0x92, 0x9b, 0xff, 0x00, 0x5d,
0xeb, 0x3a, 0x6d, 0x53, 0xfd, 0x3f, 0xec, 0x76,
0x3f, 0xbd, 0xff, 0x00, 0xa6, 0xbf, 0xc1, 0x54,
0x66, 0x69, 0x51, 0x50, 0x4b, 0x2c, 0x51, 0x7e,
0xe7, 0xce, 0xf3, 0x66, 0xff, 0x00, 0xa6, 0x75,
0x35, 0x00, 0x2d, 0x65, 0x6a, 0xde, 0x23, 0xd3,
0xec, 0x3f, 0xe5, 0xb7, 0x9b, 0x34, 0x5f, 0xf2,
0xce, 0xb4, 0x7e, 0xd5, 0x69, 0x2f, 0xfc, 0xbe,
0x45, 0x5e, 0x7b, 0xe2, 0x7d, 0x07, 0xec, 0x1e,
0x6e, 0xa5, 0xf6, 0xc8, 0xa5, 0xf3, 0x6a, 0xc0,
0xdf, 0xa2, 0x9d, 0x45, 0x6c, 0x72, 0x0d, 0xa5,
0xa5, 0xa2, 0x80, 0x12, 0x96, 0x8a, 0x28, 0x10,
0x51, 0x4b, 0x45, 0x00, 0x25, 0x14, 0xb4, 0x50,
0x02, 0x51, 0x4b, 0x45, 0x03, 0x12, 0x8a, 0x5a,
0x28, 0x10, 0x94, 0xb4, 0x51, 0x40, 0x05, 0x14,
0x51, 0x40, 0x05, 0x14, 0xb4, 0x50, 0x02, 0x51,
0x4b, 0x45, 0x00, 0x14, 0x51, 0x4b, 0x40, 0x84,
0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x96, 0x8a, 0x00,
0x4a, 0x29, 0x68, 0xa0, 0x04, 0xa2, 0x96, 0x8a,
0x00, 0x4a, 0x29, 0x68, 0xa0, 0x41, 0x45, 0x14,
0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45, 0x14,
0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45, 0x14,
0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45, 0x14,
0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45, 0x14,
0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45, 0x14,
0x50, 0x01, 0x49, 0x4b, 0x45, 0x00, 0x25, 0x14,
0xb4, 0x50, 0x01, 0x45, 0x14, 0x50, 0x30, 0xa4,
0xa5, 0xa2, 0x81, 0x09, 0x45, 0x2d, 0x14, 0x00,
0x94, 0x52, 0xd1, 0x40, 0x09, 0x45, 0x2d, 0x14,
0x00, 0x94, 0x52, 0xd1, 0x40, 0x05, 0x14, 0xb4,
0x94, 0x0c, 0x28, 0xa2, 0x8a, 0x04, 0x14, 0x51,
0x45, 0x00, 0x14, 0x51, 0x45, 0x00, 0x14, 0x51,
0x45, 0x00, 0x14, 0x51, 0x45, 0x00, 0x14, 0x52,
0xd1, 0x40, 0xc4, 0xa2, 0x96, 0x8a, 0x04, 0x25,
0x14, 0xb4, 0x50, 0x31, 0x28, 0xa5, 0xa2, 0x80,
0x12, 0x8a, 0x5a, 0x4a, 0x00, 0x5a, 0x28, 0xa2,
0x80, 0x0a, 0x29, 0x68, 0xa0, 0x04, 0xa2, 0x96,
0x8a, 0x00, 0x4a, 0x29, 0x68, 0xa0, 0x04, 0xa2,
0x9d, 0x45, 0x03, 0x1b, 0x45, 0x3a, 0x8a, 0x00,
0x6d, 0x14, 0xea, 0x28, 0x10, 0x94, 0x52, 0xd1,
0x40, 0x09, 0x4b, 0x45, 0x14, 0x08, 0x28, 0xa2,
0x8a, 0x06, 0x14, 0x52, 0xd1, 0x40, 0x09, 0x45,
0x2d, 0x2d, 0x00, 0x25, 0x14, 0xb4, 0x50, 0x21,
0x28, 0xa5, 0xa2, 0x81, 0x89, 0x4b, 0x45, 0x14,
0x00, 0x51, 0x45, 0x14, 0x00, 0x51, 0x4b, 0x45,
0x00, 0x25, 0x14, 0xb4, 0x50, 0x01, 0x45, 0x2d,
0x14, 0x0c, 0x28, 0xa2, 0x8a, 0x00, 0x4a, 0x5a,
0x5a, 0x28, 0x01, 0x28, 0xa5, 0xa2, 0x90, 0x05,
0x14, 0x51, 0x40, 0x05, 0x14, 0xb4, 0x52, 0x01,
0x28, 0xa5, 0xa2, 0x80, 0x0a, 0x28, 0xa7, 0x50,
0x03, 0x68, 0xa7, 0x51, 0x40, 0x0d, 0xa7, 0x51,
0x45, 0x00, 0x14, 0x51, 0x4b, 0x40, 0x09, 0x45,
0x2d, 0x2d, 0x00, 0x25, 0x14, 0xb4, 0x50, 0x02,
0x51, 0x4b, 0x45, 0x00, 0x14, 0x51, 0x45, 0x00,
0x14, 0x51, 0x45, 0x00, 0x14, 0xb4, 0x53, 0xa8,
0x18, 0xda, 0x5a, 0x5a, 0x28, 0x10, 0x94, 0xb4,
0x51, 0x40, 0xc2, 0x8a, 0x5a, 0x29, 0x00, 0x94,
0x52, 0xd1, 0x40, 0x05, 0x14, 0x51, 0x4c, 0x02,
0x96, 0x8a, 0x29, 0x00, 0x52, 0xd2, 0x52, 0xd0,
0x01, 0x45, 0x14, 0x50, 0x02, 0xd1, 0x45, 0x14,
0x0c, 0x28, 0xa2, 0x96, 0x80, 0x0a, 0x28, 0xa2,
0x81, 0x85, 0x2d, 0x14, 0x50, 0x01, 0x45, 0x14,
0xb4, 0x80, 0x4a, 0x29, 0x68, 0xa0, 0x62, 0xd1,
0x45, 0x14, 0xc0, 0x28, 0xa5, 0xa2, 0x90, 0x0d,
0xa5, 0xa5, 0xa2, 0x80, 0x0a, 0x5a, 0x28, 0xa4,
0x01, 0x45, 0x14, 0xb4, 0x00, 0x94, 0x52, 0xd1,
0x40, 0x05, 0x14, 0x51, 0x4c, 0x02, 0x8a, 0x29,
0x68, 0x01, 0x28, 0xa5, 0xa2, 0x80, 0x16, 0x8a,
0x28, 0xa4, 0x30, 0xa7, 0x55, 0x7b, 0xbb, 0x5f,
0xb7, 0xc1, 0xe4, 0xff, 0x00, 0xaa, 0xac, 0x49,
0xb4, 0x6d, 0x57, 0x4b, 0xf3, 0x66, 0xd2, 0xef,
0x3c, 0xd8, 0x69, 0x17, 0x4c, 0xe9, 0x2b, 0x07,
0xc4, 0x32, 0xcb, 0x75, 0xa4, 0xff, 0x00, 0xc4,
0xae, 0x68, 0xbf, 0x75, 0xfe, 0xb2, 0xb9, 0x09,
0xb5, 0x9d, 0x42, 0x5f, 0x36, 0xcf, 0xce, 0xff,
0x00, 0x5b, 0x56, 0x3c, 0x3d, 0x6b, 0xfd, 0xa9,
0xab, 0x4b, 0x67, 0x3f, 0xfc, 0xf3, 0xff, 0x00,
0x96, 0x74, 0x8d, 0xbd, 0x99, 0x9b, 0x0d, 0xfc,
0xb2, 0xf9, 0xbf, 0xeb, 0x7f, 0x7b, 0x5a, 0xf0,
0xf8, 0x8e, 0xee, 0x2d, 0x27, 0xec, 0x70, 0x7f,
0xdf, 0xca, 0x66, 0xa3, 0xe1, 0x7f, 0xb0, 0x79,
0xbe, 0x46, 0xa5, 0x17, 0xfd, 0x73, 0x92, 0xb0,
0xe1, 0xa8, 0x0f, 0x66, 0x76, 0x90, 0xf8, 0xa2,
0x29, 0x6c, 0x3c, 0x9f, 0xde, 0xf9, 0xde, 0x5f,
0xef, 0x24, 0xaa, 0x76, 0x9e, 0x2d, 0xbb, 0x8b,
0x49, 0xf2, 0x7f, 0xe5, 0xb5, 0x62, 0x5a, 0x5f,
0xcb, 0x6b, 0xfe, 0xa3, 0xfe, 0x5a, 0xd1, 0xe5,
0x45, 0xff, 0x00, 0x2c, 0x29, 0x0f, 0xd9, 0x97,
0xff, 0x00, 0xb6, 0x65,
},
},
{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 26,
SequenceNumber: 17646,
Timestamp: 2289528607,
SSRC: 0x9dbb7812,
},
Payload: []byte{
0x00, 0x00, 0x05, 0x28, 0x01, 0xff, 0xf0, 0x87,
0xba, 0x82, 0x5f, 0xb7, 0x4d, 0xe6, 0xcd, 0xff,
0x00, 0x2c, 0xe2, 0xae, 0xa7, 0x49, 0xd5, 0x3f,
0xe2, 0x9a, 0xf3, 0xa7, 0xff, 0x00, 0x5d, 0x15,
0x70, 0xb4, 0x43, 0xfb, 0xdf, 0xf5, 0xf3, 0x4b,
0xff, 0x00, 0x5c, 0xe8, 0xf6, 0x83, 0xf6, 0x67,
0xa1, 0x68, 0x7a, 0xcc, 0x5a, 0xcf, 0xfd, 0x32,
0x9b, 0xfe, 0x79, 0xd5, 0xeb, 0xbb, 0xab, 0x4b,
0x08, 0x3f, 0xd3, 0xab, 0xcd, 0x66, 0xba, 0xfb,
0x57, 0xfd, 0x32, 0x9a, 0x2a, 0x96, 0xd2, 0xff,
0x00, 0xcd, 0xbf, 0xf3, 0xb5, 0x49, 0xbc, 0xdf,
0x2b, 0xfe, 0x59, 0xd3, 0x27, 0xd9, 0x9a, 0x30,
0xdd, 0x7d, 0xab, 0x5e, 0xfb, 0x1d, 0x8c, 0xde,
0x55, 0x9c, 0xb2, 0x79, 0x95, 0xd3, 0x6a, 0x3a,
0xa7, 0xd9, 0x75, 0xdb, 0x5b, 0x39, 0xff, 0x00,
0xd4, 0xcb, 0xff, 0x00, 0x2d, 0x2b, 0xcf, 0xbe,
0xdf, 0xe5, 0x6a, 0xdf, 0x6c, 0x83, 0xfe, 0x59,
0x54, 0xda, 0xb6, 0xb3, 0x2e, 0xb3, 0x7f, 0xe7,
0x7f, 0xaa, 0xff, 0xff, 0xd9,
},
},
},
},
}
func TestDecode(t *testing.T) {
for _, ca := range cases {
t.Run(ca.name, func(t *testing.T) {
d := &Decoder{}
d.Init()
for _, pkt := range ca.pkts {
image, pts, err := d.Decode(pkt)
if err == ErrMorePacketsNeeded {
continue
}
require.NoError(t, err)
require.Equal(t, ca.pts, pts)
require.Equal(t, ca.image, image)
}
})
}
}

View File

@@ -0,0 +1,273 @@
package rtpmjpeg
import (
"crypto/rand"
"fmt"
"sort"
"time"
"github.com/pion/rtp"
"github.com/aler9/gortsplib/v2/pkg/formatdecenc/rtpmjpeg/headers"
"github.com/aler9/gortsplib/v2/pkg/formatdecenc/rtpmjpeg/jpeg"
)
const (
rtpVersion = 2
)
func randUint32() uint32 {
var b [4]byte
rand.Read(b[:])
return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])
}
// Encoder is a RTP/MJPEG encoder.
type Encoder struct {
// SSRC of packets (optional).
// It defaults to a random value.
SSRC *uint32
// initial sequence number of packets (optional).
// It defaults to a random value.
InitialSequenceNumber *uint16
// initial timestamp of packets (optional).
// It defaults to a random value.
InitialTimestamp *uint32
// maximum size of packet payloads (optional).
// It defaults to 1460.
PayloadMaxSize int
sequenceNumber uint16
}
// Init initializes the encoder.
func (e *Encoder) Init() {
if e.SSRC == nil {
v := randUint32()
e.SSRC = &v
}
if e.InitialSequenceNumber == nil {
v := uint16(randUint32())
e.InitialSequenceNumber = &v
}
if e.InitialTimestamp == nil {
v := randUint32()
e.InitialTimestamp = &v
}
if e.PayloadMaxSize == 0 {
e.PayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header)
}
e.sequenceNumber = *e.InitialSequenceNumber
}
func (e *Encoder) encodeTimestamp(ts time.Duration) uint32 {
return *e.InitialTimestamp + uint32(ts.Seconds()*rtpClockRate)
}
// Encode encodes an image into RTP/MJPEG packets.
func (e *Encoder) Encode(image []byte, pts time.Duration) ([]*rtp.Packet, error) {
l := len(image)
if l < 2 || image[0] != 0xFF || image[1] != jpeg.MarkerStartOfImage {
return nil, fmt.Errorf("SOI not found")
}
image = image[2:]
var sof *jpeg.StartOfFrame1
var dri *jpeg.DefineRestartInterval
quantizationTables := make(map[uint8][]byte)
var data []byte
outer:
for {
if len(image) < 2 {
break
}
h0, h1 := image[0], image[1]
image = image[2:]
if h0 != 0xFF {
return nil, fmt.Errorf("invalid image")
}
switch h1 {
case 0xE0, 0xE1, 0xE2, // JFIF
jpeg.MarkerDefineHuffmanTable:
mlen := int(image[0])<<8 | int(image[1])
if len(image) < mlen {
return nil, fmt.Errorf("image is too short")
}
image = image[mlen:]
case jpeg.MarkerDefineQuantizationTable:
mlen := int(image[0])<<8 | int(image[1])
if len(image) < mlen {
return nil, fmt.Errorf("image is too short")
}
var dqt jpeg.DefineQuantizationTable
err := dqt.Unmarshal(image[2:mlen])
if err != nil {
return nil, err
}
image = image[mlen:]
for _, t := range dqt.Tables {
quantizationTables[t.ID] = t.Data
}
case jpeg.MarkerDefineRestartInterval:
mlen := int(image[0])<<8 | int(image[1])
if len(image) < mlen {
return nil, fmt.Errorf("image is too short")
}
dri = &jpeg.DefineRestartInterval{}
err := dri.Unmarshal(image[2:mlen])
if err != nil {
return nil, err
}
image = image[mlen:]
case jpeg.MarkerStartOfFrame1:
mlen := int(image[0])<<8 | int(image[1])
if len(image) < mlen {
return nil, fmt.Errorf("image is too short")
}
sof = &jpeg.StartOfFrame1{}
err := sof.Unmarshal(image[2:mlen])
if err != nil {
return nil, err
}
image = image[mlen:]
if sof.Width > maxDimension {
return nil, fmt.Errorf("an image with width of %d can't be sent with RTSP", sof.Width)
}
if sof.Height > maxDimension {
return nil, fmt.Errorf("an image with height of %d can't be sent with RTSP", sof.Height)
}
if (sof.Width % 8) != 0 {
return nil, fmt.Errorf("width must be multiple of 8")
}
if (sof.Height % 8) != 0 {
return nil, fmt.Errorf("height must be multiple of 8")
}
case jpeg.MarkerStartOfScan:
mlen := int(image[0])<<8 | int(image[1])
if len(image) < mlen {
return nil, fmt.Errorf("image is too short")
}
var sos jpeg.StartOfScan
err := sos.Unmarshal(image[2:mlen])
if err != nil {
return nil, err
}
image = image[mlen:]
data = image
break outer
default:
return nil, fmt.Errorf("unknown marker: 0x%.2x", h1)
}
}
if sof == nil {
return nil, fmt.Errorf("SOF not found")
}
if sof.Type != 1 {
return nil, fmt.Errorf("JPEG type %d is not supported", sof.Type)
}
if data == nil {
return nil, fmt.Errorf("image data not found")
}
jh := headers.JPEG{
TypeSpecific: 0,
Type: sof.Type,
Quantization: 255,
Width: sof.Width,
Height: sof.Height,
}
if dri != nil {
jh.Type += 64
}
first := true
offset := 0
var ret []*rtp.Packet
for len(data) > 0 {
var buf []byte
jh.FragmentOffset = uint32(offset)
buf = jh.Marshal(buf)
if dri != nil {
buf = headers.RestartMarker{
Interval: dri.Interval,
Count: 0xFFFF,
}.Marshal(buf)
}
if first {
first = false
qth := headers.QuantizationTable{}
ids := make([]uint8, len(quantizationTables))
i := 0
for id := range quantizationTables {
ids[i] = id
i++
}
sort.Slice(ids, func(i, j int) bool {
return ids[i] < ids[j]
})
for _, id := range ids {
qth.Tables = append(qth.Tables, quantizationTables[id]...)
}
buf = qth.Marshal(buf)
}
remaining := e.PayloadMaxSize - len(buf)
ldata := len(data)
if remaining > ldata {
remaining = ldata
}
buf = append(buf, data[:remaining]...)
data = data[remaining:]
offset += remaining
ret = append(ret, &rtp.Packet{
Header: rtp.Header{
Version: rtpVersion,
PayloadType: 26,
SequenceNumber: e.sequenceNumber,
Timestamp: e.encodeTimestamp(pts),
SSRC: *e.SSRC,
Marker: len(data) == 0,
},
Payload: buf,
})
e.sequenceNumber++
}
return ret, nil
}

View File

@@ -0,0 +1,33 @@
package rtpmjpeg
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestEncode(t *testing.T) {
for _, ca := range cases {
t.Run(ca.name, func(t *testing.T) {
e := &Encoder{
SSRC: func() *uint32 {
v := uint32(0x9dbb7812)
return &v
}(),
InitialSequenceNumber: func() *uint16 {
v := uint16(0x44ed)
return &v
}(),
InitialTimestamp: func() *uint32 {
v := uint32(2289528607)
return &v
}(),
}
e.Init()
pkts, err := e.Encode(ca.image, ca.pts)
require.NoError(t, err)
require.Equal(t, ca.pkts, pkts)
})
}
}

View File

@@ -0,0 +1,2 @@
// Package headers contains RTP/MJPEG headers.
package headers

View File

@@ -0,0 +1,51 @@
package headers
import (
"fmt"
)
// JPEG is a JPEG header.
type JPEG struct {
TypeSpecific uint8
FragmentOffset uint32
Type uint8
Quantization uint8
Width int
Height int
}
// Unmarshal decodes the header.
func (h *JPEG) Unmarshal(byts []byte) (int, error) {
if len(byts) < 8 {
return 0, fmt.Errorf("buffer is too short")
}
h.TypeSpecific = byts[0]
h.FragmentOffset = uint32(byts[1])<<16 | uint32(byts[2])<<8 | uint32(byts[3])
h.Type = byts[4]
if h.Type != 1 {
return 0, fmt.Errorf("Type %d is not supported", h.Type)
}
h.Quantization = byts[5]
if h.Quantization != 255 {
return 0, fmt.Errorf("Q %d is not supported", h.Quantization)
}
h.Width = int(byts[6]) * 8
h.Height = int(byts[7]) * 8
return 8, nil
}
// Marshal encodes the header.
func (h JPEG) Marshal(byts []byte) []byte {
byts = append(byts, h.TypeSpecific)
byts = append(byts, []byte{byte(h.FragmentOffset >> 16), byte(h.FragmentOffset >> 8), byte(h.FragmentOffset)}...)
byts = append(byts, h.Type)
byts = append(byts, h.Quantization)
byts = append(byts, byte(h.Width/8))
byts = append(byts, byte(h.Height/8))
return byts
}

View File

@@ -0,0 +1,47 @@
package headers
import (
"testing"
"github.com/stretchr/testify/require"
)
var casesJpeg = []struct {
name string
enc []byte
dec JPEG
}{
{
"base",
[]byte{
0x0, 0x0, 0x0, 0x0, 0x1, 0xff, 0x8, 0x4,
},
JPEG{
TypeSpecific: 0,
Type: 1,
Quantization: 255,
Width: 64,
Height: 32,
},
},
}
func TestJpegUnmarshal(t *testing.T) {
for _, ca := range casesJpeg {
t.Run(ca.name, func(t *testing.T) {
var h JPEG
_, err := h.Unmarshal(ca.enc)
require.NoError(t, err)
require.Equal(t, ca.dec, h)
})
}
}
func TestJpegMarshal(t *testing.T) {
for _, ca := range casesJpeg {
t.Run(ca.name, func(t *testing.T) {
buf := ca.dec.Marshal(nil)
require.Equal(t, ca.enc, buf)
})
}
}

View File

@@ -0,0 +1,49 @@
package headers
import (
"fmt"
)
// QuantizationTable is a Quantization Table header.
type QuantizationTable struct {
MBZ uint8
Precision uint8
Tables []byte
}
// Unmarshal decodes the header.
func (h *QuantizationTable) Unmarshal(byts []byte) (int, error) {
if len(byts) < 4 {
return 0, fmt.Errorf("buffer is too short")
}
h.MBZ = byts[0]
h.Precision = byts[1]
if h.Precision != 0 {
return 0, fmt.Errorf("Precision %d is not supported", h.Precision)
}
length := int(byts[2])<<8 | int(byts[3])
switch length {
case 64, 128:
default:
return 0, fmt.Errorf("Quantization table length %d is not supported", length)
}
if (len(byts) - 4) < length {
return 0, fmt.Errorf("buffer is too short")
}
h.Tables = byts[4 : 4+length]
return 4 + length, nil
}
// Marshal encodes the header.
func (h QuantizationTable) Marshal(byts []byte) []byte {
byts = append(byts, h.MBZ)
byts = append(byts, h.Precision)
l := len(h.Tables)
byts = append(byts, []byte{byte(l >> 8), byte(l)}...)
byts = append(byts, h.Tables...)
return byts
}

View File

@@ -0,0 +1,54 @@
package headers
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
)
var casesQuantizationTable = []struct {
name string
enc []byte
dec QuantizationTable
}{
{
"base",
[]byte{
0x1, 0x0, 0x0, 0x40, 0x1, 0x2, 0x3, 0x4,
0x1, 0x2, 0x3, 0x4, 0x1, 0x2, 0x3, 0x4,
0x1, 0x2, 0x3, 0x4, 0x1, 0x2, 0x3, 0x4,
0x1, 0x2, 0x3, 0x4, 0x1, 0x2, 0x3, 0x4,
0x1, 0x2, 0x3, 0x4, 0x1, 0x2, 0x3, 0x4,
0x1, 0x2, 0x3, 0x4, 0x1, 0x2, 0x3, 0x4,
0x1, 0x2, 0x3, 0x4, 0x1, 0x2, 0x3, 0x4,
0x1, 0x2, 0x3, 0x4, 0x1, 0x2, 0x3, 0x4,
0x1, 0x2, 0x3, 0x4,
},
QuantizationTable{
MBZ: 1,
Precision: 0,
Tables: bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 64/4),
},
},
}
func TestQuantizationTableUnmarshal(t *testing.T) {
for _, ca := range casesQuantizationTable {
t.Run(ca.name, func(t *testing.T) {
var h QuantizationTable
_, err := h.Unmarshal(ca.enc)
require.NoError(t, err)
require.Equal(t, ca.dec, h)
})
}
}
func TestQuantizationTableMarshal(t *testing.T) {
for _, ca := range casesQuantizationTable {
t.Run(ca.name, func(t *testing.T) {
buf := ca.dec.Marshal(nil)
require.Equal(t, ca.enc, buf)
})
}
}

View File

@@ -0,0 +1,29 @@
package headers
import (
"fmt"
)
// RestartMarker is a Restart Marker header.
type RestartMarker struct {
Interval uint16
Count uint16
}
// Unmarshal decodes the header.
func (h *RestartMarker) Unmarshal(byts []byte) (int, error) {
if len(byts) < 4 {
return 0, fmt.Errorf("buffer is too short")
}
h.Interval = uint16(byts[0])<<8 | uint16(byts[1])
h.Count = uint16(byts[2])<<8 | uint16(byts[3])
return 4, nil
}
// Marshal encodes the header.
func (h RestartMarker) Marshal(byts []byte) []byte {
byts = append(byts, []byte{byte(h.Interval >> 8), byte(h.Interval)}...)
byts = append(byts, []byte{byte(h.Count >> 8), byte(h.Count)}...)
return byts
}

View File

@@ -0,0 +1,44 @@
package headers
import (
"testing"
"github.com/stretchr/testify/require"
)
var casesRestartMarker = []struct {
name string
enc []byte
dec RestartMarker
}{
{
"base",
[]byte{
0x4, 0xd2, 0xff, 0xff,
},
RestartMarker{
Interval: 1234,
Count: 0xffff,
},
},
}
func TestRestartMarkerUnmarshal(t *testing.T) {
for _, ca := range casesRestartMarker {
t.Run(ca.name, func(t *testing.T) {
var h RestartMarker
_, err := h.Unmarshal(ca.enc)
require.NoError(t, err)
require.Equal(t, ca.dec, h)
})
}
}
func TestRestartMarkerMarshal(t *testing.T) {
for _, ca := range casesRestartMarker {
t.Run(ca.name, func(t *testing.T) {
buf := ca.dec.Marshal(nil)
require.Equal(t, ca.enc, buf)
})
}
}

View File

@@ -0,0 +1,20 @@
package jpeg
// DefineHuffmanTable is a DHT marker.
type DefineHuffmanTable struct {
Codes []byte
Symbols []byte
TableNumber int
TableClass int
}
// Marshal encodes the marker.
func (m DefineHuffmanTable) Marshal(buf []byte) []byte {
buf = append(buf, []byte{0xFF, MarkerDefineHuffmanTable}...)
s := 3 + len(m.Codes) + len(m.Symbols)
buf = append(buf, []byte{byte(s >> 8), byte(s)}...) // length
buf = append(buf, []byte{byte(m.TableClass<<4) | byte(m.TableNumber)}...)
buf = append(buf, m.Codes...)
buf = append(buf, m.Symbols...)
return buf
}

View File

@@ -0,0 +1,35 @@
package jpeg
import (
"testing"
"github.com/stretchr/testify/require"
)
var casesDefineHuffmanTable = []struct {
name string
enc []byte
dec DefineHuffmanTable
}{
{
"base",
[]byte{
0xff, 0xc4, 0x0, 0x7, 0x43, 0x1, 0x2, 0x3, 0x4,
},
DefineHuffmanTable{
Codes: []byte{0x01, 0x02},
Symbols: []byte{0x03, 0x04},
TableNumber: 3,
TableClass: 4,
},
},
}
func TestDefineHuffmanTableMarshal(t *testing.T) {
for _, ca := range casesDefineHuffmanTable {
t.Run(ca.name, func(t *testing.T) {
byts := ca.dec.Marshal(nil)
require.Equal(t, ca.enc, byts)
})
}
}

View File

@@ -0,0 +1,65 @@
package jpeg
import (
"fmt"
)
// QuantizationTable is a DQT quantization table.
type QuantizationTable struct {
ID uint8
Precision uint8
Data []byte
}
// DefineQuantizationTable is a DQT marker.
type DefineQuantizationTable struct {
Tables []QuantizationTable
}
// Unmarshal decodes the marker.
func (m *DefineQuantizationTable) Unmarshal(buf []byte) error {
for len(buf) != 0 {
if len(buf) < 1 {
return fmt.Errorf("image is too short")
}
id := buf[0] & 0x0F
precision := buf[0] >> 4
buf = buf[1:]
if precision != 0 {
return fmt.Errorf("Precision %d is not supported", precision)
}
if len(buf) < 64 {
return fmt.Errorf("image is too short")
}
m.Tables = append(m.Tables, QuantizationTable{
ID: id,
Precision: precision,
Data: buf[:64],
})
buf = buf[64:]
}
return nil
}
// Marshal encodes the marker.
func (m DefineQuantizationTable) Marshal(buf []byte) []byte {
buf = append(buf, []byte{0xFF, MarkerDefineQuantizationTable}...)
// length
s := 2
for _, t := range m.Tables {
s += 1 + len(t.Data)
}
buf = append(buf, []byte{byte(s >> 8), byte(s)}...)
for _, t := range m.Tables {
buf = append(buf, []byte{(t.ID)}...)
buf = append(buf, t.Data...)
}
return buf
}

View File

@@ -0,0 +1,69 @@
package jpeg
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
)
var casesDefineQuantizationTable = []struct {
name string
enc []byte
dec DefineQuantizationTable
}{
{
"base",
[]byte{
0xff, 0xdb, 0x0, 0x84, 0x4, 0x1, 0x2, 0x3,
0x4, 0x1, 0x2, 0x3, 0x4, 0x1, 0x2, 0x3,
0x4, 0x1, 0x2, 0x3, 0x4, 0x1, 0x2, 0x3,
0x4, 0x1, 0x2, 0x3, 0x4, 0x1, 0x2, 0x3,
0x4, 0x1, 0x2, 0x3, 0x4, 0x1, 0x2, 0x3,
0x4, 0x1, 0x2, 0x3, 0x4, 0x1, 0x2, 0x3,
0x4, 0x1, 0x2, 0x3, 0x4, 0x1, 0x2, 0x3,
0x4, 0x1, 0x2, 0x3, 0x4, 0x1, 0x2, 0x3,
0x4, 0x1, 0x2, 0x3, 0x4, 0x5, 0x5, 0x6,
0x7, 0x8, 0x5, 0x6, 0x7, 0x8, 0x5, 0x6,
0x7, 0x8, 0x5, 0x6, 0x7, 0x8, 0x5, 0x6,
0x7, 0x8, 0x5, 0x6, 0x7, 0x8, 0x5, 0x6,
0x7, 0x8, 0x5, 0x6, 0x7, 0x8, 0x5, 0x6,
0x7, 0x8, 0x5, 0x6, 0x7, 0x8, 0x5, 0x6,
0x7, 0x8, 0x5, 0x6, 0x7, 0x8, 0x5, 0x6,
0x7, 0x8, 0x5, 0x6, 0x7, 0x8, 0x5, 0x6,
0x7, 0x8, 0x5, 0x6, 0x7, 0x8,
},
DefineQuantizationTable{
Tables: []QuantizationTable{
{
ID: 4,
Data: bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 64/4),
},
{
ID: 5,
Data: bytes.Repeat([]byte{0x05, 0x06, 0x07, 0x08}, 64/4),
},
},
},
},
}
func TestDefineQuantizationTableUnmarshal(t *testing.T) {
for _, ca := range casesDefineQuantizationTable {
t.Run(ca.name, func(t *testing.T) {
var h DefineQuantizationTable
err := h.Unmarshal(ca.enc[4:])
require.NoError(t, err)
require.Equal(t, ca.dec, h)
})
}
}
func TestDefineQuantizationTableMarshal(t *testing.T) {
for _, ca := range casesDefineQuantizationTable {
t.Run(ca.name, func(t *testing.T) {
byts := ca.dec.Marshal(nil)
require.Equal(t, ca.enc, byts)
})
}
}

View File

@@ -0,0 +1,20 @@
package jpeg
import (
"fmt"
)
// DefineRestartInterval is a DRI marker.
type DefineRestartInterval struct {
Interval uint16
}
// Unmarshal decodes the marker.
func (m *DefineRestartInterval) Unmarshal(buf []byte) error {
if len(buf) != 2 {
return fmt.Errorf("unsupported DRI size of %d", len(buf))
}
m.Interval = uint16(buf[0])<<8 | uint16(buf[1])
return nil
}

View File

@@ -0,0 +1,34 @@
package jpeg
import (
"testing"
"github.com/stretchr/testify/require"
)
var casesDefineRestartInterval = []struct {
name string
enc []byte
dec DefineRestartInterval
}{
{
"base",
[]byte{
0xff, MarkerDefineRestartInterval, 0x00, 0x04, 0xd0, 0xc7,
},
DefineRestartInterval{
Interval: 53447,
},
},
}
func TestDefineRestartIntervalUnmarshal(t *testing.T) {
for _, ca := range casesDefineRestartInterval {
t.Run(ca.name, func(t *testing.T) {
var h DefineRestartInterval
err := h.Unmarshal(ca.enc[4:])
require.NoError(t, err)
require.Equal(t, ca.dec, h)
})
}
}

View File

@@ -0,0 +1,13 @@
// Package jpeg contains JPEG/JFIF markers.
package jpeg
// standard JPEG markers.
const (
MarkerStartOfImage = 0xD8
MarkerDefineQuantizationTable = 0xDB
MarkerDefineHuffmanTable = 0xC4
MarkerDefineRestartInterval = 0xDD
MarkerStartOfFrame1 = 0xC0
MarkerStartOfScan = 0xDA
MarkerEndOfImage = 0xD9
)

View File

@@ -0,0 +1,83 @@
package jpeg
import (
"fmt"
)
// StartOfFrame1 is a SOF1 marker.
type StartOfFrame1 struct {
Type uint8
Width int
Height int
QuantizationTableCount uint8 // write only
}
// Unmarshal decodes the marker.
func (m *StartOfFrame1) Unmarshal(buf []byte) error {
if len(buf) != 15 {
return fmt.Errorf("unsupported SOF size of %d", len(buf))
}
precision := buf[0]
if precision != 8 {
return fmt.Errorf("precision %d is not supported", precision)
}
m.Height = int(buf[1])<<8 | int(buf[2])
m.Width = int(buf[3])<<8 | int(buf[4])
components := buf[5]
if components != 3 {
return fmt.Errorf("number of components = %d is not supported", components)
}
samp0 := buf[7]
switch samp0 {
case 0x21:
m.Type = 0
case 0x22:
m.Type = 1
default:
return fmt.Errorf("samp0 %x is not supported", samp0)
}
samp1 := buf[10]
if samp1 != 0x11 {
return fmt.Errorf("samp1 %x is not supported", samp1)
}
samp2 := buf[13]
if samp2 != 0x11 {
return fmt.Errorf("samp2 %x is not supported", samp2)
}
return nil
}
// Marshal encodes the marker.
func (m StartOfFrame1) Marshal(buf []byte) []byte {
buf = append(buf, []byte{0xFF, MarkerStartOfFrame1}...)
buf = append(buf, []byte{0, 17}...) // length
buf = append(buf, []byte{8}...) // precision
buf = append(buf, []byte{byte(m.Height >> 8), byte(m.Height)}...) // height
buf = append(buf, []byte{byte(m.Width >> 8), byte(m.Width)}...) // width
buf = append(buf, []byte{3}...) // components
if (m.Type & 0x3f) == 0 { // component 0
buf = append(buf, []byte{0x00, 0x21, 0}...)
} else {
buf = append(buf, []byte{0x00, 0x22, 0}...)
}
var secondQuantizationTable byte
if m.QuantizationTableCount == 2 {
secondQuantizationTable = 1
} else {
secondQuantizationTable = 0
}
buf = append(buf, []byte{1, 0x11, secondQuantizationTable}...) // component 1
buf = append(buf, []byte{2, 0x11, secondQuantizationTable}...) // component 2
return buf
}

View File

@@ -0,0 +1,49 @@
package jpeg
import (
"testing"
"github.com/stretchr/testify/require"
)
var casesStartOfFrame1 = []struct {
name string
enc []byte
dec StartOfFrame1
}{
{
"base",
[]byte{
0xff, 0xc0, 0x0, 0x11, 0x8, 0x2, 0x58, 0x3,
0x20, 0x3, 0x0, 0x22, 0x0, 0x1, 0x11, 0x1,
0x2, 0x11, 0x1,
},
StartOfFrame1{
Type: 1,
Width: 800,
Height: 600,
QuantizationTableCount: 2,
},
},
}
func TestStartOfFrame1Unmarshal(t *testing.T) {
for _, ca := range casesStartOfFrame1 {
t.Run(ca.name, func(t *testing.T) {
var h StartOfFrame1
err := h.Unmarshal(ca.enc[4:])
require.NoError(t, err)
h.QuantizationTableCount = 2
require.Equal(t, ca.dec, h)
})
}
}
func TestStartOfFrame1Marshal(t *testing.T) {
for _, ca := range casesStartOfFrame1 {
t.Run(ca.name, func(t *testing.T) {
byts := ca.dec.Marshal(nil)
require.Equal(t, ca.enc, byts)
})
}
}

View File

@@ -0,0 +1,10 @@
package jpeg
// StartOfImage is a SOI marker.
type StartOfImage struct{}
// Marshal encodes the marker.
func (StartOfImage) Marshal(buf []byte) []byte {
buf = append(buf, []byte{0xFF, MarkerStartOfImage}...)
return buf
}

View File

@@ -0,0 +1,12 @@
package jpeg
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestStartOfImageMarshal(t *testing.T) {
buf := StartOfImage{}.Marshal(nil)
require.Equal(t, []byte{0xff, 0xd8}, buf)
}

View File

@@ -0,0 +1,28 @@
package jpeg
import (
"fmt"
)
// StartOfScan is a SOS marker.
type StartOfScan struct{}
// Unmarshal decodes the marker.
func (StartOfScan) Unmarshal(buf []byte) error {
if len(buf) != 10 {
return fmt.Errorf("unsupported SOS size of %d", len(buf))
}
return nil
}
// Marshal encodes the marker.
func (StartOfScan) Marshal(buf []byte) []byte {
buf = append(buf, []byte{0xFF, MarkerStartOfScan}...)
buf = append(buf, []byte{0, 12}...) // length
buf = append(buf, []byte{3}...) // components
buf = append(buf, []byte{0, 0}...) // component 0
buf = append(buf, []byte{1, 0x11}...) // component 1
buf = append(buf, []byte{2, 0x11}...) // component 2
buf = append(buf, []byte{0, 63, 0}...)
return buf
}

View File

@@ -0,0 +1,42 @@
package jpeg
import (
"testing"
"github.com/stretchr/testify/require"
)
var casesStartOfScan = []struct {
name string
enc []byte
dec StartOfScan
}{
{
"base",
[]byte{
0xff, 0xda, 0x0, 0xc, 0x3, 0x0, 0x0, 0x1,
0x11, 0x2, 0x11, 0x0, 0x3f, 0x0,
},
StartOfScan{},
},
}
func TestStartOfScanUnmarshal(t *testing.T) {
for _, ca := range casesStartOfScan {
t.Run(ca.name, func(t *testing.T) {
var h StartOfScan
err := h.Unmarshal(ca.enc[4:])
require.NoError(t, err)
require.Equal(t, ca.dec, h)
})
}
}
func TestStartOfScanMarshal(t *testing.T) {
for _, ca := range casesStartOfScan {
t.Run(ca.name, func(t *testing.T) {
byts := ca.dec.Marshal(nil)
require.Equal(t, ca.enc, byts)
})
}
}

View File

@@ -0,0 +1,7 @@
// Package rtpmjpeg contains a RTP/MJPEG decoder and encoder.
package rtpmjpeg
const (
rtpClockRate = 90000
maxDimension = 2040
)

View File

@@ -46,7 +46,7 @@ func (d *Decoder) Init() {
// The PTS of subsequent AUs can be calculated by adding time.Second*mpeg4audio.SamplesPerAccessUnit/clockRate. // The PTS of subsequent AUs can be calculated by adding time.Second*mpeg4audio.SamplesPerAccessUnit/clockRate.
func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) { func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) {
if len(pkt.Payload) < 2 { if len(pkt.Payload) < 2 {
d.fragments = d.fragments[:0] d.fragments = d.fragments[:0] // discard pending fragmented packets
return nil, 0, fmt.Errorf("payload is too short") return nil, 0, fmt.Errorf("payload is too short")
} }
@@ -60,18 +60,22 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) {
// AU-headers // AU-headers
dataLens, err := d.readAUHeaders(payload, headersLen) dataLens, err := d.readAUHeaders(payload, headersLen)
if err != nil { if err != nil {
d.fragments = d.fragments[:0] // discard pending fragmented packets
return nil, 0, err return nil, 0, err
} }
pos := (headersLen / 8) pos := (headersLen / 8)
if (headersLen % 8) != 0 { if (headersLen % 8) != 0 {
pos++ pos++
} }
payload = payload[pos:] payload = payload[pos:]
var aus [][]byte
if len(d.fragments) == 0 { if len(d.fragments) == 0 {
if pkt.Header.Marker { if pkt.Header.Marker {
// AUs // AUs
aus := make([][]byte, len(dataLens)) aus = make([][]byte, len(dataLens))
for i, dataLen := range dataLens { for i, dataLen := range dataLens {
if len(payload) < int(dataLen) { if len(payload) < int(dataLen) {
return nil, 0, fmt.Errorf("payload is too short") return nil, 0, fmt.Errorf("payload is too short")
@@ -80,60 +84,53 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) {
aus[i] = payload[:dataLen] aus[i] = payload[:dataLen]
payload = payload[dataLen:] payload = payload[dataLen:]
} }
} else {
aus, err = d.removeADTS(aus) if len(dataLens) != 1 {
if err != nil { return nil, 0, fmt.Errorf("a fragmented packet can only contain one AU")
return nil, 0, err
} }
return aus, d.timeDecoder.Decode(pkt.Timestamp), nil if len(payload) < int(dataLens[0]) {
} return nil, 0, fmt.Errorf("payload is too short")
}
d.fragmentedSize = int(dataLens[0])
d.fragments = append(d.fragments, payload[:dataLens[0]])
return nil, 0, ErrMorePacketsNeeded
}
} else {
// we are decoding a fragmented AU
if len(dataLens) != 1 { if len(dataLens) != 1 {
d.fragments = d.fragments[:0] // discard pending fragmented packets
return nil, 0, fmt.Errorf("a fragmented packet can only contain one AU") return nil, 0, fmt.Errorf("a fragmented packet can only contain one AU")
} }
if len(payload) < int(dataLens[0]) { if len(payload) < int(dataLens[0]) {
d.fragments = d.fragments[:0] // discard pending fragmented packets
return nil, 0, fmt.Errorf("payload is too short") return nil, 0, fmt.Errorf("payload is too short")
} }
d.fragmentedSize = int(dataLens[0]) d.fragmentedSize += int(dataLens[0])
if d.fragmentedSize > mpeg4audio.MaxAccessUnitSize {
d.fragments = d.fragments[:0] // discard pending fragmented packets
return nil, 0, fmt.Errorf("AU size (%d) is too big (maximum is %d)", d.fragmentedSize, mpeg4audio.MaxAccessUnitSize)
}
d.fragments = append(d.fragments, payload[:dataLens[0]]) d.fragments = append(d.fragments, payload[:dataLens[0]])
return nil, 0, ErrMorePacketsNeeded
}
// we are decoding a fragmented AU if !pkt.Header.Marker {
return nil, 0, ErrMorePacketsNeeded
}
ret := make([]byte, d.fragmentedSize)
n := 0
for _, p := range d.fragments {
n += copy(ret[n:], p)
}
aus = [][]byte{ret}
if len(dataLens) != 1 {
d.fragments = d.fragments[:0] d.fragments = d.fragments[:0]
return nil, 0, fmt.Errorf("a fragmented packet can only contain one AU")
} }
if len(payload) < int(dataLens[0]) {
return nil, 0, fmt.Errorf("payload is too short")
}
d.fragmentedSize += int(dataLens[0])
if d.fragmentedSize > mpeg4audio.MaxAccessUnitSize {
d.fragments = d.fragments[:0]
return nil, 0, fmt.Errorf("AU size (%d) is too big (maximum is %d)", d.fragmentedSize, mpeg4audio.MaxAccessUnitSize)
}
d.fragments = append(d.fragments, payload[:dataLens[0]])
if !pkt.Header.Marker {
return nil, 0, ErrMorePacketsNeeded
}
ret := make([]byte, d.fragmentedSize)
n := 0
for _, p := range d.fragments {
n += copy(ret[n:], p)
}
aus := [][]byte{ret}
d.fragments = d.fragments[:0]
aus, err = d.removeADTS(aus) aus, err = d.removeADTS(aus)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
@@ -201,8 +198,8 @@ func (d *Decoder) readAUHeaders(buf []byte, headersLen int) ([]uint64, error) {
return dataLens, nil return dataLens, nil
} }
// some cameras wrap AUs into ADTS
func (d *Decoder) removeADTS(aus [][]byte) ([][]byte, error) { func (d *Decoder) removeADTS(aus [][]byte) ([][]byte, error) {
// some cameras wrap AUs into ADTS
if !d.firstAUParsed { if !d.firstAUParsed {
d.firstAUParsed = true d.firstAUParsed = true

View File

@@ -14,10 +14,18 @@ import (
// ErrMorePacketsNeeded is returned when more packets are needed. // ErrMorePacketsNeeded is returned when more packets are needed.
var ErrMorePacketsNeeded = errors.New("need more packets") var ErrMorePacketsNeeded = errors.New("need more packets")
// ErrNonStartingPacketAndNoPrevious is returned when we received a non-starting
// packet of a fragmented frame and we didn't received anything before.
// It's normal to receive this when we are decoding a stream that has been already
// running for some time.
var ErrNonStartingPacketAndNoPrevious = errors.New(
"received a non-starting fragment without any previous starting fragment")
// Decoder is a RTP/VP8 decoder. // Decoder is a RTP/VP8 decoder.
type Decoder struct { type Decoder struct {
timeDecoder *rtptimedec.Decoder timeDecoder *rtptimedec.Decoder
fragments [][]byte firstPacketReceived bool
fragments [][]byte
} }
// Init initializes the decoder. // Init initializes the decoder.
@@ -30,46 +38,56 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, time.Duration, error) {
var vpkt codecs.VP8Packet var vpkt codecs.VP8Packet
_, err := vpkt.Unmarshal(pkt.Payload) _, err := vpkt.Unmarshal(pkt.Payload)
if err != nil { if err != nil {
d.fragments = d.fragments[:0] // discard pending fragmented packets
return nil, 0, err return nil, 0, err
} }
if vpkt.PID != 0 { if vpkt.PID != 0 {
d.fragments = d.fragments[:0] // discard pending fragmented packets
return nil, 0, fmt.Errorf("packets containing single partitions are not supported") return nil, 0, fmt.Errorf("packets containing single partitions are not supported")
} }
if vpkt.S == 1 { var frame []byte
d.fragments = d.fragments[:0]
if pkt.Marker { if vpkt.S == 1 {
return vpkt.Payload, d.timeDecoder.Decode(pkt.Timestamp), nil d.fragments = d.fragments[:0] // discard pending fragmented packets
d.firstPacketReceived = true
if !pkt.Marker {
d.fragments = append(d.fragments, vpkt.Payload)
return nil, 0, ErrMorePacketsNeeded
}
frame = vpkt.Payload
} else {
if len(d.fragments) == 0 {
if !d.firstPacketReceived {
return nil, 0, ErrNonStartingPacketAndNoPrevious
}
return nil, 0, fmt.Errorf("received a non-starting fragment")
} }
d.fragments = append(d.fragments, vpkt.Payload) d.fragments = append(d.fragments, vpkt.Payload)
return nil, 0, ErrMorePacketsNeeded
if !pkt.Marker {
return nil, 0, ErrMorePacketsNeeded
}
n := 0
for _, frag := range d.fragments {
n += len(frag)
}
frame = make([]byte, n)
pos := 0
for _, frag := range d.fragments {
pos += copy(frame[pos:], frag)
}
d.fragments = d.fragments[:0]
} }
if len(d.fragments) == 0 {
return nil, 0, fmt.Errorf("received a non-starting fragment")
}
d.fragments = append(d.fragments, vpkt.Payload)
if !pkt.Marker {
return nil, 0, ErrMorePacketsNeeded
}
n := 0
for _, frag := range d.fragments {
n += len(frag)
}
frame := make([]byte, n)
pos := 0
for _, frag := range d.fragments {
pos += copy(frame[pos:], frag)
}
d.fragments = d.fragments[:0]
return frame, d.timeDecoder.Decode(pkt.Timestamp), nil return frame, d.timeDecoder.Decode(pkt.Timestamp), nil
} }

View File

@@ -14,10 +14,18 @@ import (
// ErrMorePacketsNeeded is returned when more packets are needed. // ErrMorePacketsNeeded is returned when more packets are needed.
var ErrMorePacketsNeeded = errors.New("need more packets") var ErrMorePacketsNeeded = errors.New("need more packets")
// ErrNonStartingPacketAndNoPrevious is returned when we received a non-starting
// packet of a fragmented frame and we didn't received anything before.
// It's normal to receive this when we are decoding a stream that has been already
// running for some time.
var ErrNonStartingPacketAndNoPrevious = errors.New(
"received a non-starting fragment without any previous starting fragment")
// Decoder is a RTP/VP9 decoder. // Decoder is a RTP/VP9 decoder.
type Decoder struct { type Decoder struct {
timeDecoder *rtptimedec.Decoder timeDecoder *rtptimedec.Decoder
fragments [][]byte firstPacketReceived bool
fragments [][]byte
} }
// Init initializes the decoder. // Init initializes the decoder.
@@ -30,42 +38,51 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, time.Duration, error) {
var vpkt codecs.VP9Packet var vpkt codecs.VP9Packet
_, err := vpkt.Unmarshal(pkt.Payload) _, err := vpkt.Unmarshal(pkt.Payload)
if err != nil { if err != nil {
d.fragments = d.fragments[:0] // discard pending fragmented packets
return nil, 0, err return nil, 0, err
} }
if vpkt.B { var frame []byte
d.fragments = d.fragments[:0]
if vpkt.E { if vpkt.B {
return vpkt.Payload, d.timeDecoder.Decode(pkt.Timestamp), nil d.fragments = d.fragments[:0] // discard pending fragmented packets
d.firstPacketReceived = true
if !vpkt.E {
d.fragments = append(d.fragments, vpkt.Payload)
return nil, 0, ErrMorePacketsNeeded
}
frame = vpkt.Payload
} else {
if len(d.fragments) == 0 {
if !d.firstPacketReceived {
return nil, 0, ErrNonStartingPacketAndNoPrevious
}
return nil, 0, fmt.Errorf("received a non-starting fragment")
} }
d.fragments = append(d.fragments, vpkt.Payload) d.fragments = append(d.fragments, vpkt.Payload)
return nil, 0, ErrMorePacketsNeeded
if !vpkt.E {
return nil, 0, ErrMorePacketsNeeded
}
n := 0
for _, frag := range d.fragments {
n += len(frag)
}
frame = make([]byte, n)
pos := 0
for _, frag := range d.fragments {
pos += copy(frame[pos:], frag)
}
d.fragments = d.fragments[:0]
} }
if len(d.fragments) == 0 {
return nil, 0, fmt.Errorf("received a non-starting fragment")
}
d.fragments = append(d.fragments, vpkt.Payload)
if !vpkt.E {
return nil, 0, ErrMorePacketsNeeded
}
n := 0
for _, frag := range d.fragments {
n += len(frag)
}
frame := make([]byte, n)
pos := 0
for _, frag := range d.fragments {
pos += copy(frame[pos:], frag)
}
d.fragments = d.fragments[:0]
return frame, d.timeDecoder.Decode(pkt.Timestamp), nil return frame, d.timeDecoder.Decode(pkt.Timestamp), nil
} }