Add HEVC support
This commit is contained in:
parent
fde0595744
commit
4ee14576ae
21
av/av.go
21
av/av.go
@ -116,6 +116,9 @@ type CodecType uint32
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
H264 = MakeVideoCodecType(avCodecTypeMagic + 1)
|
H264 = MakeVideoCodecType(avCodecTypeMagic + 1)
|
||||||
|
HEVC = MakeVideoCodecType(avCodecTypeMagic + 2)
|
||||||
|
VP9 = MakeVideoCodecType(avCodecTypeMagic + 3)
|
||||||
|
AV1 = MakeVideoCodecType(avCodecTypeMagic + 4)
|
||||||
AAC = MakeAudioCodecType(avCodecTypeMagic + 1)
|
AAC = MakeAudioCodecType(avCodecTypeMagic + 1)
|
||||||
PCM_MULAW = MakeAudioCodecType(avCodecTypeMagic + 2)
|
PCM_MULAW = MakeAudioCodecType(avCodecTypeMagic + 2)
|
||||||
PCM_ALAW = MakeAudioCodecType(avCodecTypeMagic + 3)
|
PCM_ALAW = MakeAudioCodecType(avCodecTypeMagic + 3)
|
||||||
@ -126,10 +129,16 @@ var (
|
|||||||
const codecTypeAudioBit = 0x1
|
const codecTypeAudioBit = 0x1
|
||||||
const codecTypeOtherBits = 1
|
const codecTypeOtherBits = 1
|
||||||
|
|
||||||
func (self CodecType) String() string {
|
func (c CodecType) String() string {
|
||||||
switch self {
|
switch c {
|
||||||
case H264:
|
case H264:
|
||||||
return "H264"
|
return "H264"
|
||||||
|
case HEVC:
|
||||||
|
return "HEVC"
|
||||||
|
case VP9:
|
||||||
|
return "VP9"
|
||||||
|
case AV1:
|
||||||
|
return "AV1"
|
||||||
case AAC:
|
case AAC:
|
||||||
return "AAC"
|
return "AAC"
|
||||||
case PCM_MULAW:
|
case PCM_MULAW:
|
||||||
@ -144,12 +153,12 @@ func (self CodecType) String() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self CodecType) IsAudio() bool {
|
func (c CodecType) IsAudio() bool {
|
||||||
return self&codecTypeAudioBit != 0
|
return c&codecTypeAudioBit != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self CodecType) IsVideo() bool {
|
func (c CodecType) IsVideo() bool {
|
||||||
return self&codecTypeAudioBit == 0
|
return c&codecTypeAudioBit == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a new audio codec type.
|
// Make a new audio codec type.
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
package pktque
|
package pktque
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/datarhei/joy4/av"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/datarhei/joy4/av"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Filter interface {
|
type Filter interface {
|
||||||
|
@ -3,10 +3,11 @@ package aacparser
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/datarhei/joy4/av"
|
|
||||||
"github.com/datarhei/joy4/utils/bits"
|
|
||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/datarhei/joy4/av"
|
||||||
|
"github.com/datarhei/joy4/utils/bits"
|
||||||
)
|
)
|
||||||
|
|
||||||
// copied from libavcodec/mpeg4audio.h
|
// copied from libavcodec/mpeg4audio.h
|
||||||
|
@ -3,6 +3,7 @@ package h264parser
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/datarhei/joy4/av"
|
"github.com/datarhei/joy4/av"
|
||||||
"github.com/datarhei/joy4/utils/bits"
|
"github.com/datarhei/joy4/utils/bits"
|
||||||
"github.com/datarhei/joy4/utils/bits/pio"
|
"github.com/datarhei/joy4/utils/bits/pio"
|
||||||
|
@ -6,16 +6,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestParser(t *testing.T) {
|
func TestParser(t *testing.T) {
|
||||||
var ok bool
|
var typ int
|
||||||
var nalus [][]byte
|
var nalus [][]byte
|
||||||
|
|
||||||
annexbFrame, _ := hex.DecodeString("00000001223322330000000122332233223300000133000001000001")
|
annexbFrame, _ := hex.DecodeString("00000001223322330000000122332233223300000133000001000001")
|
||||||
nalus, ok = SplitNALUs(annexbFrame)
|
nalus, typ = SplitNALUs(annexbFrame)
|
||||||
t.Log(ok, len(nalus))
|
t.Log(typ, len(nalus))
|
||||||
|
|
||||||
avccFrame, _ := hex.DecodeString(
|
avccFrame, _ := hex.DecodeString(
|
||||||
"00000008aabbccaabbccaabb00000001aa",
|
"00000008aabbccaabbccaabb00000001aa",
|
||||||
)
|
)
|
||||||
nalus, ok = SplitNALUs(avccFrame)
|
nalus, typ = SplitNALUs(avccFrame)
|
||||||
t.Log(ok, len(nalus))
|
t.Log(typ, len(nalus))
|
||||||
}
|
}
|
||||||
|
695
codec/hevcparser/parser.go
Normal file
695
codec/hevcparser/parser.go
Normal file
@ -0,0 +1,695 @@
|
|||||||
|
package hevcparser
|
||||||
|
|
||||||
|
// based on https://github.com/deepch/vdk/blob/v0.0.21/codec/h265parser/parser.go
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/datarhei/joy4/av"
|
||||||
|
"github.com/datarhei/joy4/utils/bits"
|
||||||
|
"github.com/datarhei/joy4/utils/bits/pio"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SPSInfo struct {
|
||||||
|
ProfileIdc uint
|
||||||
|
LevelIdc uint
|
||||||
|
CropLeft uint
|
||||||
|
CropRight uint
|
||||||
|
CropTop uint
|
||||||
|
CropBottom uint
|
||||||
|
Width uint
|
||||||
|
Height uint
|
||||||
|
PicWidthInLumaSamples uint
|
||||||
|
PicHeightInLumaSamples uint
|
||||||
|
generalProfileSpace uint
|
||||||
|
generalTierFlag uint
|
||||||
|
generalProfileIDC uint
|
||||||
|
generalProfileCompatibilityFlags uint32
|
||||||
|
generalConstraintIndicatorFlags uint64
|
||||||
|
generalLevelIDC uint
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
NAL_UNIT_CODED_SLICE_TRAIL_N = 0
|
||||||
|
NAL_UNIT_CODED_SLICE_TRAIL_R = 1
|
||||||
|
NAL_UNIT_CODED_SLICE_TSA_N = 2
|
||||||
|
NAL_UNIT_CODED_SLICE_TSA_R = 3
|
||||||
|
NAL_UNIT_CODED_SLICE_STSA_N = 4
|
||||||
|
NAL_UNIT_CODED_SLICE_STSA_R = 5
|
||||||
|
NAL_UNIT_CODED_SLICE_RADL_N = 6
|
||||||
|
NAL_UNIT_CODED_SLICE_RADL_R = 7
|
||||||
|
NAL_UNIT_CODED_SLICE_RASL_N = 8
|
||||||
|
NAL_UNIT_CODED_SLICE_RASL_R = 9
|
||||||
|
NAL_UNIT_RESERVED_VCL_N10 = 10
|
||||||
|
NAL_UNIT_RESERVED_VCL_R11 = 11
|
||||||
|
NAL_UNIT_RESERVED_VCL_N12 = 12
|
||||||
|
NAL_UNIT_RESERVED_VCL_R13 = 13
|
||||||
|
NAL_UNIT_RESERVED_VCL_N14 = 14
|
||||||
|
NAL_UNIT_RESERVED_VCL_R15 = 15
|
||||||
|
NAL_UNIT_CODED_SLICE_BLA_W_LP = 16
|
||||||
|
NAL_UNIT_CODED_SLICE_BLA_W_RADL = 17
|
||||||
|
NAL_UNIT_CODED_SLICE_BLA_N_LP = 18
|
||||||
|
NAL_UNIT_CODED_SLICE_IDR_W_RADL = 19
|
||||||
|
NAL_UNIT_CODED_SLICE_IDR_N_LP = 20
|
||||||
|
NAL_UNIT_CODED_SLICE_CRA = 21
|
||||||
|
NAL_UNIT_RESERVED_IRAP_VCL22 = 22
|
||||||
|
NAL_UNIT_RESERVED_IRAP_VCL23 = 23
|
||||||
|
NAL_UNIT_RESERVED_VCL24 = 24
|
||||||
|
NAL_UNIT_RESERVED_VCL25 = 25
|
||||||
|
NAL_UNIT_RESERVED_VCL26 = 26
|
||||||
|
NAL_UNIT_RESERVED_VCL27 = 27
|
||||||
|
NAL_UNIT_RESERVED_VCL28 = 28
|
||||||
|
NAL_UNIT_RESERVED_VCL29 = 29
|
||||||
|
NAL_UNIT_RESERVED_VCL30 = 30
|
||||||
|
NAL_UNIT_RESERVED_VCL31 = 31
|
||||||
|
NAL_UNIT_VPS = 32
|
||||||
|
NAL_UNIT_SPS = 33
|
||||||
|
NAL_UNIT_PPS = 34
|
||||||
|
NAL_UNIT_ACCESS_UNIT_DELIMITER = 35
|
||||||
|
NAL_UNIT_EOS = 36
|
||||||
|
NAL_UNIT_EOB = 37
|
||||||
|
NAL_UNIT_FILLER_DATA = 38
|
||||||
|
NAL_UNIT_PREFIX_SEI = 39
|
||||||
|
NAL_UNIT_SUFFIX_SEI = 40
|
||||||
|
NAL_UNIT_RESERVED_NVCL41 = 41
|
||||||
|
NAL_UNIT_RESERVED_NVCL42 = 42
|
||||||
|
NAL_UNIT_RESERVED_NVCL43 = 43
|
||||||
|
NAL_UNIT_RESERVED_NVCL44 = 44
|
||||||
|
NAL_UNIT_RESERVED_NVCL45 = 45
|
||||||
|
NAL_UNIT_RESERVED_NVCL46 = 46
|
||||||
|
NAL_UNIT_RESERVED_NVCL47 = 47
|
||||||
|
NAL_UNIT_UNSPECIFIED_48 = 48
|
||||||
|
NAL_UNIT_UNSPECIFIED_49 = 49
|
||||||
|
NAL_UNIT_UNSPECIFIED_50 = 50
|
||||||
|
NAL_UNIT_UNSPECIFIED_51 = 51
|
||||||
|
NAL_UNIT_UNSPECIFIED_52 = 52
|
||||||
|
NAL_UNIT_UNSPECIFIED_53 = 53
|
||||||
|
NAL_UNIT_UNSPECIFIED_54 = 54
|
||||||
|
NAL_UNIT_UNSPECIFIED_55 = 55
|
||||||
|
NAL_UNIT_UNSPECIFIED_56 = 56
|
||||||
|
NAL_UNIT_UNSPECIFIED_57 = 57
|
||||||
|
NAL_UNIT_UNSPECIFIED_58 = 58
|
||||||
|
NAL_UNIT_UNSPECIFIED_59 = 59
|
||||||
|
NAL_UNIT_UNSPECIFIED_60 = 60
|
||||||
|
NAL_UNIT_UNSPECIFIED_61 = 61
|
||||||
|
NAL_UNIT_UNSPECIFIED_62 = 62
|
||||||
|
NAL_UNIT_UNSPECIFIED_63 = 63
|
||||||
|
NAL_UNIT_INVALID = 64
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MAX_VPS_COUNT = 16
|
||||||
|
MAX_SUB_LAYERS = 7
|
||||||
|
MAX_SPS_COUNT = 32
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrorHEVCIncorectUnitSize = errors.New("incorrect unit size")
|
||||||
|
ErrorHECVIncorectUnitType = errors.New("incorrect unit type")
|
||||||
|
)
|
||||||
|
|
||||||
|
var StartCodeBytes = []byte{0, 0, 1}
|
||||||
|
var AUDBytes = []byte{0, 0, 0, 1, 0x9, 0xf0, 0, 0, 0, 1} // AUD
|
||||||
|
|
||||||
|
const (
|
||||||
|
NALU_RAW = iota
|
||||||
|
NALU_AVCC
|
||||||
|
NALU_ANNEXB
|
||||||
|
)
|
||||||
|
|
||||||
|
func SplitNALUs(b []byte) (nalus [][]byte, typ int) {
|
||||||
|
if len(b) < 4 {
|
||||||
|
return [][]byte{b}, NALU_RAW
|
||||||
|
}
|
||||||
|
val3 := pio.U24BE(b)
|
||||||
|
val4 := pio.U32BE(b)
|
||||||
|
if val4 <= uint32(len(b)) {
|
||||||
|
_val4 := val4
|
||||||
|
_b := b[4:]
|
||||||
|
nalus := [][]byte{}
|
||||||
|
for {
|
||||||
|
nalus = append(nalus, _b[:_val4])
|
||||||
|
_b = _b[_val4:]
|
||||||
|
if len(_b) < 4 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_val4 = pio.U32BE(_b)
|
||||||
|
_b = _b[4:]
|
||||||
|
if _val4 > uint32(len(_b)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(_b) == 0 {
|
||||||
|
return nalus, NALU_AVCC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if val3 == 1 || val4 == 1 {
|
||||||
|
_val3 := val3
|
||||||
|
_val4 := val4
|
||||||
|
start := 0
|
||||||
|
pos := 0
|
||||||
|
for {
|
||||||
|
if start != pos {
|
||||||
|
nalus = append(nalus, b[start:pos])
|
||||||
|
}
|
||||||
|
if _val3 == 1 {
|
||||||
|
pos += 3
|
||||||
|
} else if _val4 == 1 {
|
||||||
|
pos += 4
|
||||||
|
}
|
||||||
|
start = pos
|
||||||
|
if start == len(b) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_val3 = 0
|
||||||
|
_val4 = 0
|
||||||
|
for pos < len(b) {
|
||||||
|
if pos+2 < len(b) && b[pos] == 0 {
|
||||||
|
_val3 = pio.U24BE(b[pos:])
|
||||||
|
if _val3 == 0 {
|
||||||
|
if pos+3 < len(b) {
|
||||||
|
_val4 = uint32(b[pos+3])
|
||||||
|
if _val4 == 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if _val3 == 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pos++
|
||||||
|
} else {
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
typ = NALU_ANNEXB
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return [][]byte{b}, NALU_RAW
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseSPS(sps []byte) (ctx SPSInfo, err error) {
|
||||||
|
if len(sps) < 2 {
|
||||||
|
err = ErrorHEVCIncorectUnitSize
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rbsp := nal2rbsp(sps[2:])
|
||||||
|
br := &bits.GolombBitReader{R: bytes.NewReader(rbsp)}
|
||||||
|
|
||||||
|
// sps_video_parameter_set_id
|
||||||
|
if _, err = br.ReadBits(4); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// sps_max_sub_layers_minus1
|
||||||
|
spsMaxSubLayersMinus1, err := br.ReadBits(3)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// sps_temporal_id_nesting_flag
|
||||||
|
if _, err = br.ReadBit(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// profile_tier_level( 1, sps_max_sub_layers_minus1 )
|
||||||
|
if err = parsePTL(br, &ctx, spsMaxSubLayersMinus1); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// sps_seq_parameter_set_id
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// chroma_format_idc
|
||||||
|
var chroma_format_idc uint
|
||||||
|
if chroma_format_idc, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if chroma_format_idc == 3 {
|
||||||
|
// separate_colour_plane_flag
|
||||||
|
if _, err = br.ReadBit(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Table 6-1, Section 6.2
|
||||||
|
var subWidthC uint
|
||||||
|
var subHeightC uint
|
||||||
|
|
||||||
|
switch chroma_format_idc {
|
||||||
|
case 0:
|
||||||
|
subWidthC, subHeightC = 1, 1
|
||||||
|
case 1:
|
||||||
|
subWidthC, subHeightC = 2, 2
|
||||||
|
case 2:
|
||||||
|
subWidthC, subHeightC = 2, 1
|
||||||
|
case 3:
|
||||||
|
subWidthC, subHeightC = 1, 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// pic_width_in_luma_samples
|
||||||
|
if ctx.PicWidthInLumaSamples, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// pic_height_in_luma_samples
|
||||||
|
if ctx.PicHeightInLumaSamples, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// conformance_window_flag
|
||||||
|
conformanceWindowFlag, err := br.ReadBit()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var conf_win_left_offset uint
|
||||||
|
var conf_win_right_offset uint
|
||||||
|
var conf_win_top_offset uint
|
||||||
|
var conf_win_bottom_offset uint
|
||||||
|
|
||||||
|
if conformanceWindowFlag != 0 {
|
||||||
|
// conf_win_left_offset
|
||||||
|
conf_win_left_offset, err = br.ReadExponentialGolombCode()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.CropLeft = subWidthC * conf_win_left_offset
|
||||||
|
|
||||||
|
// conf_win_right_offset
|
||||||
|
conf_win_right_offset, err = br.ReadExponentialGolombCode()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.CropRight = subWidthC * conf_win_right_offset
|
||||||
|
|
||||||
|
// conf_win_top_offset
|
||||||
|
conf_win_top_offset, err = br.ReadExponentialGolombCode()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.CropTop = subHeightC * conf_win_top_offset
|
||||||
|
|
||||||
|
// conf_win_bottom_offset
|
||||||
|
conf_win_bottom_offset, err = br.ReadExponentialGolombCode()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.CropBottom = subHeightC * conf_win_bottom_offset
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Width = ctx.PicWidthInLumaSamples - ctx.CropLeft - ctx.CropRight
|
||||||
|
ctx.Height = ctx.PicHeightInLumaSamples - ctx.CropTop - ctx.CropBottom
|
||||||
|
|
||||||
|
// bit_depth_luma_minus8
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// bit_depth_chroma_minus8
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// log2_max_pic_order_cnt_lsb_minus4
|
||||||
|
_, err = br.ReadExponentialGolombCode()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// sps_sub_layer_ordering_info_present_flag
|
||||||
|
spsSubLayerOrderingInfoPresentFlag, err := br.ReadBit()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var i uint
|
||||||
|
if spsSubLayerOrderingInfoPresentFlag != 0 {
|
||||||
|
i = 0
|
||||||
|
} else {
|
||||||
|
i = spsMaxSubLayersMinus1
|
||||||
|
}
|
||||||
|
for ; i <= spsMaxSubLayersMinus1; i++ {
|
||||||
|
// sps_max_dec_pic_buffering_minus1[ i ]
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// sps_max_num_reorder_pics[ i ]
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// sps_max_latency_increase_plus1[ i ]
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// log2_min_luma_coding_block_size_minus3
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// log2_diff_max_min_luma_coding_block_size
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// log2_min_luma_transform_block_size_minus2
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// log2_diff_max_min_luma_transform_block_size
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// max_transform_hierarchy_depth_inter
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// max_transform_hierarchy_depth_intra
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePTL(br *bits.GolombBitReader, ctx *SPSInfo, maxSubLayersMinus1 uint) error {
|
||||||
|
var err error
|
||||||
|
var ptl SPSInfo
|
||||||
|
if ptl.generalProfileSpace, err = br.ReadBits(2); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ptl.generalTierFlag, err = br.ReadBit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ptl.generalProfileIDC, err = br.ReadBits(5); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ptl.generalProfileCompatibilityFlags, err = br.ReadBits32(32); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ptl.generalConstraintIndicatorFlags, err = br.ReadBits64(48); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ptl.generalLevelIDC, err = br.ReadBits(8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
updatePTL(ctx, &ptl)
|
||||||
|
if maxSubLayersMinus1 == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
subLayerProfilePresentFlag := make([]uint, maxSubLayersMinus1)
|
||||||
|
subLayerLevelPresentFlag := make([]uint, maxSubLayersMinus1)
|
||||||
|
for i := uint(0); i < maxSubLayersMinus1; i++ {
|
||||||
|
if subLayerProfilePresentFlag[i], err = br.ReadBit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if subLayerLevelPresentFlag[i], err = br.ReadBit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if maxSubLayersMinus1 > 0 {
|
||||||
|
for i := maxSubLayersMinus1; i < 8; i++ {
|
||||||
|
if _, err = br.ReadBits(2); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := uint(0); i < maxSubLayersMinus1; i++ {
|
||||||
|
if subLayerProfilePresentFlag[i] != 0 {
|
||||||
|
if _, err = br.ReadBits32(32); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = br.ReadBits32(32); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = br.ReadBits32(24); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if subLayerLevelPresentFlag[i] != 0 {
|
||||||
|
if _, err = br.ReadBits(8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updatePTL(ctx, ptl *SPSInfo) {
|
||||||
|
ctx.generalProfileSpace = ptl.generalProfileSpace
|
||||||
|
|
||||||
|
if ptl.generalTierFlag > ctx.generalTierFlag {
|
||||||
|
ctx.generalLevelIDC = ptl.generalLevelIDC
|
||||||
|
|
||||||
|
ctx.generalTierFlag = ptl.generalTierFlag
|
||||||
|
} else {
|
||||||
|
if ptl.generalLevelIDC > ctx.generalLevelIDC {
|
||||||
|
ctx.generalLevelIDC = ptl.generalLevelIDC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ptl.generalProfileIDC > ctx.generalProfileIDC {
|
||||||
|
ctx.generalProfileIDC = ptl.generalProfileIDC
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.generalProfileCompatibilityFlags &= ptl.generalProfileCompatibilityFlags
|
||||||
|
|
||||||
|
ctx.generalConstraintIndicatorFlags &= ptl.generalConstraintIndicatorFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
func nal2rbsp(nal []byte) []byte {
|
||||||
|
return bytes.Replace(nal, []byte{0x0, 0x0, 0x3}, []byte{0x0, 0x0}, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CodecData struct {
|
||||||
|
Record []byte
|
||||||
|
RecordInfo HEVCDecoderConfRecord
|
||||||
|
SPSInfo SPSInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec CodecData) Type() av.CodecType {
|
||||||
|
return av.HEVC
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec CodecData) HEVCDecoderConfRecordBytes() []byte {
|
||||||
|
return codec.Record
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec CodecData) SPS() []byte {
|
||||||
|
return codec.RecordInfo.SPS[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec CodecData) PPS() []byte {
|
||||||
|
return codec.RecordInfo.PPS[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec CodecData) VPS() []byte {
|
||||||
|
return codec.RecordInfo.VPS[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec CodecData) Width() int {
|
||||||
|
return int(codec.SPSInfo.Width)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec CodecData) Height() int {
|
||||||
|
return int(codec.SPSInfo.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCodecDataFromHEVCDecoderConfRecord(record []byte) (self CodecData, err error) {
|
||||||
|
self.Record = record
|
||||||
|
if _, err = (&self.RecordInfo).Unmarshal(record); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(self.RecordInfo.SPS) == 0 {
|
||||||
|
err = fmt.Errorf("hevcparser: no SPS found in HEVCDecoderConfRecord")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(self.RecordInfo.PPS) == 0 {
|
||||||
|
err = fmt.Errorf("hevcparser: no PPS found in HEVCDecoderConfRecord")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(self.RecordInfo.VPS) == 0 {
|
||||||
|
err = fmt.Errorf("hevcparser: no VPS found in HEVCDecoderConfRecord")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.SPSInfo, err = ParseSPS(self.RecordInfo.SPS[0]); err != nil {
|
||||||
|
err = fmt.Errorf("hevcparser: parse SPS failed(%s)", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCodecDataFromVPSAndSPSAndPPS(vps, sps, pps []byte) (self CodecData, err error) {
|
||||||
|
recordinfo := HEVCDecoderConfRecord{}
|
||||||
|
recordinfo.HEVCProfileIndication = sps[3]
|
||||||
|
recordinfo.ProfileCompatibility = sps[4]
|
||||||
|
recordinfo.HEVCLevelIndication = sps[5]
|
||||||
|
recordinfo.SPS = [][]byte{sps}
|
||||||
|
recordinfo.PPS = [][]byte{pps}
|
||||||
|
recordinfo.VPS = [][]byte{vps}
|
||||||
|
recordinfo.LengthSizeMinusOne = 3
|
||||||
|
if self.SPSInfo, err = ParseSPS(sps); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf := make([]byte, recordinfo.Len())
|
||||||
|
recordinfo.Marshal(buf, self.SPSInfo)
|
||||||
|
self.RecordInfo = recordinfo
|
||||||
|
self.Record = buf
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type HEVCDecoderConfRecord struct {
|
||||||
|
HEVCProfileIndication uint8
|
||||||
|
ProfileCompatibility uint8
|
||||||
|
HEVCLevelIndication uint8
|
||||||
|
LengthSizeMinusOne uint8
|
||||||
|
VPS [][]byte
|
||||||
|
SPS [][]byte
|
||||||
|
PPS [][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrDecconfInvalid = fmt.Errorf("hevcparser: HEVCDecoderConfRecord invalid")
|
||||||
|
|
||||||
|
func (record *HEVCDecoderConfRecord) Unmarshal(b []byte) (n int, err error) {
|
||||||
|
if len(b) < 30 {
|
||||||
|
err = ErrDecconfInvalid
|
||||||
|
return
|
||||||
|
}
|
||||||
|
record.HEVCProfileIndication = b[1]
|
||||||
|
record.ProfileCompatibility = b[2]
|
||||||
|
record.HEVCLevelIndication = b[3]
|
||||||
|
record.LengthSizeMinusOne = b[4] & 0x03
|
||||||
|
|
||||||
|
vpscount := int(b[25] & 0x1f)
|
||||||
|
n += 26
|
||||||
|
for i := 0; i < vpscount; i++ {
|
||||||
|
if len(b) < n+2 {
|
||||||
|
err = ErrDecconfInvalid
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vpslen := int(pio.U16BE(b[n:]))
|
||||||
|
n += 2
|
||||||
|
|
||||||
|
if len(b) < n+vpslen {
|
||||||
|
err = ErrDecconfInvalid
|
||||||
|
return
|
||||||
|
}
|
||||||
|
record.VPS = append(record.VPS, b[n:n+vpslen])
|
||||||
|
n += vpslen
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) < n+1 {
|
||||||
|
err = ErrDecconfInvalid
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n++
|
||||||
|
n++
|
||||||
|
|
||||||
|
spscount := int(b[n])
|
||||||
|
n++
|
||||||
|
|
||||||
|
for i := 0; i < spscount; i++ {
|
||||||
|
if len(b) < n+2 {
|
||||||
|
err = ErrDecconfInvalid
|
||||||
|
return
|
||||||
|
}
|
||||||
|
spslen := int(pio.U16BE(b[n:]))
|
||||||
|
n += 2
|
||||||
|
|
||||||
|
if len(b) < n+spslen {
|
||||||
|
err = ErrDecconfInvalid
|
||||||
|
return
|
||||||
|
}
|
||||||
|
record.SPS = append(record.SPS, b[n:n+spslen])
|
||||||
|
n += spslen
|
||||||
|
}
|
||||||
|
|
||||||
|
n++
|
||||||
|
n++
|
||||||
|
|
||||||
|
ppscount := int(b[n])
|
||||||
|
n++
|
||||||
|
|
||||||
|
for i := 0; i < ppscount; i++ {
|
||||||
|
if len(b) < n+2 {
|
||||||
|
err = ErrDecconfInvalid
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ppslen := int(pio.U16BE(b[n:]))
|
||||||
|
n += 2
|
||||||
|
|
||||||
|
if len(b) < n+ppslen {
|
||||||
|
err = ErrDecconfInvalid
|
||||||
|
return
|
||||||
|
}
|
||||||
|
record.PPS = append(record.PPS, b[n:n+ppslen])
|
||||||
|
n += ppslen
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record HEVCDecoderConfRecord) Len() (n int) {
|
||||||
|
n = 23
|
||||||
|
for _, sps := range record.SPS {
|
||||||
|
n += 5 + len(sps)
|
||||||
|
}
|
||||||
|
for _, pps := range record.PPS {
|
||||||
|
n += 5 + len(pps)
|
||||||
|
}
|
||||||
|
for _, vps := range record.VPS {
|
||||||
|
n += 5 + len(vps)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record HEVCDecoderConfRecord) Marshal(b []byte, si SPSInfo) (n int) {
|
||||||
|
b[0] = 1
|
||||||
|
b[1] = record.HEVCProfileIndication
|
||||||
|
b[2] = record.ProfileCompatibility
|
||||||
|
b[3] = record.HEVCLevelIndication
|
||||||
|
b[21] = 3
|
||||||
|
b[22] = 3
|
||||||
|
n += 23
|
||||||
|
b[n] = (record.VPS[0][0] >> 1) & 0x3f
|
||||||
|
n++
|
||||||
|
b[n] = byte(len(record.VPS) >> 8)
|
||||||
|
n++
|
||||||
|
b[n] = byte(len(record.VPS))
|
||||||
|
n++
|
||||||
|
for _, vps := range record.VPS {
|
||||||
|
pio.PutU16BE(b[n:], uint16(len(vps)))
|
||||||
|
n += 2
|
||||||
|
copy(b[n:], vps)
|
||||||
|
n += len(vps)
|
||||||
|
}
|
||||||
|
b[n] = (record.SPS[0][0] >> 1) & 0x3f
|
||||||
|
n++
|
||||||
|
b[n] = byte(len(record.SPS) >> 8)
|
||||||
|
n++
|
||||||
|
b[n] = byte(len(record.SPS))
|
||||||
|
n++
|
||||||
|
for _, sps := range record.SPS {
|
||||||
|
pio.PutU16BE(b[n:], uint16(len(sps)))
|
||||||
|
n += 2
|
||||||
|
copy(b[n:], sps)
|
||||||
|
n += len(sps)
|
||||||
|
}
|
||||||
|
b[n] = (record.PPS[0][0] >> 1) & 0x3f
|
||||||
|
n++
|
||||||
|
b[n] = byte(len(record.PPS) >> 8)
|
||||||
|
n++
|
||||||
|
b[n] = byte(len(record.PPS))
|
||||||
|
n++
|
||||||
|
for _, pps := range record.PPS {
|
||||||
|
pio.PutU16BE(b[n:], uint16(len(pps)))
|
||||||
|
n += 2
|
||||||
|
copy(b[n:], pps)
|
||||||
|
n += len(pps)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
58
examples/rtmp_client/main.go
Normal file
58
examples/rtmp_client/main.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/datarhei/joy4/av"
|
||||||
|
"github.com/datarhei/joy4/av/avutil"
|
||||||
|
"github.com/datarhei/joy4/codec/h264parser"
|
||||||
|
"github.com/datarhei/joy4/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
format.RegisterAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
log.Fatalf("%s [url]", os.Args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err := avutil.Open(os.Args[1])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error connecting: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
defer src.Close()
|
||||||
|
|
||||||
|
var streams []av.CodecData
|
||||||
|
|
||||||
|
if streams, err = src.Streams(); err != nil {
|
||||||
|
log.Fatalf("error streams: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
idx := int8(-1)
|
||||||
|
for i, s := range streams {
|
||||||
|
if s.Type().IsVideo() {
|
||||||
|
fmt.Printf("video: %s\n", s.Type().String())
|
||||||
|
v := s.(h264parser.CodecData)
|
||||||
|
os.Stdout.Write(v.AVCDecoderConfRecordBytes())
|
||||||
|
idx = int8(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
p, err := src.ReadPacket()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error reading: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Idx != idx {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Stdout.Write(p.Data)
|
||||||
|
}
|
||||||
|
}
|
@ -116,7 +116,7 @@ func (s *server) handlePlay(conn *rtmp.Conn) {
|
|||||||
s.lock.RUnlock()
|
s.lock.RUnlock()
|
||||||
|
|
||||||
if ch != nil {
|
if ch != nil {
|
||||||
conn.SetMetaData(ch.metadata)
|
//conn.SetMetaData(ch.metadata)
|
||||||
|
|
||||||
s.log("PLAY", "START", conn.URL.Path, "", client)
|
s.log("PLAY", "START", conn.URL.Path, "", client)
|
||||||
cursor := ch.que.Oldest()
|
cursor := ch.que.Oldest()
|
||||||
@ -139,8 +139,6 @@ func (s *server) handlePlay(conn *rtmp.Conn) {
|
|||||||
} else {
|
} else {
|
||||||
s.log("PLAY", "NOTFOUND", conn.URL.Path, "", client)
|
s.log("PLAY", "NOTFOUND", conn.URL.Path, "", client)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) handlePublish(conn *rtmp.Conn) {
|
func (s *server) handlePublish(conn *rtmp.Conn) {
|
||||||
@ -169,14 +167,14 @@ func (s *server) handlePublish(conn *rtmp.Conn) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata := conn.GetMetaData()
|
//metadata := conn.GetMetaData()
|
||||||
|
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
|
|
||||||
ch := s.channels[conn.URL.Path]
|
ch := s.channels[conn.URL.Path]
|
||||||
if ch == nil {
|
if ch == nil {
|
||||||
ch = &channel{}
|
ch = &channel{}
|
||||||
ch.metadata = metadata
|
//ch.metadata = metadata
|
||||||
ch.que = pubsub.NewQueue()
|
ch.que = pubsub.NewQueue()
|
||||||
ch.que.WriteHeader(streams)
|
ch.que.WriteHeader(streams)
|
||||||
for _, stream := range streams {
|
for _, stream := range streams {
|
||||||
@ -221,8 +219,6 @@ func (s *server) handlePublish(conn *rtmp.Conn) {
|
|||||||
ch.que.Close()
|
ch.que.Close()
|
||||||
|
|
||||||
s.log("PUBLISH", "STOP", conn.URL.Path, "", client)
|
s.log("PUBLISH", "STOP", conn.URL.Path, "", client)
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -2,16 +2,19 @@ package flv
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/datarhei/joy4/av"
|
"github.com/datarhei/joy4/av"
|
||||||
"github.com/datarhei/joy4/av/avutil"
|
"github.com/datarhei/joy4/av/avutil"
|
||||||
"github.com/datarhei/joy4/codec"
|
"github.com/datarhei/joy4/codec"
|
||||||
"github.com/datarhei/joy4/codec/aacparser"
|
"github.com/datarhei/joy4/codec/aacparser"
|
||||||
"github.com/datarhei/joy4/codec/fake"
|
"github.com/datarhei/joy4/codec/fake"
|
||||||
"github.com/datarhei/joy4/codec/h264parser"
|
"github.com/datarhei/joy4/codec/h264parser"
|
||||||
|
"github.com/datarhei/joy4/codec/hevcparser"
|
||||||
"github.com/datarhei/joy4/format/flv/flvio"
|
"github.com/datarhei/joy4/format/flv/flvio"
|
||||||
"github.com/datarhei/joy4/utils/bits/pio"
|
"github.com/datarhei/joy4/utils/bits/pio"
|
||||||
"io"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var MaxProbePacketCount = 20
|
var MaxProbePacketCount = 20
|
||||||
@ -27,6 +30,8 @@ func NewMetadataByStreams(streams []av.CodecData) (metadata flvio.AMFMap, err er
|
|||||||
switch typ {
|
switch typ {
|
||||||
case av.H264:
|
case av.H264:
|
||||||
metadata["videocodecid"] = flvio.VIDEO_H264
|
metadata["videocodecid"] = flvio.VIDEO_H264
|
||||||
|
case av.HEVC:
|
||||||
|
metadata["videocodecid"] = flvio.FourCCToFloat(flvio.FOURCC_HEVC)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("flv: metadata: unsupported video codecType=%v", stream.Type())
|
err = fmt.Errorf("flv: metadata: unsupported video codecType=%v", stream.Type())
|
||||||
@ -83,10 +88,30 @@ func (self *Prober) PushTag(tag flvio.Tag, timestamp int32) (err error) {
|
|||||||
|
|
||||||
switch tag.Type {
|
switch tag.Type {
|
||||||
case flvio.TAG_VIDEO:
|
case flvio.TAG_VIDEO:
|
||||||
|
if tag.IsExHeader {
|
||||||
|
if tag.FourCC == flvio.FOURCC_HEVC {
|
||||||
|
if tag.PacketType == flvio.PKTTYPE_SEQUENCE_START {
|
||||||
|
if !self.GotVideo {
|
||||||
|
var stream hevcparser.CodecData
|
||||||
|
fmt.Printf("got HEVC sequence start:\n%s\n", hex.Dump(tag.Data))
|
||||||
|
if stream, err = hevcparser.NewCodecDataFromHEVCDecoderConfRecord(tag.Data); err != nil {
|
||||||
|
err = fmt.Errorf("flv: hevc seqhdr invalid: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.VideoStreamIdx = len(self.Streams)
|
||||||
|
self.Streams = append(self.Streams, stream)
|
||||||
|
self.GotVideo = true
|
||||||
|
}
|
||||||
|
} else if tag.PacketType == flvio.PKTTYPE_CODED_FRAMES || tag.PacketType == flvio.PKTTYPE_CODED_FRAMESX {
|
||||||
|
self.CacheTag(tag, timestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
switch tag.AVCPacketType {
|
switch tag.AVCPacketType {
|
||||||
case flvio.AVC_SEQHDR:
|
case flvio.AVC_SEQHDR:
|
||||||
if !self.GotVideo {
|
if !self.GotVideo {
|
||||||
var stream h264parser.CodecData
|
var stream h264parser.CodecData
|
||||||
|
fmt.Printf("got H264 sequence start:\n%s\n", hex.Dump(tag.Data))
|
||||||
if stream, err = h264parser.NewCodecDataFromAVCDecoderConfRecord(tag.Data); err != nil {
|
if stream, err = h264parser.NewCodecDataFromAVCDecoderConfRecord(tag.Data); err != nil {
|
||||||
err = fmt.Errorf("flv: h264 seqhdr invalid: %s", err.Error())
|
err = fmt.Errorf("flv: h264 seqhdr invalid: %s", err.Error())
|
||||||
return
|
return
|
||||||
@ -99,6 +124,7 @@ func (self *Prober) PushTag(tag flvio.Tag, timestamp int32) (err error) {
|
|||||||
case flvio.AVC_NALU:
|
case flvio.AVC_NALU:
|
||||||
self.CacheTag(tag, timestamp)
|
self.CacheTag(tag, timestamp)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case flvio.TAG_AUDIO:
|
case flvio.TAG_AUDIO:
|
||||||
switch tag.SoundFormat {
|
switch tag.SoundFormat {
|
||||||
@ -166,8 +192,8 @@ func (self *Prober) TagToPacket(tag flvio.Tag, timestamp int32) (pkt av.Packet,
|
|||||||
switch tag.Type {
|
switch tag.Type {
|
||||||
case flvio.TAG_VIDEO:
|
case flvio.TAG_VIDEO:
|
||||||
pkt.Idx = int8(self.VideoStreamIdx)
|
pkt.Idx = int8(self.VideoStreamIdx)
|
||||||
switch tag.AVCPacketType {
|
switch tag.PacketType {
|
||||||
case flvio.AVC_NALU:
|
case flvio.PKTTYPE_CODED_FRAMES, flvio.PKTTYPE_CODED_FRAMESX:
|
||||||
ok = true
|
ok = true
|
||||||
pkt.Data = tag.Data
|
pkt.Data = tag.Data
|
||||||
pkt.CompositionTime = flvio.TsToTime(tag.CompositionTime)
|
pkt.CompositionTime = flvio.TsToTime(tag.CompositionTime)
|
||||||
@ -219,6 +245,22 @@ func CodecDataToTag(stream av.CodecData) (_tag flvio.Tag, ok bool, err error) {
|
|||||||
Data: h264.AVCDecoderConfRecordBytes(),
|
Data: h264.AVCDecoderConfRecordBytes(),
|
||||||
FrameType: flvio.FRAME_KEY,
|
FrameType: flvio.FRAME_KEY,
|
||||||
}
|
}
|
||||||
|
fmt.Printf("set H264 sequence start:\n%v\n", hex.Dump(h264.AVCDecoderConfRecordBytes()))
|
||||||
|
ok = true
|
||||||
|
_tag = tag
|
||||||
|
|
||||||
|
case av.HEVC:
|
||||||
|
fmt.Printf("CodecDataToTag for HEVC\n")
|
||||||
|
hevc := stream.(hevcparser.CodecData)
|
||||||
|
tag := flvio.Tag{
|
||||||
|
Type: flvio.TAG_VIDEO,
|
||||||
|
IsExHeader: true,
|
||||||
|
PacketType: flvio.PKTTYPE_SEQUENCE_START,
|
||||||
|
FourCC: flvio.FOURCC_HEVC,
|
||||||
|
Data: hevc.HEVCDecoderConfRecordBytes(),
|
||||||
|
FrameType: flvio.FRAME_KEY,
|
||||||
|
}
|
||||||
|
fmt.Printf("set HEVC sequence start:\n%v\n", hex.Dump(hevc.HEVCDecoderConfRecordBytes()))
|
||||||
ok = true
|
ok = true
|
||||||
_tag = tag
|
_tag = tag
|
||||||
|
|
||||||
@ -272,6 +314,27 @@ func PacketToTag(pkt av.Packet, stream av.CodecData) (tag flvio.Tag, timestamp i
|
|||||||
tag.FrameType = flvio.FRAME_INTER
|
tag.FrameType = flvio.FRAME_INTER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case av.HEVC:
|
||||||
|
//fmt.Printf("PacketToTag for HEVC\n")
|
||||||
|
tag = flvio.Tag{
|
||||||
|
Type: flvio.TAG_VIDEO,
|
||||||
|
IsExHeader: true,
|
||||||
|
PacketType: flvio.PKTTYPE_CODED_FRAMES,
|
||||||
|
CompositionTime: flvio.TimeToTs(pkt.CompositionTime),
|
||||||
|
FourCC: flvio.FOURCC_HEVC,
|
||||||
|
Data: pkt.Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
if pkt.CompositionTime == 0 {
|
||||||
|
tag.PacketType = flvio.PKTTYPE_CODED_FRAMESX
|
||||||
|
}
|
||||||
|
|
||||||
|
if pkt.IsKeyFrame {
|
||||||
|
tag.FrameType = flvio.FRAME_KEY
|
||||||
|
} else {
|
||||||
|
tag.FrameType = flvio.FRAME_INTER
|
||||||
|
}
|
||||||
|
|
||||||
case av.AAC:
|
case av.AAC:
|
||||||
tag = flvio.Tag{
|
tag = flvio.Tag{
|
||||||
Type: flvio.TAG_AUDIO,
|
Type: flvio.TAG_AUDIO,
|
||||||
|
@ -2,10 +2,11 @@ package flvio
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/datarhei/joy4/av"
|
|
||||||
"github.com/datarhei/joy4/utils/bits/pio"
|
|
||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/datarhei/joy4/av"
|
||||||
|
"github.com/datarhei/joy4/utils/bits/pio"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TsToTime(ts int32) time.Duration {
|
func TsToTime(ts int32) time.Duration {
|
||||||
@ -60,6 +61,27 @@ const (
|
|||||||
VIDEO_H264 = 7
|
VIDEO_H264 = 7
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PKTTYPE_SEQUENCE_START = 0
|
||||||
|
PKTTYPE_CODED_FRAMES = 1
|
||||||
|
PKTTYPE_SEQUENCE_END = 2
|
||||||
|
PKTTYPE_CODED_FRAMESX = 3
|
||||||
|
PKTTYPE_METADATA = 4
|
||||||
|
PKTTYPE_MPEG2TS_SEQUENCE_START = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
FOURCC_AV1 = [4]byte{'a', 'v', '0', '1'}
|
||||||
|
FOURCC_VP9 = [4]byte{'v', 'p', '0', '9'}
|
||||||
|
FOURCC_HEVC = [4]byte{'h', 'v', 'c', '1'}
|
||||||
|
)
|
||||||
|
|
||||||
|
func FourCCToFloat(fourcc [4]byte) float64 {
|
||||||
|
i := int(fourcc[0])<<24 | int(fourcc[1])<<16 | int(fourcc[2])<<8 | int(fourcc[3])
|
||||||
|
|
||||||
|
return float64(i)
|
||||||
|
}
|
||||||
|
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
/*
|
/*
|
||||||
8 = Audio
|
8 = Audio
|
||||||
@ -126,14 +148,22 @@ type Tag struct {
|
|||||||
AACPacketType uint8
|
AACPacketType uint8
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
0: reserved
|
||||||
1: keyframe (for AVC, a seekable frame)
|
1: keyframe (for AVC, a seekable frame)
|
||||||
2: inter frame (for AVC, a non- seekable frame)
|
2: inter frame (for AVC, a non- seekable frame)
|
||||||
3: disposable inter frame (H.263 only)
|
3: disposable inter frame (H.263 only)
|
||||||
4: generated keyframe (reserved for server use only)
|
4: generated keyframe (reserved for server use only)
|
||||||
5: video info/command frame
|
5: video info/command frame
|
||||||
|
6: reserved
|
||||||
|
7: reserved
|
||||||
*/
|
*/
|
||||||
FrameType uint8
|
FrameType uint8
|
||||||
|
|
||||||
|
/*
|
||||||
|
FrameType & 0b1000 != 0
|
||||||
|
*/
|
||||||
|
IsExHeader bool
|
||||||
|
|
||||||
/*
|
/*
|
||||||
1: JPEG (currently unused)
|
1: JPEG (currently unused)
|
||||||
2: Sorenson H.263
|
2: Sorenson H.263
|
||||||
@ -145,6 +175,16 @@ type Tag struct {
|
|||||||
*/
|
*/
|
||||||
CodecID uint8
|
CodecID uint8
|
||||||
|
|
||||||
|
/*
|
||||||
|
0: PacketTypeSequenceStart
|
||||||
|
1: PacketTypeCodedFrames
|
||||||
|
2: PacketTypeSequenceEnd
|
||||||
|
3: PacketTypeCodedFramesX
|
||||||
|
4: PacketTypeMetadata
|
||||||
|
5: PacketTypeMPEG2TSSequenceStart
|
||||||
|
*/
|
||||||
|
PacketType uint8
|
||||||
|
|
||||||
/*
|
/*
|
||||||
0: AVC sequence header
|
0: AVC sequence header
|
||||||
1: AVC NALU
|
1: AVC NALU
|
||||||
@ -154,18 +194,20 @@ type Tag struct {
|
|||||||
|
|
||||||
CompositionTime int32
|
CompositionTime int32
|
||||||
|
|
||||||
|
FourCC [4]byte
|
||||||
|
|
||||||
Data []byte
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self Tag) ChannelLayout() av.ChannelLayout {
|
func (t Tag) ChannelLayout() av.ChannelLayout {
|
||||||
if self.SoundType == SOUND_MONO {
|
if t.SoundType == SOUND_MONO {
|
||||||
return av.CH_MONO
|
return av.CH_MONO
|
||||||
} else {
|
} else {
|
||||||
return av.CH_STEREO
|
return av.CH_STEREO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Tag) audioParseHeader(b []byte) (n int, err error) {
|
func (t *Tag) audioParseHeader(b []byte) (n int, err error) {
|
||||||
if len(b) < n+1 {
|
if len(b) < n+1 {
|
||||||
err = fmt.Errorf("audiodata: parse invalid")
|
err = fmt.Errorf("audiodata: parse invalid")
|
||||||
return
|
return
|
||||||
@ -173,97 +215,163 @@ func (self *Tag) audioParseHeader(b []byte) (n int, err error) {
|
|||||||
|
|
||||||
flags := b[n]
|
flags := b[n]
|
||||||
n++
|
n++
|
||||||
self.SoundFormat = flags >> 4
|
t.SoundFormat = flags >> 4
|
||||||
self.SoundRate = (flags >> 2) & 0x3
|
t.SoundRate = (flags >> 2) & 0x3
|
||||||
self.SoundSize = (flags >> 1) & 0x1
|
t.SoundSize = (flags >> 1) & 0x1
|
||||||
self.SoundType = flags & 0x1
|
t.SoundType = flags & 0x1
|
||||||
|
|
||||||
switch self.SoundFormat {
|
switch t.SoundFormat {
|
||||||
case SOUND_AAC:
|
case SOUND_AAC:
|
||||||
if len(b) < n+1 {
|
if len(b) < n+1 {
|
||||||
err = fmt.Errorf("audiodata: parse invalid")
|
err = fmt.Errorf("audiodata: parse invalid")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.AACPacketType = b[n]
|
t.AACPacketType = b[n]
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self Tag) audioFillHeader(b []byte) (n int) {
|
func (t Tag) audioFillHeader(b []byte) (n int) {
|
||||||
var flags uint8
|
var flags uint8
|
||||||
flags |= self.SoundFormat << 4
|
flags |= t.SoundFormat << 4
|
||||||
flags |= self.SoundRate << 2
|
flags |= t.SoundRate << 2
|
||||||
flags |= self.SoundSize << 1
|
flags |= t.SoundSize << 1
|
||||||
flags |= self.SoundType
|
flags |= t.SoundType
|
||||||
b[n] = flags
|
b[n] = flags
|
||||||
n++
|
n++
|
||||||
|
|
||||||
switch self.SoundFormat {
|
switch t.SoundFormat {
|
||||||
case SOUND_AAC:
|
case SOUND_AAC:
|
||||||
b[n] = self.AACPacketType
|
b[n] = t.AACPacketType
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Tag) videoParseHeader(b []byte) (n int, err error) {
|
func (t *Tag) videoParseHeader(b []byte) (n int, err error) {
|
||||||
if len(b) < n+1 {
|
if len(b) < n+1 {
|
||||||
err = fmt.Errorf("videodata: parse invalid")
|
err = fmt.Errorf("videodata: parse invalid")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
flags := b[n]
|
flags := b[n]
|
||||||
self.FrameType = flags >> 4
|
t.FrameType = flags >> 4
|
||||||
self.CodecID = flags & 0xf
|
t.CodecID = flags & 0b1111
|
||||||
|
|
||||||
|
//fmt.Printf("%#8b\n", flags)
|
||||||
n++
|
n++
|
||||||
|
|
||||||
if self.FrameType == FRAME_INTER || self.FrameType == FRAME_KEY {
|
if (t.FrameType & 0b1000) != 0 {
|
||||||
|
t.IsExHeader = true
|
||||||
|
t.PacketType = t.CodecID
|
||||||
|
t.CodecID = 0
|
||||||
|
|
||||||
|
if t.PacketType != PKTTYPE_METADATA {
|
||||||
|
t.FrameType = t.FrameType & 0b0111
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !t.IsExHeader {
|
||||||
|
if t.FrameType == FRAME_INTER || t.FrameType == FRAME_KEY {
|
||||||
if len(b) < n+4 {
|
if len(b) < n+4 {
|
||||||
err = fmt.Errorf("videodata: parse invalid")
|
err = fmt.Errorf("videodata: parse invalid: neither interframe nor keyframe")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.AVCPacketType = b[n]
|
t.AVCPacketType = b[n]
|
||||||
|
switch t.AVCPacketType {
|
||||||
|
case AVC_SEQHDR:
|
||||||
|
t.PacketType = PKTTYPE_SEQUENCE_START
|
||||||
|
case AVC_NALU:
|
||||||
|
t.PacketType = PKTTYPE_CODED_FRAMES
|
||||||
|
case AVC_EOS:
|
||||||
|
t.PacketType = PKTTYPE_SEQUENCE_END
|
||||||
|
}
|
||||||
n++
|
n++
|
||||||
|
|
||||||
self.CompositionTime = pio.I24BE(b[n:])
|
t.CompositionTime = pio.I24BE(b[n:])
|
||||||
n += 3
|
n += 3
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if len(b) < n+4 {
|
||||||
|
err = fmt.Errorf("videodata: parse invalid: not enough bytes for the fourCC value")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.FourCC[0] = b[n]
|
||||||
|
t.FourCC[1] = b[n+1]
|
||||||
|
t.FourCC[2] = b[n+2]
|
||||||
|
t.FourCC[3] = b[n+3]
|
||||||
|
|
||||||
|
n += 4
|
||||||
|
|
||||||
|
t.CompositionTime = 0
|
||||||
|
|
||||||
|
if t.FourCC == FOURCC_HEVC {
|
||||||
|
if t.PacketType == PKTTYPE_CODED_FRAMES {
|
||||||
|
t.CompositionTime = pio.I24BE(b[n:])
|
||||||
|
n += 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("parseVideoHeader: PacketType: %d\n%s\n", t.PacketType, hex.Dump(b[:n]))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self Tag) videoFillHeader(b []byte) (n int) {
|
func (t Tag) videoFillHeader(b []byte) (n int) {
|
||||||
flags := self.FrameType<<4 | self.CodecID
|
if t.IsExHeader {
|
||||||
|
flags := t.FrameType<<4 | t.PacketType | 0b10000000
|
||||||
b[n] = flags
|
b[n] = flags
|
||||||
n++
|
n++
|
||||||
b[n] = self.AVCPacketType
|
b[n] = t.FourCC[0]
|
||||||
n++
|
b[n+1] = t.FourCC[1]
|
||||||
pio.PutI24BE(b[n:], self.CompositionTime)
|
b[n+2] = t.FourCC[2]
|
||||||
|
b[n+3] = t.FourCC[3]
|
||||||
|
n += 4
|
||||||
|
|
||||||
|
if t.FourCC == FOURCC_HEVC {
|
||||||
|
if t.PacketType == PKTTYPE_CODED_FRAMES {
|
||||||
|
pio.PutI24BE(b[n:], t.CompositionTime)
|
||||||
n += 3
|
n += 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
flags := t.FrameType<<4 | t.CodecID
|
||||||
|
b[n] = flags
|
||||||
|
n++
|
||||||
|
b[n] = t.AVCPacketType
|
||||||
|
n++
|
||||||
|
pio.PutI24BE(b[n:], t.CompositionTime)
|
||||||
|
n += 3
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("videoFillHeader: PacketType: %d\n%s\n", t.PacketType, hex.Dump(b[:n]))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self Tag) FillHeader(b []byte) (n int) {
|
func (t Tag) FillHeader(b []byte) (n int) {
|
||||||
switch self.Type {
|
switch t.Type {
|
||||||
case TAG_AUDIO:
|
case TAG_AUDIO:
|
||||||
return self.audioFillHeader(b)
|
return t.audioFillHeader(b)
|
||||||
|
|
||||||
case TAG_VIDEO:
|
case TAG_VIDEO:
|
||||||
return self.videoFillHeader(b)
|
return t.videoFillHeader(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Tag) ParseHeader(b []byte) (n int, err error) {
|
func (t *Tag) ParseHeader(b []byte) (n int, err error) {
|
||||||
switch self.Type {
|
switch t.Type {
|
||||||
case TAG_AUDIO:
|
case TAG_AUDIO:
|
||||||
return self.audioParseHeader(b)
|
return t.audioParseHeader(b)
|
||||||
|
|
||||||
case TAG_VIDEO:
|
case TAG_VIDEO:
|
||||||
return self.videoParseHeader(b)
|
return t.videoParseHeader(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -78,6 +78,7 @@ func (self *Server) handleConn(conn *Conn) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if conn.playing {
|
if conn.playing {
|
||||||
|
fmt.Printf("play\n")
|
||||||
if self.HandlePlay != nil {
|
if self.HandlePlay != nil {
|
||||||
self.HandlePlay(conn)
|
self.HandlePlay(conn)
|
||||||
}
|
}
|
||||||
@ -463,6 +464,8 @@ func (self *Conn) readConnect() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("readConnect: %+v\n", self.commandobj)
|
||||||
|
|
||||||
var ok bool
|
var ok bool
|
||||||
var _app, _tcurl interface{}
|
var _app, _tcurl interface{}
|
||||||
if _app, ok = self.commandobj["app"]; !ok {
|
if _app, ok = self.commandobj["app"]; !ok {
|
||||||
@ -691,6 +694,8 @@ func (self *Conn) writeConnect(path string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("writeConnect: app: %s\n", path)
|
||||||
|
|
||||||
// > connect("app")
|
// > connect("app")
|
||||||
if Debug {
|
if Debug {
|
||||||
fmt.Printf("rtmp: > connect('%s') host=%s\n", path, self.URL.Host)
|
fmt.Printf("rtmp: > connect('%s') host=%s\n", path, self.URL.Host)
|
||||||
@ -705,6 +710,7 @@ func (self *Conn) writeConnect(path string) (err error) {
|
|||||||
"audioCodecs": 4071,
|
"audioCodecs": 4071,
|
||||||
"videoCodecs": 252,
|
"videoCodecs": 252,
|
||||||
"videoFunction": 1,
|
"videoFunction": 1,
|
||||||
|
"fourCcList": flvio.AMFArray{"av01", "vp09", "hvc1"},
|
||||||
},
|
},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return
|
return
|
||||||
@ -984,14 +990,19 @@ func (self *Conn) WriteHeader(streams []av.CodecData) (err error) {
|
|||||||
|
|
||||||
var metadata flvio.AMFMap = nil
|
var metadata flvio.AMFMap = nil
|
||||||
|
|
||||||
metadata = self.GetMetaData()
|
//metadata = self.GetMetaData()
|
||||||
|
|
||||||
|
fmt.Printf("WriteHeader\n")
|
||||||
|
|
||||||
if metadata == nil {
|
if metadata == nil {
|
||||||
if metadata, err = flv.NewMetadataByStreams(streams); err != nil {
|
if metadata, err = flv.NewMetadataByStreams(streams); err != nil {
|
||||||
|
fmt.Printf("WriteHeader error: %s\n", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("WriteHeader: %#v\n", metadata)
|
||||||
|
|
||||||
// > onMetaData()
|
// > onMetaData()
|
||||||
if err = self.writeDataMsg(5, self.avmsgsid, "onMetaData", metadata); err != nil {
|
if err = self.writeDataMsg(5, self.avmsgsid, "onMetaData", metadata); err != nil {
|
||||||
return
|
return
|
||||||
@ -1547,17 +1558,21 @@ func (self *Conn) handleMsg(timestamp uint32, msgsid uint32, msgtypeid uint8, ms
|
|||||||
|
|
||||||
if metaindex != -1 && metaindex < len(self.datamsgvals) {
|
if metaindex != -1 && metaindex < len(self.datamsgvals) {
|
||||||
self.metadata = self.datamsgvals[metaindex].(flvio.AMFMap)
|
self.metadata = self.datamsgvals[metaindex].(flvio.AMFMap)
|
||||||
|
fmt.Printf("onMetadata: %+v\n", self.metadata)
|
||||||
|
fmt.Printf("videocodecid: %#08x (%f)\n", int64(self.metadata["videocodecid"].(float64)), self.metadata["videocodecid"].(float64))
|
||||||
}
|
}
|
||||||
|
|
||||||
case msgtypeidVideoMsg:
|
case msgtypeidVideoMsg:
|
||||||
if len(msgdata) == 0 {
|
if len(msgdata) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
//fmt.Printf("msgdata: %#08x\n", msgdata[:5])
|
||||||
tag := flvio.Tag{Type: flvio.TAG_VIDEO}
|
tag := flvio.Tag{Type: flvio.TAG_VIDEO}
|
||||||
var n int
|
var n int
|
||||||
if n, err = (&tag).ParseHeader(msgdata); err != nil {
|
if n, err = (&tag).ParseHeader(msgdata); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
//fmt.Printf("tag: %+v\n", tag)
|
||||||
if !(tag.FrameType == flvio.FRAME_INTER || tag.FrameType == flvio.FRAME_KEY) {
|
if !(tag.FrameType == flvio.FRAME_INTER || tag.FrameType == flvio.FRAME_KEY) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,30 @@ func (self *GolombBitReader) ReadBits(n int) (res uint, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *GolombBitReader) ReadBits32(n uint) (r uint32, err error) {
|
||||||
|
var t uint
|
||||||
|
for i := uint(0); i < n; i++ {
|
||||||
|
t, err = self.ReadBit()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r = (r << 1) | uint32(t)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *GolombBitReader) ReadBits64(n uint) (r uint64, err error) {
|
||||||
|
var t uint
|
||||||
|
for i := uint(0); i < n; i++ {
|
||||||
|
t, err = self.ReadBit()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r = (r << 1) | uint64(t)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (self *GolombBitReader) ReadExponentialGolombCode() (res uint, err error) {
|
func (self *GolombBitReader) ReadExponentialGolombCode() (res uint, err error) {
|
||||||
i := 0
|
i := 0
|
||||||
for {
|
for {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user