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 } } }