Move packet parsing code into its own package.
This commit is contained in:
@@ -1,294 +0,0 @@
|
||||
package rtpconn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/rtp/codecs"
|
||||
)
|
||||
|
||||
// isKeyframe determines if packet is the start of a keyframe.
|
||||
// It returns (true, true) if that is the case, (false, true) if that is
|
||||
// definitely not the case, and (false, false) if the information cannot
|
||||
// be determined.
|
||||
func isKeyframe(codec string, packet *rtp.Packet) (bool, bool) {
|
||||
if strings.EqualFold(codec, "video/vp8") {
|
||||
var vp8 codecs.VP8Packet
|
||||
_, err := vp8.Unmarshal(packet.Payload)
|
||||
if err != nil || len(vp8.Payload) < 1 {
|
||||
return false, false
|
||||
}
|
||||
|
||||
if vp8.S != 0 && vp8.PID == 0 && (vp8.Payload[0]&0x1) == 0 {
|
||||
return true, true
|
||||
}
|
||||
return false, true
|
||||
} else if strings.EqualFold(codec, "video/vp9") {
|
||||
var vp9 codecs.VP9Packet
|
||||
_, err := vp9.Unmarshal(packet.Payload)
|
||||
if err != nil || len(vp9.Payload) < 1 {
|
||||
return false, false
|
||||
}
|
||||
if !vp9.B {
|
||||
return false, true
|
||||
}
|
||||
|
||||
if (vp9.Payload[0] & 0xc0) != 0x80 {
|
||||
return false, false
|
||||
}
|
||||
|
||||
profile := (vp9.Payload[0] >> 4) & 0x3
|
||||
if profile != 3 {
|
||||
return (vp9.Payload[0] & 0xC) == 0, true
|
||||
}
|
||||
return (vp9.Payload[0] & 0x6) == 0, true
|
||||
} else if strings.EqualFold(codec, "video/av1x") {
|
||||
if len(packet.Payload) < 2 {
|
||||
return false, true
|
||||
}
|
||||
// Z=0, N=1
|
||||
if (packet.Payload[0] & 0x88) != 0x08 {
|
||||
return false, true
|
||||
}
|
||||
w := (packet.Payload[0] & 0x30) >> 4
|
||||
|
||||
getObu := func(data []byte, last bool) ([]byte, int, bool) {
|
||||
if last {
|
||||
return data, len(data), false
|
||||
}
|
||||
offset := 0
|
||||
length := 0
|
||||
for {
|
||||
if len(data) <= offset {
|
||||
return nil, offset, offset > 0
|
||||
}
|
||||
l := data[offset]
|
||||
length |= int(l&0x7f) << (offset * 7)
|
||||
offset++
|
||||
if (l & 0x80) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(data) < offset+length {
|
||||
return data[offset:], len(data), true
|
||||
}
|
||||
return data[offset : offset+length],
|
||||
offset + length, false
|
||||
}
|
||||
offset := 1
|
||||
i := 0
|
||||
for {
|
||||
obu, length, truncated :=
|
||||
getObu(packet.Payload[offset:], int(w) == i+1)
|
||||
if len(obu) < 1 {
|
||||
return false, false
|
||||
}
|
||||
tpe := (obu[0] & 0x38) >> 3
|
||||
switch i {
|
||||
case 0:
|
||||
// OBU_SEQUENCE_HEADER
|
||||
if tpe != 1 {
|
||||
return false, true
|
||||
}
|
||||
default:
|
||||
// OBU_FRAME_HEADER or OBU_FRAME
|
||||
if tpe == 3 || tpe == 6 {
|
||||
if len(obu) < 2 {
|
||||
return false, false
|
||||
}
|
||||
// show_existing_frame == 0
|
||||
if (obu[1] & 0x80) != 0 {
|
||||
return false, true
|
||||
}
|
||||
// frame_type == KEY_FRAME
|
||||
return (obu[1] & 0x60) == 0, true
|
||||
}
|
||||
}
|
||||
if truncated || i >= int(w) {
|
||||
// the first frame header is in a second
|
||||
// packet, give up.
|
||||
return false, false
|
||||
}
|
||||
offset += length
|
||||
i++
|
||||
}
|
||||
} else if strings.EqualFold(codec, "video/h264") {
|
||||
if len(packet.Payload) < 1 {
|
||||
return false, false
|
||||
}
|
||||
nalu := packet.Payload[0] & 0x1F
|
||||
if nalu == 0 {
|
||||
// reserved
|
||||
return false, false
|
||||
} else if nalu <= 23 {
|
||||
// simple NALU
|
||||
return nalu == 5, true
|
||||
} else if nalu == 24 || nalu == 25 || nalu == 26 || nalu == 27 {
|
||||
// STAP-A, STAP-B, MTAP16 or MTAP24
|
||||
i := 1
|
||||
if nalu == 25 || nalu == 26 || nalu == 27 {
|
||||
// skip DON
|
||||
i += 2
|
||||
}
|
||||
for i < len(packet.Payload) {
|
||||
if i+2 > len(packet.Payload) {
|
||||
return false, false
|
||||
}
|
||||
length := uint16(packet.Payload[i])<<8 |
|
||||
uint16(packet.Payload[i+1])
|
||||
i += 2
|
||||
if i+int(length) > len(packet.Payload) {
|
||||
return false, false
|
||||
}
|
||||
offset := 0
|
||||
if nalu == 26 {
|
||||
offset = 3
|
||||
} else if nalu == 27 {
|
||||
offset = 4
|
||||
}
|
||||
if offset >= int(length) {
|
||||
return false, false
|
||||
}
|
||||
n := packet.Payload[i+offset] & 0x1F
|
||||
if n == 7 {
|
||||
return true, true
|
||||
} else if n >= 24 {
|
||||
// is this legal?
|
||||
return false, false
|
||||
}
|
||||
i += int(length)
|
||||
}
|
||||
if i == len(packet.Payload) {
|
||||
return false, true
|
||||
}
|
||||
return false, false
|
||||
} else if nalu == 28 || nalu == 29 {
|
||||
// FU-A or FU-B
|
||||
if len(packet.Payload) < 2 {
|
||||
return false, false
|
||||
}
|
||||
if (packet.Payload[1] & 0x80) == 0 {
|
||||
// not a starting fragment
|
||||
return false, true
|
||||
}
|
||||
return (packet.Payload[1]&0x1F == 7), true
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
|
||||
var errTruncated = errors.New("truncated packet")
|
||||
var errUnsupportedCodec = errors.New("unsupported codec")
|
||||
|
||||
type packetFlags struct {
|
||||
seqno uint16
|
||||
start bool
|
||||
pid uint16 // only if it needs rewriting
|
||||
tid uint8
|
||||
sid uint8
|
||||
tidupsync bool
|
||||
sidsync bool
|
||||
sidnonreference bool
|
||||
discardable bool
|
||||
}
|
||||
|
||||
func getPacketFlags(codec string, buf []byte) (packetFlags, error) {
|
||||
if len(buf) < 12 {
|
||||
return packetFlags{}, errTruncated
|
||||
}
|
||||
|
||||
var flags packetFlags
|
||||
|
||||
flags.seqno = (uint16(buf[2]) << 8) | uint16(buf[3])
|
||||
|
||||
if strings.EqualFold(codec, "video/vp8") {
|
||||
var packet rtp.Packet
|
||||
err := packet.Unmarshal(buf)
|
||||
if err != nil {
|
||||
return flags, err
|
||||
}
|
||||
var vp8 codecs.VP8Packet
|
||||
_, err = vp8.Unmarshal(packet.Payload)
|
||||
if err != nil {
|
||||
return flags, err
|
||||
}
|
||||
|
||||
flags.start = vp8.S == 1 && vp8.PID == 0
|
||||
flags.pid = vp8.PictureID
|
||||
flags.tid = vp8.TID
|
||||
flags.tidupsync = vp8.Y == 1
|
||||
flags.discardable = vp8.N == 1
|
||||
return flags, nil
|
||||
} else if strings.EqualFold(codec, "video/vp9") {
|
||||
var packet rtp.Packet
|
||||
err := packet.Unmarshal(buf)
|
||||
if err != nil {
|
||||
return flags, err
|
||||
}
|
||||
var vp9 codecs.VP9Packet
|
||||
_, err = vp9.Unmarshal(packet.Payload)
|
||||
if err != nil {
|
||||
return flags, err
|
||||
}
|
||||
flags.start = vp9.B
|
||||
flags.tid = vp9.TID
|
||||
flags.sid = vp9.SID
|
||||
flags.tidupsync = vp9.U
|
||||
flags.sidsync = vp9.P
|
||||
// not yet in pion/rtp
|
||||
flags.sidnonreference = (packet.Payload[0] & 0x01) != 0
|
||||
return flags, nil
|
||||
}
|
||||
return flags, nil
|
||||
}
|
||||
|
||||
func rewritePacket(codec string, data []byte, seqno uint16, delta uint16) error {
|
||||
if len(data) < 12 {
|
||||
return errTruncated
|
||||
}
|
||||
|
||||
data[2] = uint8(seqno >> 8)
|
||||
data[3] = uint8(seqno)
|
||||
if delta == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
offset := 12
|
||||
offset += int(data[0]&0x0F) * 4
|
||||
if len(data) < offset+4 {
|
||||
return errTruncated
|
||||
}
|
||||
|
||||
if (data[0] & 0x10) != 0 {
|
||||
length := uint16(data[offset+2])<<8 | uint16(data[offset+3])
|
||||
offset += 4 + int(length)*4
|
||||
if len(data) < offset+4 {
|
||||
return errTruncated
|
||||
}
|
||||
}
|
||||
|
||||
if strings.EqualFold(codec, "video/vp8") {
|
||||
x := (data[offset] & 0x80) != 0
|
||||
if !x {
|
||||
return nil
|
||||
}
|
||||
i := (data[offset+1] & 0x80) != 0
|
||||
if !i {
|
||||
return nil
|
||||
}
|
||||
m := (data[offset+2] & 0x80) != 0
|
||||
if m {
|
||||
pid := (uint16(data[offset+2]&0x7F) << 8) |
|
||||
uint16(data[offset+3])
|
||||
pid = (pid + delta) & 0x7FFF
|
||||
data[offset+2] = 0x80 | byte((pid>>8)&0x7F)
|
||||
data[offset+3] = byte(pid & 0xFF)
|
||||
} else {
|
||||
data[offset+2] = (data[offset+2] + uint8(delta)) & 0x7F
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return errUnsupportedCodec
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package rtpconn
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var vp8 = []byte{
|
||||
0x80, 0, 0, 42,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
|
||||
0x90, 0x80, 0x80, 57,
|
||||
|
||||
0, 0, 0, 0,
|
||||
}
|
||||
|
||||
func TestPacketFlags(t *testing.T) {
|
||||
buf := append([]byte{}, vp8...)
|
||||
flags, err := getPacketFlags("video/vp8", buf)
|
||||
if flags.seqno != 42 || !flags.start || flags.pid != 57 ||
|
||||
flags.sid != 0 || flags.tid != 0 ||
|
||||
flags.tidupsync || flags.discardable || err != nil {
|
||||
t.Errorf("Got %v, %v, %v, %v, %v, %v (%v)",
|
||||
flags.seqno, flags.start, flags.pid, flags.sid,
|
||||
flags.tidupsync, flags.discardable, err,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRewrite(t *testing.T) {
|
||||
for i := uint16(0); i < 0x7fff; i++ {
|
||||
buf := append([]byte{}, vp8...)
|
||||
err := rewritePacket("video/vp8", buf, i, i)
|
||||
if err != nil {
|
||||
t.Errorf("rewrite: %v", err)
|
||||
continue
|
||||
}
|
||||
flags, err := getPacketFlags("video/vp8", buf)
|
||||
if err != nil || flags.seqno != i ||
|
||||
flags.pid != (57 + i) & 0x7FFF {
|
||||
t.Errorf("Expected %v %v, got %v %v (%v)",
|
||||
i, (57 + i) & 0x7FFF,
|
||||
flags.seqno, flags.pid, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
-20
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/pion/sdp/v3"
|
||||
"github.com/pion/webrtc/v3"
|
||||
|
||||
"github.com/jech/galene/codecs"
|
||||
"github.com/jech/galene/conn"
|
||||
"github.com/jech/galene/estimator"
|
||||
"github.com/jech/galene/group"
|
||||
@@ -213,59 +214,59 @@ var packetBufPool = sync.Pool{
|
||||
func (down *rtpDownTrack) Write(buf []byte) (int, error) {
|
||||
codec := down.remote.Codec().MimeType
|
||||
|
||||
flags, err := getPacketFlags(codec, buf)
|
||||
flags, err := codecs.PacketFlags(codec, buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
layer := down.getLayerInfo()
|
||||
|
||||
if flags.tid > layer.maxTid || flags.sid > layer.maxSid {
|
||||
if flags.tid > layer.maxTid {
|
||||
if flags.Tid > layer.maxTid || flags.Sid > layer.maxSid {
|
||||
if flags.Tid > layer.maxTid {
|
||||
if layer.tid == layer.maxTid {
|
||||
layer.wantedTid = flags.tid
|
||||
layer.tid = flags.tid
|
||||
layer.wantedTid = flags.Tid
|
||||
layer.tid = flags.Tid
|
||||
}
|
||||
layer.maxTid = flags.tid
|
||||
layer.maxTid = flags.Tid
|
||||
}
|
||||
if flags.sid > layer.maxSid {
|
||||
if flags.Sid > layer.maxSid {
|
||||
if layer.sid == layer.maxSid {
|
||||
layer.wantedSid = flags.sid
|
||||
layer.sid = flags.sid
|
||||
layer.wantedSid = flags.Sid
|
||||
layer.sid = flags.Sid
|
||||
}
|
||||
layer.maxSid = flags.sid
|
||||
layer.maxSid = flags.Sid
|
||||
}
|
||||
down.setLayerInfo(layer)
|
||||
down.adjustLayer()
|
||||
}
|
||||
if flags.start && (layer.tid != layer.wantedTid) {
|
||||
if layer.wantedTid < layer.tid || flags.tidupsync {
|
||||
if flags.Start && (layer.tid != layer.wantedTid) {
|
||||
if layer.wantedTid < layer.tid || flags.TidUpSync {
|
||||
layer.tid = layer.wantedTid
|
||||
down.setLayerInfo(layer)
|
||||
}
|
||||
}
|
||||
|
||||
if flags.start && (layer.sid != layer.wantedSid) {
|
||||
if flags.sidsync {
|
||||
if flags.Start && (layer.sid != layer.wantedSid) {
|
||||
if flags.SidSync {
|
||||
layer.sid = layer.wantedTid
|
||||
down.setLayerInfo(layer)
|
||||
}
|
||||
}
|
||||
|
||||
if flags.tid > layer.tid || flags.sid > layer.sid ||
|
||||
(flags.sid < layer.sid && flags.sidnonreference) {
|
||||
ok := down.packetmap.Drop(flags.seqno, flags.pid)
|
||||
if flags.Tid > layer.tid || flags.Sid > layer.sid ||
|
||||
(flags.Sid < layer.sid && flags.SidNonReference) {
|
||||
ok := down.packetmap.Drop(flags.Seqno, flags.Pid)
|
||||
if ok {
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
ok, newseqno, piddelta := down.packetmap.Map(flags.seqno, flags.pid)
|
||||
ok, newseqno, piddelta := down.packetmap.Map(flags.Seqno, flags.Pid)
|
||||
if !ok {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if newseqno == flags.seqno && piddelta == 0 {
|
||||
if newseqno == flags.Seqno && piddelta == 0 {
|
||||
return down.write(buf)
|
||||
}
|
||||
|
||||
@@ -274,7 +275,7 @@ func (down *rtpDownTrack) Write(buf []byte) (int, error) {
|
||||
buf2 := ibuf2.([]byte)
|
||||
|
||||
n := copy(buf2, buf)
|
||||
err = rewritePacket(codec, buf2[:n], newseqno, piddelta)
|
||||
err = codecs.RewritePacket(codec, buf2[:n], newseqno, piddelta)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/webrtc/v3"
|
||||
|
||||
"github.com/jech/galene/codecs"
|
||||
"github.com/jech/galene/packetcache"
|
||||
"github.com/jech/galene/rtptime"
|
||||
)
|
||||
@@ -74,7 +75,7 @@ func readLoop(track *rtpUpTrack) {
|
||||
|
||||
track.jitter.Accumulate(packet.Timestamp)
|
||||
|
||||
kf, kfKnown := isKeyframe(codec.MimeType, &packet)
|
||||
kf, kfKnown := codecs.Keyframe(codec.MimeType, &packet)
|
||||
if kf || !kfKnown {
|
||||
kfNeeded = false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user