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