mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 15:16:51 +08:00
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:
@@ -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)
|
||||||
|
72
examples/client-publish-format-mjpeg/main.go
Normal file
72
examples/client-publish-format-mjpeg/main.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
87
examples/client-read-format-mjpeg/main.go
Normal file
87
examples/client-read-format-mjpeg/main.go
Normal 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())
|
||||||
|
}
|
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
|
@@ -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{}
|
||||||
|
@@ -310,7 +310,7 @@ func TestNewFromMediaDescription(t *testing.T) {
|
|||||||
Formats: []string{"26"},
|
Formats: []string{"26"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&JPEG{},
|
&MJPEG{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"video mpeg2 video",
|
"video mpeg2 video",
|
||||||
|
@@ -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
|
|
||||||
}
|
|
@@ -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
53
pkg/format/mjpeg.go
Normal 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
301
pkg/format/mjpeg_test.go
Normal 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)
|
||||||
|
}
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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",
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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",
|
||||||
|
238
pkg/formatdecenc/rtpmjpeg/decoder.go
Normal file
238
pkg/formatdecenc/rtpmjpeg/decoder.go
Normal 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
|
||||||
|
}
|
529
pkg/formatdecenc/rtpmjpeg/decoder_test.go
Normal file
529
pkg/formatdecenc/rtpmjpeg/decoder_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
273
pkg/formatdecenc/rtpmjpeg/encoder.go
Normal file
273
pkg/formatdecenc/rtpmjpeg/encoder.go
Normal 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
|
||||||
|
}
|
33
pkg/formatdecenc/rtpmjpeg/encoder_test.go
Normal file
33
pkg/formatdecenc/rtpmjpeg/encoder_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
2
pkg/formatdecenc/rtpmjpeg/headers/headers.go
Normal file
2
pkg/formatdecenc/rtpmjpeg/headers/headers.go
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// Package headers contains RTP/MJPEG headers.
|
||||||
|
package headers
|
51
pkg/formatdecenc/rtpmjpeg/headers/jpeg.go
Normal file
51
pkg/formatdecenc/rtpmjpeg/headers/jpeg.go
Normal 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
|
||||||
|
}
|
47
pkg/formatdecenc/rtpmjpeg/headers/jpeg_test.go
Normal file
47
pkg/formatdecenc/rtpmjpeg/headers/jpeg_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
49
pkg/formatdecenc/rtpmjpeg/headers/quantization_table.go
Normal file
49
pkg/formatdecenc/rtpmjpeg/headers/quantization_table.go
Normal 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
|
||||||
|
}
|
54
pkg/formatdecenc/rtpmjpeg/headers/quantization_table_test.go
Normal file
54
pkg/formatdecenc/rtpmjpeg/headers/quantization_table_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
29
pkg/formatdecenc/rtpmjpeg/headers/restart_marker.go
Normal file
29
pkg/formatdecenc/rtpmjpeg/headers/restart_marker.go
Normal 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
|
||||||
|
}
|
44
pkg/formatdecenc/rtpmjpeg/headers/restart_marker_test.go
Normal file
44
pkg/formatdecenc/rtpmjpeg/headers/restart_marker_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
20
pkg/formatdecenc/rtpmjpeg/jpeg/define_huffman_table.go
Normal file
20
pkg/formatdecenc/rtpmjpeg/jpeg/define_huffman_table.go
Normal 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
|
||||||
|
}
|
35
pkg/formatdecenc/rtpmjpeg/jpeg/define_huffman_table_test.go
Normal file
35
pkg/formatdecenc/rtpmjpeg/jpeg/define_huffman_table_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
65
pkg/formatdecenc/rtpmjpeg/jpeg/define_quantization_table.go
Normal file
65
pkg/formatdecenc/rtpmjpeg/jpeg/define_quantization_table.go
Normal 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
|
||||||
|
}
|
@@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
20
pkg/formatdecenc/rtpmjpeg/jpeg/define_restart_interval.go
Normal file
20
pkg/formatdecenc/rtpmjpeg/jpeg/define_restart_interval.go
Normal 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
|
||||||
|
}
|
@@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
13
pkg/formatdecenc/rtpmjpeg/jpeg/jpeg.go
Normal file
13
pkg/formatdecenc/rtpmjpeg/jpeg/jpeg.go
Normal 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
|
||||||
|
)
|
83
pkg/formatdecenc/rtpmjpeg/jpeg/start_of_frame1.go
Normal file
83
pkg/formatdecenc/rtpmjpeg/jpeg/start_of_frame1.go
Normal 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
|
||||||
|
}
|
49
pkg/formatdecenc/rtpmjpeg/jpeg/start_of_frame1_test.go
Normal file
49
pkg/formatdecenc/rtpmjpeg/jpeg/start_of_frame1_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
10
pkg/formatdecenc/rtpmjpeg/jpeg/start_of_image.go
Normal file
10
pkg/formatdecenc/rtpmjpeg/jpeg/start_of_image.go
Normal 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
|
||||||
|
}
|
12
pkg/formatdecenc/rtpmjpeg/jpeg/start_of_image_test.go
Normal file
12
pkg/formatdecenc/rtpmjpeg/jpeg/start_of_image_test.go
Normal 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)
|
||||||
|
}
|
28
pkg/formatdecenc/rtpmjpeg/jpeg/start_of_scan.go
Normal file
28
pkg/formatdecenc/rtpmjpeg/jpeg/start_of_scan.go
Normal 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
|
||||||
|
}
|
42
pkg/formatdecenc/rtpmjpeg/jpeg/start_of_scan_test.go
Normal file
42
pkg/formatdecenc/rtpmjpeg/jpeg/start_of_scan_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
7
pkg/formatdecenc/rtpmjpeg/rtpmjpeg.go
Normal file
7
pkg/formatdecenc/rtpmjpeg/rtpmjpeg.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// Package rtpmjpeg contains a RTP/MJPEG decoder and encoder.
|
||||||
|
package rtpmjpeg
|
||||||
|
|
||||||
|
const (
|
||||||
|
rtpClockRate = 90000
|
||||||
|
maxDimension = 2040
|
||||||
|
)
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user