mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-10-06 08:46:57 +08:00
146 lines
3.1 KiB
Go
146 lines
3.1 KiB
Go
package h264
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
|
)
|
|
|
|
const (
|
|
NALUTypePFrame = 1 // Coded slice of a non-IDR picture
|
|
NALUTypeIFrame = 5 // Coded slice of an IDR picture
|
|
NALUTypeSEI = 6 // Supplemental enhancement information (SEI)
|
|
NALUTypeSPS = 7 // Sequence parameter set
|
|
NALUTypePPS = 8 // Picture parameter set
|
|
NALUTypeAUD = 9 // Access unit delimiter
|
|
)
|
|
|
|
func NALUType(b []byte) byte {
|
|
return b[4] & 0x1F
|
|
}
|
|
|
|
// IsKeyframe - check if any NALU in one AU is Keyframe
|
|
func IsKeyframe(b []byte) bool {
|
|
for {
|
|
switch NALUType(b) {
|
|
case NALUTypePFrame:
|
|
return false
|
|
case NALUTypeIFrame:
|
|
return true
|
|
}
|
|
|
|
size := int(binary.BigEndian.Uint32(b)) + 4
|
|
if size < len(b) {
|
|
b = b[size:]
|
|
continue
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
func Join(ps, iframe []byte) []byte {
|
|
b := make([]byte, len(ps)+len(iframe))
|
|
i := copy(b, ps)
|
|
copy(b[i:], iframe)
|
|
return b
|
|
}
|
|
|
|
// https://developers.google.com/cast/docs/media
|
|
const (
|
|
ProfileBaseline = 0x42
|
|
ProfileMain = 0x4D
|
|
ProfileHigh = 0x64
|
|
CapabilityBaseline = 0xE0
|
|
CapabilityMain = 0x40
|
|
)
|
|
|
|
// GetProfileLevelID - get profile from fmtp line
|
|
// Some devices won't play video with high level, so limit max profile and max level.
|
|
// And return some profile even if fmtp line is empty.
|
|
func GetProfileLevelID(fmtp string) string {
|
|
// avc1.640029 - H.264 high 4.1 (Chromecast 1st and 2nd Gen)
|
|
profile := byte(ProfileHigh)
|
|
capab := byte(0)
|
|
level := byte(41)
|
|
|
|
if fmtp != "" {
|
|
var conf []byte
|
|
// some cameras has wrong profile-level-id
|
|
// https://github.com/AlexxIT/go2rtc/issues/155
|
|
if s := core.Between(fmtp, "sprop-parameter-sets=", ","); s != "" {
|
|
if sps, _ := base64.StdEncoding.DecodeString(s); len(sps) >= 4 {
|
|
conf = sps[1:4]
|
|
}
|
|
} else if s = core.Between(fmtp, "profile-level-id=", ";"); s != "" {
|
|
conf, _ = hex.DecodeString(s)
|
|
}
|
|
|
|
if len(conf) == 3 {
|
|
// sanitize profile, capab and level to supported values
|
|
switch conf[0] {
|
|
case ProfileBaseline, ProfileMain:
|
|
profile = conf[0]
|
|
}
|
|
switch conf[1] {
|
|
case CapabilityBaseline, CapabilityMain:
|
|
capab = conf[1]
|
|
}
|
|
switch conf[2] {
|
|
case 30, 31, 40:
|
|
level = conf[2]
|
|
}
|
|
}
|
|
}
|
|
|
|
return fmt.Sprintf("%02X%02X%02X", profile, capab, level)
|
|
}
|
|
|
|
func GetParameterSet(fmtp string) (sps, pps []byte) {
|
|
if fmtp == "" {
|
|
return
|
|
}
|
|
|
|
s := core.Between(fmtp, "sprop-parameter-sets=", ";")
|
|
if s == "" {
|
|
return
|
|
}
|
|
|
|
i := strings.IndexByte(s, ',')
|
|
if i < 0 {
|
|
return
|
|
}
|
|
|
|
sps, _ = base64.StdEncoding.DecodeString(s[:i])
|
|
pps, _ = base64.StdEncoding.DecodeString(s[i+1:])
|
|
|
|
return
|
|
}
|
|
|
|
// GetFmtpLine from SPS+PPS+IFrame in AVC format
|
|
func GetFmtpLine(avc []byte) string {
|
|
s := "packetization-mode=1"
|
|
|
|
for {
|
|
size := 4 + int(binary.BigEndian.Uint32(avc))
|
|
|
|
switch NALUType(avc) {
|
|
case NALUTypeSPS:
|
|
s += ";profile-level-id=" + hex.EncodeToString(avc[5:8])
|
|
s += ";sprop-parameter-sets=" + base64.StdEncoding.EncodeToString(avc[4:size])
|
|
case NALUTypePPS:
|
|
s += "," + base64.StdEncoding.EncodeToString(avc[4:size])
|
|
}
|
|
|
|
if size < len(avc) {
|
|
avc = avc[size:]
|
|
} else {
|
|
return s
|
|
}
|
|
}
|
|
}
|