h265 video mp4 support
This commit is contained in:
parent
ae4ae47c04
commit
01cae17ec2
@ -2,6 +2,7 @@ package h265parser
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/deepch/vdk/av"
|
"github.com/deepch/vdk/av"
|
||||||
@ -9,6 +10,32 @@ import (
|
|||||||
"github.com/deepch/vdk/utils/bits/pio"
|
"github.com/deepch/vdk/utils/bits/pio"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SPSInfo struct {
|
||||||
|
ProfileIdc uint
|
||||||
|
LevelIdc uint
|
||||||
|
MbWidth uint
|
||||||
|
MbHeight uint
|
||||||
|
CropLeft uint
|
||||||
|
CropRight uint
|
||||||
|
CropTop uint
|
||||||
|
CropBottom uint
|
||||||
|
Width uint
|
||||||
|
Height uint
|
||||||
|
numTemporalLayers uint
|
||||||
|
temporalIdNested uint
|
||||||
|
chromaFormat uint
|
||||||
|
PicWidthInLumaSamples uint
|
||||||
|
PicHeightInLumaSamples uint
|
||||||
|
bitDepthLumaMinus8 uint
|
||||||
|
bitDepthChromaMinus8 uint
|
||||||
|
generalProfileSpace uint
|
||||||
|
generalTierFlag uint
|
||||||
|
generalProfileIDC uint
|
||||||
|
generalProfileCompatibilityFlags uint32
|
||||||
|
generalConstraintIndicatorFlags uint64
|
||||||
|
generalLevelIDC uint
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NAL_UNIT_CODED_SLICE_TRAIL_N = 0
|
NAL_UNIT_CODED_SLICE_TRAIL_N = 0
|
||||||
NAL_UNIT_CODED_SLICE_TRAIL_R = 1
|
NAL_UNIT_CODED_SLICE_TRAIL_R = 1
|
||||||
@ -83,6 +110,11 @@ const (
|
|||||||
MAX_SPS_COUNT = 32
|
MAX_SPS_COUNT = 32
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrorH265IncorectUnitSize = errors.New("Invorect Unit Size")
|
||||||
|
ErrorH265IncorectUnitType = errors.New("Incorect Unit Type")
|
||||||
|
)
|
||||||
|
|
||||||
func IsDataNALU(b []byte) bool {
|
func IsDataNALU(b []byte) bool {
|
||||||
typ := b[0] & 0x1f
|
typ := b[0] & 0x1f
|
||||||
return typ >= 1 && typ <= 5
|
return typ >= 1 && typ <= 5
|
||||||
@ -128,7 +160,6 @@ func SplitNALUs(b []byte) (nalus [][]byte, typ int) {
|
|||||||
return nalus, NALU_AVCC
|
return nalus, NALU_AVCC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// is Annex B
|
|
||||||
if val3 == 1 || val4 == 1 {
|
if val3 == 1 || val4 == 1 {
|
||||||
_val3 := val3
|
_val3 := val3
|
||||||
_val4 := val4
|
_val4 := val4
|
||||||
@ -175,212 +206,216 @@ func SplitNALUs(b []byte) (nalus [][]byte, typ int) {
|
|||||||
return [][]byte{b}, NALU_RAW
|
return [][]byte{b}, NALU_RAW
|
||||||
}
|
}
|
||||||
|
|
||||||
type SPSInfo struct {
|
func ParseSPS(sps []byte) (ctx SPSInfo, err error) {
|
||||||
ProfileIdc uint
|
if len(sps) < 2 {
|
||||||
LevelIdc uint
|
err = ErrorH265IncorectUnitSize
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rbsp := nal2rbsp(sps[2:])
|
||||||
|
br := &bits.GolombBitReader{R: bytes.NewReader(rbsp)}
|
||||||
|
if _, err = br.ReadBits(4); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
spsMaxSubLayersMinus1, err := br.ReadBits(3)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
MbWidth uint
|
if spsMaxSubLayersMinus1+1 > ctx.numTemporalLayers {
|
||||||
MbHeight uint
|
ctx.numTemporalLayers = spsMaxSubLayersMinus1 + 1
|
||||||
|
}
|
||||||
|
if ctx.temporalIdNested, err = br.ReadBit(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = parsePTL(br, &ctx, spsMaxSubLayersMinus1); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var cf uint
|
||||||
|
if cf, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.chromaFormat = uint(cf)
|
||||||
|
if ctx.chromaFormat == 3 {
|
||||||
|
if _, err = br.ReadBit(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ctx.PicWidthInLumaSamples, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Width = uint(ctx.PicWidthInLumaSamples)
|
||||||
|
if ctx.PicHeightInLumaSamples, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Height = uint(ctx.PicHeightInLumaSamples)
|
||||||
|
conformanceWindowFlag, err := br.ReadBit()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if conformanceWindowFlag != 0 {
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CropLeft uint
|
var bdlm8 uint
|
||||||
CropRight uint
|
if bdlm8, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
CropTop uint
|
return
|
||||||
CropBottom uint
|
}
|
||||||
|
ctx.bitDepthChromaMinus8 = uint(bdlm8)
|
||||||
|
var bdcm8 uint
|
||||||
|
if bdcm8, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.bitDepthChromaMinus8 = uint(bdcm8)
|
||||||
|
|
||||||
Width uint
|
_, err = br.ReadExponentialGolombCode()
|
||||||
Height uint
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
spsSubLayerOrderingInfoPresentFlag, err := br.ReadBit()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var i uint
|
||||||
|
if spsSubLayerOrderingInfoPresentFlag != 0 {
|
||||||
|
i = 0
|
||||||
|
} else {
|
||||||
|
i = spsMaxSubLayersMinus1
|
||||||
|
}
|
||||||
|
for ; i <= spsMaxSubLayersMinus1; i++ {
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = br.ReadExponentialGolombCode(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseSPS(data []byte) (self SPSInfo, err error) {
|
func parsePTL(br *bits.GolombBitReader, ctx *SPSInfo, maxSubLayersMinus1 uint) error {
|
||||||
r := &bits.GolombBitReader{R: bytes.NewReader(data)}
|
var err error
|
||||||
|
var ptl SPSInfo
|
||||||
if _, err = r.ReadBits(8); err != nil {
|
if ptl.generalProfileSpace, err = br.ReadBits(2); err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
if ptl.generalTierFlag, err = br.ReadBit(); err != nil {
|
||||||
if self.ProfileIdc, err = r.ReadBits(8); err != nil {
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
if ptl.generalProfileIDC, err = br.ReadBits(5); err != nil {
|
||||||
// constraint_set0_flag-constraint_set6_flag,reserved_zero_2bits
|
return err
|
||||||
if _, err = r.ReadBits(8); err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
if ptl.generalProfileCompatibilityFlags, err = br.ReadBits32(32); err != nil {
|
||||||
// level_idc
|
return err
|
||||||
if self.LevelIdc, err = r.ReadBits(8); err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
if ptl.generalConstraintIndicatorFlags, err = br.ReadBits64(48); err != nil {
|
||||||
// seq_parameter_set_id
|
return err
|
||||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
if ptl.generalLevelIDC, err = br.ReadBits(8); err != nil {
|
||||||
if self.ProfileIdc == 100 || self.ProfileIdc == 110 ||
|
return err
|
||||||
self.ProfileIdc == 122 || self.ProfileIdc == 244 ||
|
}
|
||||||
self.ProfileIdc == 44 || self.ProfileIdc == 83 ||
|
updatePTL(ctx, &ptl)
|
||||||
self.ProfileIdc == 86 || self.ProfileIdc == 118 {
|
if maxSubLayersMinus1 == 0 {
|
||||||
|
return nil
|
||||||
var chroma_format_idc uint
|
}
|
||||||
if chroma_format_idc, err = r.ReadExponentialGolombCode(); err != nil {
|
subLayerProfilePresentFlag := make([]uint, maxSubLayersMinus1)
|
||||||
return
|
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 {
|
||||||
if chroma_format_idc == 3 {
|
return err
|
||||||
// residual_colour_transform_flag
|
|
||||||
if _, err = r.ReadBit(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// bit_depth_luma_minus8
|
if maxSubLayersMinus1 > 0 {
|
||||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
for i := maxSubLayersMinus1; i < 8; i++ {
|
||||||
return
|
if _, err = br.ReadBits(2); err != nil {
|
||||||
}
|
return err
|
||||||
// bit_depth_chroma_minus8
|
|
||||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// qpprime_y_zero_transform_bypass_flag
|
|
||||||
if _, err = r.ReadBit(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var seq_scaling_matrix_present_flag uint
|
|
||||||
if seq_scaling_matrix_present_flag, err = r.ReadBit(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if seq_scaling_matrix_present_flag != 0 {
|
|
||||||
for i := 0; i < 8; i++ {
|
|
||||||
var seq_scaling_list_present_flag uint
|
|
||||||
if seq_scaling_list_present_flag, err = r.ReadBit(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if seq_scaling_list_present_flag != 0 {
|
|
||||||
var sizeOfScalingList uint
|
|
||||||
if i < 6 {
|
|
||||||
sizeOfScalingList = 16
|
|
||||||
} else {
|
|
||||||
sizeOfScalingList = 64
|
|
||||||
}
|
|
||||||
lastScale := uint(8)
|
|
||||||
nextScale := uint(8)
|
|
||||||
for j := uint(0); j < sizeOfScalingList; j++ {
|
|
||||||
if nextScale != 0 {
|
|
||||||
var delta_scale uint
|
|
||||||
if delta_scale, err = r.ReadSE(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
nextScale = (lastScale + delta_scale + 256) % 256
|
|
||||||
}
|
|
||||||
if nextScale != 0 {
|
|
||||||
lastScale = nextScale
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// log2_max_frame_num_minus4
|
if subLayerLevelPresentFlag[i] != 0 {
|
||||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
if _, err = br.ReadBits(8); err != nil {
|
||||||
return
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
var pic_order_cnt_type uint
|
|
||||||
if pic_order_cnt_type, err = r.ReadExponentialGolombCode(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if pic_order_cnt_type == 0 {
|
|
||||||
// log2_max_pic_order_cnt_lsb_minus4
|
|
||||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if pic_order_cnt_type == 1 {
|
|
||||||
// delta_pic_order_always_zero_flag
|
|
||||||
if _, err = r.ReadBit(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// offset_for_non_ref_pic
|
|
||||||
if _, err = r.ReadSE(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// offset_for_top_to_bottom_field
|
|
||||||
if _, err = r.ReadSE(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var num_ref_frames_in_pic_order_cnt_cycle uint
|
|
||||||
if num_ref_frames_in_pic_order_cnt_cycle, err = r.ReadExponentialGolombCode(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i := uint(0); i < num_ref_frames_in_pic_order_cnt_cycle; i++ {
|
|
||||||
if _, err = r.ReadSE(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// max_num_ref_frames
|
func updatePTL(ctx, ptl *SPSInfo) {
|
||||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
ctx.generalProfileSpace = ptl.generalProfileSpace
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// gaps_in_frame_num_value_allowed_flag
|
if ptl.generalTierFlag > ctx.generalTierFlag {
|
||||||
if _, err = r.ReadBit(); err != nil {
|
ctx.generalLevelIDC = ptl.generalLevelIDC
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.MbWidth, err = r.ReadExponentialGolombCode(); err != nil {
|
ctx.generalTierFlag = ptl.generalTierFlag
|
||||||
return
|
} else {
|
||||||
}
|
if ptl.generalLevelIDC > ctx.generalLevelIDC {
|
||||||
self.MbWidth++
|
ctx.generalLevelIDC = ptl.generalLevelIDC
|
||||||
|
|
||||||
if self.MbHeight, err = r.ReadExponentialGolombCode(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.MbHeight++
|
|
||||||
|
|
||||||
var frame_mbs_only_flag uint
|
|
||||||
if frame_mbs_only_flag, err = r.ReadBit(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if frame_mbs_only_flag == 0 {
|
|
||||||
// mb_adaptive_frame_field_flag
|
|
||||||
if _, err = r.ReadBit(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// direct_8x8_inference_flag
|
if ptl.generalProfileIDC > ctx.generalProfileIDC {
|
||||||
if _, err = r.ReadBit(); err != nil {
|
ctx.generalProfileIDC = ptl.generalProfileIDC
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var frame_cropping_flag uint
|
ctx.generalProfileCompatibilityFlags &= ptl.generalProfileCompatibilityFlags
|
||||||
if frame_cropping_flag, err = r.ReadBit(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if frame_cropping_flag != 0 {
|
|
||||||
if self.CropLeft, err = r.ReadExponentialGolombCode(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if self.CropRight, err = r.ReadExponentialGolombCode(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if self.CropTop, err = r.ReadExponentialGolombCode(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if self.CropBottom, err = r.ReadExponentialGolombCode(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.Width = (self.MbWidth * 16) - self.CropLeft*2 - self.CropRight*2
|
ctx.generalConstraintIndicatorFlags &= ptl.generalConstraintIndicatorFlags
|
||||||
self.Height = ((2 - frame_mbs_only_flag) * self.MbHeight * 16) - self.CropTop*2 - self.CropBottom*2
|
}
|
||||||
|
|
||||||
return
|
func nal2rbsp(nal []byte) []byte {
|
||||||
|
return bytes.Replace(nal, []byte{0x0, 0x0, 0x3}, []byte{0x0, 0x0}, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
type CodecData struct {
|
type CodecData struct {
|
||||||
@ -443,23 +478,20 @@ func NewCodecDataFromAVCDecoderConfRecord(record []byte) (self CodecData, err er
|
|||||||
|
|
||||||
func NewCodecDataFromVPSAndSPSAndPPS(vps, sps, pps []byte) (self CodecData, err error) {
|
func NewCodecDataFromVPSAndSPSAndPPS(vps, sps, pps []byte) (self CodecData, err error) {
|
||||||
recordinfo := AVCDecoderConfRecord{}
|
recordinfo := AVCDecoderConfRecord{}
|
||||||
recordinfo.AVCProfileIndication = sps[1]
|
recordinfo.AVCProfileIndication = sps[3]
|
||||||
recordinfo.ProfileCompatibility = sps[2]
|
recordinfo.ProfileCompatibility = sps[4]
|
||||||
recordinfo.AVCLevelIndication = sps[3]
|
recordinfo.AVCLevelIndication = sps[5]
|
||||||
recordinfo.SPS = [][]byte{sps}
|
recordinfo.SPS = [][]byte{sps}
|
||||||
recordinfo.PPS = [][]byte{pps}
|
recordinfo.PPS = [][]byte{pps}
|
||||||
recordinfo.VPS = [][]byte{vps}
|
recordinfo.VPS = [][]byte{vps}
|
||||||
recordinfo.LengthSizeMinusOne = 3
|
recordinfo.LengthSizeMinusOne = 3
|
||||||
|
|
||||||
buf := make([]byte, recordinfo.Len())
|
|
||||||
recordinfo.Marshal(buf)
|
|
||||||
|
|
||||||
self.RecordInfo = recordinfo
|
|
||||||
self.Record = buf
|
|
||||||
|
|
||||||
if self.SPSInfo, err = ParseSPS(sps); err != nil {
|
if self.SPSInfo, err = ParseSPS(sps); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
buf := make([]byte, recordinfo.Len())
|
||||||
|
recordinfo.Marshal(buf, self.SPSInfo)
|
||||||
|
self.RecordInfo = recordinfo
|
||||||
|
self.Record = buf
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -480,14 +512,12 @@ func (self *AVCDecoderConfRecord) Unmarshal(b []byte) (n int, err error) {
|
|||||||
err = ErrDecconfInvalid
|
err = ErrDecconfInvalid
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.AVCProfileIndication = b[1]
|
self.AVCProfileIndication = b[1]
|
||||||
self.ProfileCompatibility = b[2]
|
self.ProfileCompatibility = b[2]
|
||||||
self.AVCLevelIndication = b[3]
|
self.AVCLevelIndication = b[3]
|
||||||
self.LengthSizeMinusOne = b[4] & 0x03
|
self.LengthSizeMinusOne = b[4] & 0x03
|
||||||
spscount := int(b[5] & 0x1f)
|
spscount := int(b[5] & 0x1f)
|
||||||
n += 6
|
n += 6
|
||||||
|
|
||||||
for i := 0; i < spscount; i++ {
|
for i := 0; i < spscount; i++ {
|
||||||
if len(b) < n+2 {
|
if len(b) < n+2 {
|
||||||
err = ErrDecconfInvalid
|
err = ErrDecconfInvalid
|
||||||
@ -550,55 +580,63 @@ func (self *AVCDecoderConfRecord) Unmarshal(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self AVCDecoderConfRecord) Len() (n int) {
|
func (self AVCDecoderConfRecord) Len() (n int) {
|
||||||
n = 7
|
n = 23
|
||||||
for _, sps := range self.SPS {
|
for _, sps := range self.SPS {
|
||||||
n += 2 + len(sps)
|
n += 5 + len(sps)
|
||||||
}
|
}
|
||||||
for _, pps := range self.PPS {
|
for _, pps := range self.PPS {
|
||||||
n += 2 + len(pps)
|
n += 5 + len(pps)
|
||||||
}
|
}
|
||||||
for _, vps := range self.VPS {
|
for _, vps := range self.VPS {
|
||||||
n += 2 + len(vps)
|
n += 5 + len(vps)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self AVCDecoderConfRecord) Marshal(b []byte) (n int) {
|
func (self AVCDecoderConfRecord) Marshal(b []byte, si SPSInfo) (n int) {
|
||||||
b[0] = 1
|
b[0] = 1
|
||||||
b[1] = self.AVCProfileIndication
|
b[1] = self.AVCProfileIndication
|
||||||
b[2] = self.ProfileCompatibility
|
b[2] = self.ProfileCompatibility
|
||||||
b[3] = self.AVCLevelIndication
|
b[3] = self.AVCLevelIndication
|
||||||
b[4] = self.LengthSizeMinusOne | 0xfc
|
b[21] = 3
|
||||||
b[5] = uint8(len(self.SPS)) | 0xe0
|
b[22] = 3
|
||||||
n += 6
|
n += 23
|
||||||
|
b[n] = (self.VPS[0][0] >> 1) & 0x3f
|
||||||
for _, sps := range self.SPS {
|
|
||||||
pio.PutU16BE(b[n:], uint16(len(sps)))
|
|
||||||
n += 2
|
|
||||||
copy(b[n:], sps)
|
|
||||||
n += len(sps)
|
|
||||||
}
|
|
||||||
|
|
||||||
b[n] = uint8(len(self.PPS))
|
|
||||||
n++
|
n++
|
||||||
|
b[n] = byte(len(self.VPS) >> 8)
|
||||||
for _, pps := range self.PPS {
|
n++
|
||||||
pio.PutU16BE(b[n:], uint16(len(pps)))
|
b[n] = byte(len(self.VPS))
|
||||||
n += 2
|
|
||||||
copy(b[n:], pps)
|
|
||||||
n += len(pps)
|
|
||||||
}
|
|
||||||
|
|
||||||
b[n] = uint8(len(self.VPS))
|
|
||||||
n++
|
n++
|
||||||
|
|
||||||
for _, vps := range self.VPS {
|
for _, vps := range self.VPS {
|
||||||
pio.PutU16BE(b[n:], uint16(len(vps)))
|
pio.PutU16BE(b[n:], uint16(len(vps)))
|
||||||
n += 2
|
n += 2
|
||||||
copy(b[n:], vps)
|
copy(b[n:], vps)
|
||||||
n += len(vps)
|
n += len(vps)
|
||||||
}
|
}
|
||||||
|
b[n] = (self.SPS[0][0] >> 1) & 0x3f
|
||||||
|
n++
|
||||||
|
b[n] = byte(len(self.SPS) >> 8)
|
||||||
|
n++
|
||||||
|
b[n] = byte(len(self.SPS))
|
||||||
|
n++
|
||||||
|
for _, sps := range self.SPS {
|
||||||
|
pio.PutU16BE(b[n:], uint16(len(sps)))
|
||||||
|
n += 2
|
||||||
|
copy(b[n:], sps)
|
||||||
|
n += len(sps)
|
||||||
|
}
|
||||||
|
b[n] = (self.PPS[0][0] >> 1) & 0x3f
|
||||||
|
n++
|
||||||
|
b[n] = byte(len(self.PPS) >> 8)
|
||||||
|
n++
|
||||||
|
b[n] = byte(len(self.PPS))
|
||||||
|
n++
|
||||||
|
for _, pps := range self.PPS {
|
||||||
|
pio.PutU16BE(b[n:], uint16(len(pps)))
|
||||||
|
n += 2
|
||||||
|
copy(b[n:], pps)
|
||||||
|
n += len(pps)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -623,17 +661,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func ParseSliceHeaderFromNALU(packet []byte) (sliceType SliceType, err error) {
|
func ParseSliceHeaderFromNALU(packet []byte) (sliceType SliceType, err error) {
|
||||||
|
|
||||||
if len(packet) <= 1 {
|
if len(packet) <= 1 {
|
||||||
err = fmt.Errorf("h265parser: packet too short to parse slice header")
|
err = fmt.Errorf("h265parser: packet too short to parse slice header")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
nal_unit_type := packet[0] & 0x1f
|
nal_unit_type := packet[0] & 0x1f
|
||||||
switch nal_unit_type {
|
switch nal_unit_type {
|
||||||
case 1, 2, 5, 19:
|
case 1, 2, 5, 19:
|
||||||
// slice_layer_without_partitioning_rbsp
|
|
||||||
// slice_data_partition_a_layer_rbsp
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("h265parser: nal_unit_type=%d has no slice header", nal_unit_type)
|
err = fmt.Errorf("h265parser: nal_unit_type=%d has no slice header", nal_unit_type)
|
||||||
@ -641,13 +675,9 @@ func ParseSliceHeaderFromNALU(packet []byte) (sliceType SliceType, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
r := &bits.GolombBitReader{R: bytes.NewReader(packet[1:])}
|
r := &bits.GolombBitReader{R: bytes.NewReader(packet[1:])}
|
||||||
|
|
||||||
// first_mb_in_slice
|
|
||||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// slice_type
|
|
||||||
var u uint
|
var u uint
|
||||||
if u, err = r.ReadExponentialGolombCode(); err != nil {
|
if u, err = r.ReadExponentialGolombCode(); err != nil {
|
||||||
return
|
return
|
||||||
@ -664,6 +694,5 @@ func ParseSliceHeaderFromNALU(packet []byte) (sliceType SliceType, err error) {
|
|||||||
err = fmt.Errorf("h265parser: slice_type=%d invalid", u)
|
err = fmt.Errorf("h265parser: slice_type=%d invalid", u)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -181,6 +181,7 @@ func (self *Stream) fillTrackAtom() (err error) {
|
|||||||
} else if self.Type() == av.H265 {
|
} else if self.Type() == av.H265 {
|
||||||
codec := self.CodecData.(h265parser.CodecData)
|
codec := self.CodecData.(h265parser.CodecData)
|
||||||
width, height := codec.Width(), codec.Height()
|
width, height := codec.Width(), codec.Height()
|
||||||
|
|
||||||
self.sample.SampleDesc.HV1Desc = &mp4io.HV1Desc{
|
self.sample.SampleDesc.HV1Desc = &mp4io.HV1Desc{
|
||||||
DataRefIdx: 1,
|
DataRefIdx: 1,
|
||||||
HorizontalResolution: 72,
|
HorizontalResolution: 72,
|
||||||
@ -192,6 +193,7 @@ func (self *Stream) fillTrackAtom() (err error) {
|
|||||||
ColorTableId: -1,
|
ColorTableId: -1,
|
||||||
Conf: &mp4io.HV1Conf{Data: codec.AVCDecoderConfRecordBytes()},
|
Conf: &mp4io.HV1Conf{Data: codec.AVCDecoderConfRecordBytes()},
|
||||||
}
|
}
|
||||||
|
|
||||||
self.trackAtom.Media.Handler = &mp4io.HandlerRefer{
|
self.trackAtom.Media.Handler = &mp4io.HandlerRefer{
|
||||||
SubType: [4]byte{'v', 'i', 'd', 'e'},
|
SubType: [4]byte{'v', 'i', 'd', 'e'},
|
||||||
Name: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'G', 'G', 0, 0, 0},
|
Name: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'G', 'G', 0, 0, 0},
|
||||||
@ -200,6 +202,7 @@ func (self *Stream) fillTrackAtom() (err error) {
|
|||||||
Flags: 0x000001,
|
Flags: 0x000001,
|
||||||
}
|
}
|
||||||
self.codecString = fmt.Sprintf("hvc1.%02X%02X%02X", codec.RecordInfo.AVCProfileIndication, codec.RecordInfo.ProfileCompatibility, codec.RecordInfo.AVCLevelIndication)
|
self.codecString = fmt.Sprintf("hvc1.%02X%02X%02X", codec.RecordInfo.AVCProfileIndication, codec.RecordInfo.ProfileCompatibility, codec.RecordInfo.AVCLevelIndication)
|
||||||
|
|
||||||
} else if self.Type() == av.AAC {
|
} else if self.Type() == av.AAC {
|
||||||
codec := self.CodecData.(aacparser.CodecData)
|
codec := self.CodecData.(aacparser.CodecData)
|
||||||
self.sample.SampleDesc.MP4ADesc = &mp4io.MP4ADesc{
|
self.sample.SampleDesc.MP4ADesc = &mp4io.MP4ADesc{
|
||||||
@ -354,6 +357,7 @@ func (element *Stream) writePacketV3(pkt av.Packet, rawdur time.Duration, maxFra
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (element *Stream) writePacketV2(pkt av.Packet, rawdur time.Duration, maxFrames int) (bool, []byte, error) {
|
func (element *Stream) writePacketV2(pkt av.Packet, rawdur time.Duration, maxFrames int) (bool, []byte, error) {
|
||||||
|
//pkt.Data = pkt.Data[4:]
|
||||||
trackID := pkt.Idx + 1
|
trackID := pkt.Idx + 1
|
||||||
if element.sampleIndex == 0 {
|
if element.sampleIndex == 0 {
|
||||||
element.moof.Header = &mp4fio.MovieFragHeader{Seqnum: uint32(element.muxer.fragmentIndex + 1)}
|
element.moof.Header = &mp4fio.MovieFragHeader{Seqnum: uint32(element.muxer.fragmentIndex + 1)}
|
||||||
|
@ -142,26 +142,33 @@ func Dial(options RTSPClientOptions) (*RTSPClient, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if i2.AVType == VIDEO {
|
if i2.AVType == VIDEO {
|
||||||
if i2.Type == av.H264 && len(i2.SpropParameterSets) > 1 {
|
if i2.Type == av.H264 {
|
||||||
if codecData, err := h264parser.NewCodecDataFromSPSAndPPS(i2.SpropParameterSets[0], i2.SpropParameterSets[1]); err == nil {
|
if len(i2.SpropParameterSets) > 1 {
|
||||||
client.sps = i2.SpropParameterSets[0]
|
if codecData, err := h264parser.NewCodecDataFromSPSAndPPS(i2.SpropParameterSets[0], i2.SpropParameterSets[1]); err == nil {
|
||||||
client.pps = i2.SpropParameterSets[1]
|
client.sps = i2.SpropParameterSets[0]
|
||||||
client.CodecData = append(client.CodecData, codecData)
|
client.pps = i2.SpropParameterSets[1]
|
||||||
client.videoIDX = int8(len(client.CodecData) - 1)
|
client.CodecData = append(client.CodecData, codecData)
|
||||||
client.videoCodec = av.H264
|
}
|
||||||
|
} else {
|
||||||
|
client.CodecData = append(client.CodecData, h264parser.CodecData{})
|
||||||
}
|
}
|
||||||
} else if i2.Type == av.H265 && len(i2.SpropVPS) > 1 && len(i2.SpropSPS) > 1 && len(i2.SpropPPS) > 1 {
|
client.videoCodec = av.H264
|
||||||
if codecData, err := h265parser.NewCodecDataFromVPSAndSPSAndPPS(i2.SpropVPS, i2.SpropSPS, i2.SpropPPS); err == nil {
|
} else if i2.Type == av.H265 {
|
||||||
client.vps = i2.SpropVPS
|
if len(i2.SpropVPS) > 1 && len(i2.SpropSPS) > 1 && len(i2.SpropPPS) > 1 {
|
||||||
client.sps = i2.SpropSPS
|
if codecData, err := h265parser.NewCodecDataFromVPSAndSPSAndPPS(i2.SpropVPS, i2.SpropSPS, i2.SpropPPS); err == nil {
|
||||||
client.pps = i2.SpropPPS
|
client.vps = i2.SpropVPS
|
||||||
client.CodecData = append(client.CodecData, codecData)
|
client.sps = i2.SpropSPS
|
||||||
client.videoIDX = int8(len(client.CodecData) - 1)
|
client.pps = i2.SpropPPS
|
||||||
client.videoCodec = av.H265
|
client.CodecData = append(client.CodecData, codecData)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
client.CodecData = append(client.CodecData, h265parser.CodecData{})
|
||||||
}
|
}
|
||||||
|
client.videoCodec = av.H265
|
||||||
} else {
|
} else {
|
||||||
client.Println("SDP Video Codec Type Not Supported", i2.Type)
|
client.Println("SDP Video Codec Type Not Supported", i2.Type)
|
||||||
}
|
}
|
||||||
|
client.videoIDX = int8(len(client.CodecData) - 1)
|
||||||
client.videoID = ch
|
client.videoID = ch
|
||||||
}
|
}
|
||||||
if i2.AVType == AUDIO {
|
if i2.AVType == AUDIO {
|
||||||
@ -552,7 +559,7 @@ func (client *RTSPClient) RTPDemuxer(payloadRAW *[]byte) ([]*av.Packet, bool) {
|
|||||||
Data: append(binSize(len(nal)), nal...),
|
Data: append(binSize(len(nal)), nal...),
|
||||||
CompositionTime: time.Duration(1) * time.Millisecond,
|
CompositionTime: time.Duration(1) * time.Millisecond,
|
||||||
Idx: client.videoIDX,
|
Idx: client.videoIDX,
|
||||||
IsKeyFrame: naluType == h265parser.NAL_UNIT_CODED_SLICE_IDR_W_RADL,
|
IsKeyFrame: false,
|
||||||
Duration: time.Duration(float32(timestamp-client.PreVideoTS)/90) * time.Millisecond,
|
Duration: time.Duration(float32(timestamp-client.PreVideoTS)/90) * time.Millisecond,
|
||||||
Time: time.Duration(timestamp/90) * time.Millisecond,
|
Time: time.Duration(timestamp/90) * time.Millisecond,
|
||||||
})
|
})
|
||||||
@ -564,22 +571,19 @@ func (client *RTSPClient) RTPDemuxer(payloadRAW *[]byte) ([]*av.Packet, bool) {
|
|||||||
client.CodecUpdatePPS(nal)
|
client.CodecUpdatePPS(nal)
|
||||||
case h265parser.NAL_UNIT_UNSPECIFIED_49:
|
case h265parser.NAL_UNIT_UNSPECIFIED_49:
|
||||||
se := nal[2] >> 6
|
se := nal[2] >> 6
|
||||||
naluType = nal[2] & 0x3f
|
naluType := nal[2] & 0x3f
|
||||||
if se == 2 {
|
if se == 2 {
|
||||||
client.BufferRtpPacket.Truncate(0)
|
client.BufferRtpPacket.Truncate(0)
|
||||||
client.BufferRtpPacket.Reset()
|
client.BufferRtpPacket.Reset()
|
||||||
client.BufferRtpPacket.Write([]byte{0, 0, 0, 0, (nal[0] & 0x81) | (naluType << 1), nal[1]})
|
client.BufferRtpPacket.Write([]byte{(nal[0] & 0x81) | (naluType << 1), nal[1]})
|
||||||
r := make([]byte, 2)
|
r := make([]byte, 2)
|
||||||
r[1] = nal[1]
|
r[1] = nal[1]
|
||||||
r[0] = (nal[0] & 0x81) | (naluType << 1)
|
r[0] = (nal[0] & 0x81) | (naluType << 1)
|
||||||
client.BufferRtpPacket.Write(nal[3:])
|
client.BufferRtpPacket.Write(nal[3:])
|
||||||
} else if se == 1 {
|
} else if se == 1 {
|
||||||
client.BufferRtpPacket.Write(nal[3:])
|
client.BufferRtpPacket.Write(nal[3:])
|
||||||
binary.BigEndian.PutUint32(client.BufferRtpPacket.Bytes()[:4], uint32(client.BufferRtpPacket.Len())-4)
|
|
||||||
buf := make([]byte, client.BufferRtpPacket.Len())
|
|
||||||
copy(buf, client.BufferRtpPacket.Bytes())
|
|
||||||
retmap = append(retmap, &av.Packet{
|
retmap = append(retmap, &av.Packet{
|
||||||
Data: append(binSize(len(nal)), nal...),
|
Data: append(binSize(client.BufferRtpPacket.Len()), client.BufferRtpPacket.Bytes()...),
|
||||||
CompositionTime: time.Duration(1) * time.Millisecond,
|
CompositionTime: time.Duration(1) * time.Millisecond,
|
||||||
Idx: client.videoIDX,
|
Idx: client.videoIDX,
|
||||||
IsKeyFrame: naluType == h265parser.NAL_UNIT_CODED_SLICE_IDR_W_RADL,
|
IsKeyFrame: naluType == h265parser.NAL_UNIT_CODED_SLICE_IDR_W_RADL,
|
||||||
@ -590,8 +594,9 @@ func (client *RTSPClient) RTPDemuxer(payloadRAW *[]byte) ([]*av.Packet, bool) {
|
|||||||
client.BufferRtpPacket.Write(nal[3:])
|
client.BufferRtpPacket.Write(nal[3:])
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
//log.Println("???", naluType)
|
client.Println("Unsupported Nal", naluType)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if client.videoCodec == av.H264 {
|
} else if client.videoCodec == av.H264 {
|
||||||
naluType := nal[0] & 0x1f
|
naluType := nal[0] & 0x1f
|
||||||
switch {
|
switch {
|
||||||
|
@ -195,6 +195,9 @@ func (element *Muxer) WritePacket(pkt av.Packet) (err error) {
|
|||||||
}
|
}
|
||||||
if tmp, ok := element.streams[pkt.Idx]; ok {
|
if tmp, ok := element.streams[pkt.Idx]; ok {
|
||||||
element.StreamACK.Reset(10 * time.Second)
|
element.StreamACK.Reset(10 * time.Second)
|
||||||
|
if len(pkt.Data) < 5 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
switch tmp.codec.Type() {
|
switch tmp.codec.Type() {
|
||||||
case av.H264:
|
case av.H264:
|
||||||
codec := tmp.codec.(h264parser.CodecData)
|
codec := tmp.codec.(h264parser.CodecData)
|
||||||
|
1
go.mod
1
go.mod
@ -5,4 +5,5 @@ go 1.14
|
|||||||
require (
|
require (
|
||||||
github.com/pion/webrtc/v2 v2.2.23 // indirect
|
github.com/pion/webrtc/v2 v2.2.23 // indirect
|
||||||
github.com/pion/webrtc/v3 v3.0.1 // indirect
|
github.com/pion/webrtc/v3 v3.0.1 // indirect
|
||||||
|
github.com/q191201771/lal v0.19.1 // indirect
|
||||||
)
|
)
|
||||||
|
4
go.sum
4
go.sum
@ -100,6 +100,10 @@ github.com/pion/webrtc/v3 v3.0.1/go.mod h1:ePzv8r2tzj95nJuPsns/7OiS5M8RiGSULdxzI
|
|||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/q191201771/lal v0.19.1 h1:Fxh33T+9y7Dp958/SFalzpWlt18LEYYT2z8UZdzhokQ=
|
||||||
|
github.com/q191201771/lal v0.19.1/go.mod h1:HUvNYsaXA7ZwGStxtpnxpH0lN5qzd350glLmdigDg0A=
|
||||||
|
github.com/q191201771/naza v0.17.0 h1:ZuETbHDX8srxxxL4AiDW+HTwEHnbfJvS40R8PUNStpQ=
|
||||||
|
github.com/q191201771/naza v0.17.0/go.mod h1:5LeGupZZFtYP1g/S203n9vXoUNVdlRnPIfM6rExjqt0=
|
||||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
@ -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…
Reference in New Issue
Block a user