mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-09-26 20:31:11 +08:00
221 lines
4.4 KiB
Go
221 lines
4.4 KiB
Go
package mjpeg
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"image"
|
|
"image/jpeg"
|
|
|
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
|
"github.com/pion/rtp"
|
|
)
|
|
|
|
func RTPDepay(handlerFunc core.HandlerFunc) core.HandlerFunc {
|
|
buf := make([]byte, 0, 512*1024) // 512K
|
|
|
|
return func(packet *rtp.Packet) {
|
|
//log.Printf("[RTP] codec: %s, size: %6d, ts: %10d, pt: %2d, ssrc: %d, seq: %d, mark: %v", track.Codec.Name, len(packet.Payload), packet.Timestamp, packet.PayloadType, packet.SSRC, packet.SequenceNumber, packet.Marker)
|
|
|
|
// https://www.rfc-editor.org/rfc/rfc2435#section-3.1
|
|
b := packet.Payload
|
|
|
|
// 3.1. JPEG header
|
|
t := b[4]
|
|
|
|
// 3.1.7. Restart Marker header
|
|
if 64 <= t && t <= 127 {
|
|
b = b[12:] // skip it
|
|
} else {
|
|
b = b[8:]
|
|
}
|
|
|
|
if len(buf) == 0 {
|
|
var lqt, cqt []byte
|
|
|
|
// 3.1.8. Quantization Table header
|
|
q := packet.Payload[5]
|
|
if q >= 128 {
|
|
lqt = b[4:68]
|
|
cqt = b[68:132]
|
|
b = b[132:]
|
|
} else {
|
|
lqt, cqt = MakeTables(q)
|
|
}
|
|
|
|
// https://www.rfc-editor.org/rfc/rfc2435#section-3.1.5
|
|
// The maximum width is 2040 pixels.
|
|
w := uint16(packet.Payload[6]) << 3
|
|
h := uint16(packet.Payload[7]) << 3
|
|
|
|
// fix sizes more than 2040
|
|
switch {
|
|
// 512x1920 512x1440
|
|
case w == cutSize(2560) && (h == 1920 || h == 1440):
|
|
w = 2560
|
|
// 1792x112
|
|
case w == cutSize(3840) && h == cutSize(2160):
|
|
w = 3840
|
|
h = 2160
|
|
// 256x1296
|
|
case w == cutSize(2304) && h == 1296:
|
|
w = 2304
|
|
}
|
|
|
|
//fmt.Printf("t: %d, q: %d, w: %d, h: %d\n", t, q, w, h)
|
|
buf = MakeHeaders(buf, t, w, h, lqt, cqt)
|
|
}
|
|
|
|
// 3.1.9. JPEG Payload
|
|
buf = append(buf, b...)
|
|
|
|
if !packet.Marker {
|
|
return
|
|
}
|
|
|
|
if end := buf[len(buf)-2:]; end[0] != 0xFF && end[1] != 0xD9 {
|
|
buf = append(buf, 0xFF, 0xD9)
|
|
}
|
|
|
|
clone := *packet
|
|
clone.Payload = buf
|
|
|
|
buf = buf[:0] // clear buffer
|
|
|
|
handlerFunc(&clone)
|
|
}
|
|
}
|
|
|
|
func cutSize(size uint16) uint16 {
|
|
return ((size >> 3) & 0xFF) << 3
|
|
}
|
|
|
|
func RTPPay(handlerFunc core.HandlerFunc) core.HandlerFunc {
|
|
const packetSize = 1436
|
|
|
|
sequencer := rtp.NewRandomSequencer()
|
|
|
|
return func(packet *rtp.Packet) {
|
|
// reincode image to more common form
|
|
p, err := Transcode(packet.Payload)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
h1 := make([]byte, 8)
|
|
h1[4] = 1 // Type
|
|
h1[5] = 255 // Q
|
|
|
|
// MBZ=0, Precision=0, Length=128
|
|
h2 := make([]byte, 4, 132)
|
|
h2[3] = 128
|
|
|
|
var jpgData []byte
|
|
for jpgData == nil {
|
|
// 2 bytes h1
|
|
if p[0] != 0xFF {
|
|
return
|
|
}
|
|
|
|
size := binary.BigEndian.Uint16(p[2:]) + 2
|
|
|
|
// 2 bytes payload size (include 2 bytes)
|
|
switch p[1] {
|
|
case 0xD8: // 0. Start Of Image (size=0)
|
|
p = p[2:]
|
|
continue
|
|
case 0xDB: // 1. Define Quantization Table (size=130)
|
|
for i := uint16(4 + 1); i < size; i += 1 + 64 {
|
|
h2 = append(h2, p[i:i+64]...)
|
|
}
|
|
case 0xC0: // 2. Start Of Frame (size=15)
|
|
if p[4] != 8 {
|
|
return
|
|
}
|
|
h := binary.BigEndian.Uint16(p[5:])
|
|
w := binary.BigEndian.Uint16(p[7:])
|
|
h1[6] = uint8(w >> 3)
|
|
h1[7] = uint8(h >> 3)
|
|
case 0xC4: // 3. Define Huffman Table (size=416)
|
|
case 0xDA: // 4. Start Of Scan (size=10)
|
|
jpgData = p[size:]
|
|
}
|
|
|
|
p = p[size:]
|
|
}
|
|
|
|
offset := 0
|
|
p = make([]byte, 0)
|
|
|
|
for jpgData != nil {
|
|
p = p[:0]
|
|
|
|
if offset > 0 {
|
|
h1[1] = byte(offset >> 16)
|
|
h1[2] = byte(offset >> 8)
|
|
h1[3] = byte(offset)
|
|
p = append(p, h1...)
|
|
} else {
|
|
p = append(p, h1...)
|
|
p = append(p, h2...)
|
|
}
|
|
|
|
dataLen := packetSize - len(p)
|
|
if dataLen < len(jpgData) {
|
|
p = append(p, jpgData[:dataLen]...)
|
|
jpgData = jpgData[dataLen:]
|
|
offset += dataLen
|
|
} else {
|
|
p = append(p, jpgData...)
|
|
jpgData = nil
|
|
}
|
|
|
|
clone := rtp.Packet{
|
|
Header: rtp.Header{
|
|
Version: 2,
|
|
Marker: jpgData == nil,
|
|
SequenceNumber: sequencer.NextSequenceNumber(),
|
|
Timestamp: packet.Timestamp,
|
|
},
|
|
Payload: p,
|
|
}
|
|
handlerFunc(&clone)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Transcode(b []byte) ([]byte, error) {
|
|
img, err := jpeg.Decode(bytes.NewReader(b))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
wh := img.Bounds().Size()
|
|
w := wh.X
|
|
h := wh.Y
|
|
|
|
if w > 2040 {
|
|
w = 2040
|
|
} else if w&3 > 0 {
|
|
w &= 3
|
|
}
|
|
if h > 2040 {
|
|
h = 2040
|
|
} else if h&3 > 0 {
|
|
h &= 3
|
|
}
|
|
|
|
if w != wh.X || h != wh.Y {
|
|
x0 := (wh.X - w) / 2
|
|
y0 := (wh.Y - h) / 2
|
|
rect := image.Rect(x0, y0, x0+w, y0+h)
|
|
img = img.(*image.YCbCr).SubImage(rect)
|
|
}
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
if err = jpeg.Encode(buf, img, nil); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|