From 8f1c244cbf5600df6ea7fc48d8263a56954c8756 Mon Sep 17 00:00:00 2001 From: Tim Mattison Date: Thu, 30 May 2024 10:31:12 -0400 Subject: [PATCH 1/2] co64's version is expected to be 0 https://rtmp.veriskope.com/pdf/video_file_format_spec_v10.pdf --- go-mp4/stco-box.go | 216 ++++++++++++++++++++++----------------------- 1 file changed, 108 insertions(+), 108 deletions(-) diff --git a/go-mp4/stco-box.go b/go-mp4/stco-box.go index b339b9c..d9518a6 100644 --- a/go-mp4/stco-box.go +++ b/go-mp4/stco-box.go @@ -1,8 +1,8 @@ package mp4 import ( - "encoding/binary" - "io" + "encoding/binary" + "io" ) // aligned(8) class ChunkOffsetBox @@ -21,147 +21,147 @@ import ( // } type ChunkOffsetBox struct { - box *FullBox - stco *movstco + box *FullBox + stco *movstco } func NewChunkOffsetBox() *ChunkOffsetBox { - return &ChunkOffsetBox{ - box: NewFullBox([4]byte{'s', 't', 'c', 'o'}, 0), - } + return &ChunkOffsetBox{ + box: NewFullBox([4]byte{'s', 't', 'c', 'o'}, 0), + } } func (stco *ChunkOffsetBox) Size() uint64 { - if stco.stco == nil { - return stco.box.Size() - } else { - return stco.box.Size() + 4 + 4*uint64(stco.stco.entryCount) - } + if stco.stco == nil { + return stco.box.Size() + } else { + return stco.box.Size() + 4 + 4*uint64(stco.stco.entryCount) + } } func (stco *ChunkOffsetBox) Decode(r io.Reader) (offset int, err error) { - if _, err = stco.box.Decode(r); err != nil { - return - } - tmp := make([]byte, 4) - if _, err = io.ReadFull(r, tmp); err != nil { - return - } - offset = 8 - stco.stco = new(movstco) - stco.stco.entryCount = binary.BigEndian.Uint32(tmp) - stco.stco.chunkOffsetlist = make([]uint64, stco.stco.entryCount) - buf := make([]byte, stco.stco.entryCount*4) - if _, err = io.ReadFull(r, buf); err != nil { - return - } - idx := 0 - for i := 0; i < int(stco.stco.entryCount); i++ { - stco.stco.chunkOffsetlist[i] = uint64(binary.BigEndian.Uint32(buf[idx:])) - idx += 4 - } - offset += idx - return + if _, err = stco.box.Decode(r); err != nil { + return + } + tmp := make([]byte, 4) + if _, err = io.ReadFull(r, tmp); err != nil { + return + } + offset = 8 + stco.stco = new(movstco) + stco.stco.entryCount = binary.BigEndian.Uint32(tmp) + stco.stco.chunkOffsetlist = make([]uint64, stco.stco.entryCount) + buf := make([]byte, stco.stco.entryCount*4) + if _, err = io.ReadFull(r, buf); err != nil { + return + } + idx := 0 + for i := 0; i < int(stco.stco.entryCount); i++ { + stco.stco.chunkOffsetlist[i] = uint64(binary.BigEndian.Uint32(buf[idx:])) + idx += 4 + } + offset += idx + return } func (stco *ChunkOffsetBox) Encode() (int, []byte) { - stco.box.Box.Size = stco.Size() - offset, buf := stco.box.Encode() - binary.BigEndian.PutUint32(buf[offset:], stco.stco.entryCount) - offset += 4 - for i := 0; i < int(stco.stco.entryCount); i++ { - binary.BigEndian.PutUint32(buf[offset:], uint32(stco.stco.chunkOffsetlist[i])) - offset += 4 - } - return offset, buf + stco.box.Box.Size = stco.Size() + offset, buf := stco.box.Encode() + binary.BigEndian.PutUint32(buf[offset:], stco.stco.entryCount) + offset += 4 + for i := 0; i < int(stco.stco.entryCount); i++ { + binary.BigEndian.PutUint32(buf[offset:], uint32(stco.stco.chunkOffsetlist[i])) + offset += 4 + } + return offset, buf } type ChunkLargeOffsetBox struct { - box *FullBox - stco *movstco + box *FullBox + stco *movstco } func NewChunkLargeOffsetBox() *ChunkLargeOffsetBox { - return &ChunkLargeOffsetBox{ - box: NewFullBox([4]byte{'c', 'o', '6', '4'}, 4), - } + return &ChunkLargeOffsetBox{ + box: NewFullBox([4]byte{'c', 'o', '6', '4'}, 0), + } } func (co64 *ChunkLargeOffsetBox) Size() uint64 { - if co64.stco == nil { - return co64.box.Size() - } else { - return co64.box.Size() + 4 + 8*uint64(co64.stco.entryCount) - } + if co64.stco == nil { + return co64.box.Size() + } else { + return co64.box.Size() + 4 + 8*uint64(co64.stco.entryCount) + } } func (co64 *ChunkLargeOffsetBox) Decode(r io.Reader) (offset int, err error) { - if _, err = co64.box.Decode(r); err != nil { - return - } - tmp := make([]byte, 4) - if _, err = io.ReadFull(r, tmp); err != nil { - return - } - offset = 8 - co64.stco = new(movstco) - co64.stco.entryCount = binary.BigEndian.Uint32(tmp) - co64.stco.chunkOffsetlist = make([]uint64, co64.stco.entryCount) - buf := make([]byte, co64.stco.entryCount*8) - if _, err = io.ReadFull(r, buf); err != nil { - return - } - idx := 0 - for i := 0; i < int(co64.stco.entryCount); i++ { - co64.stco.chunkOffsetlist[i] = binary.BigEndian.Uint64(buf[idx:]) - idx += 8 - } - offset += idx - return + if _, err = co64.box.Decode(r); err != nil { + return + } + tmp := make([]byte, 4) + if _, err = io.ReadFull(r, tmp); err != nil { + return + } + offset = 8 + co64.stco = new(movstco) + co64.stco.entryCount = binary.BigEndian.Uint32(tmp) + co64.stco.chunkOffsetlist = make([]uint64, co64.stco.entryCount) + buf := make([]byte, co64.stco.entryCount*8) + if _, err = io.ReadFull(r, buf); err != nil { + return + } + idx := 0 + for i := 0; i < int(co64.stco.entryCount); i++ { + co64.stco.chunkOffsetlist[i] = binary.BigEndian.Uint64(buf[idx:]) + idx += 8 + } + offset += idx + return } func (co64 *ChunkLargeOffsetBox) Encode() (int, []byte) { - co64.box.Box.Size = co64.Size() - offset, buf := co64.box.Encode() - binary.BigEndian.PutUint32(buf[offset:], co64.stco.entryCount) - offset += 4 - for i := 0; i < int(co64.stco.entryCount); i++ { - binary.BigEndian.PutUint64(buf[offset:], co64.stco.chunkOffsetlist[i]) - offset += 8 - } - return offset, buf + co64.box.Box.Size = co64.Size() + offset, buf := co64.box.Encode() + binary.BigEndian.PutUint32(buf[offset:], co64.stco.entryCount) + offset += 4 + for i := 0; i < int(co64.stco.entryCount); i++ { + binary.BigEndian.PutUint64(buf[offset:], co64.stco.chunkOffsetlist[i]) + offset += 8 + } + return offset, buf } func makeStco(stco *movstco) (boxdata []byte) { - if stco.entryCount > 0 && stco.chunkOffsetlist[stco.entryCount-1] > 0xFFFFFFFF { - co64 := NewChunkLargeOffsetBox() - co64.stco = stco - _, boxdata = co64.Encode() - } else { - stcobox := NewChunkOffsetBox() - stcobox.stco = stco - _, boxdata = stcobox.Encode() - } - return + if stco.entryCount > 0 && stco.chunkOffsetlist[stco.entryCount-1] > 0xFFFFFFFF { + co64 := NewChunkLargeOffsetBox() + co64.stco = stco + _, boxdata = co64.Encode() + } else { + stcobox := NewChunkOffsetBox() + stcobox.stco = stco + _, boxdata = stcobox.Encode() + } + return } func decodeStcoBox(demuxer *MovDemuxer) (err error) { - stco := ChunkOffsetBox{box: new(FullBox)} - if _, err = stco.Decode(demuxer.reader); err != nil { - return - } - track := demuxer.tracks[len(demuxer.tracks)-1] - track.stbltable.stco = stco.stco - return + stco := ChunkOffsetBox{box: new(FullBox)} + if _, err = stco.Decode(demuxer.reader); err != nil { + return + } + track := demuxer.tracks[len(demuxer.tracks)-1] + track.stbltable.stco = stco.stco + return } func decodeCo64Box(demuxer *MovDemuxer) (err error) { - co64 := ChunkLargeOffsetBox{box: new(FullBox)} - if _, err = co64.Decode(demuxer.reader); err != nil { - return - } - track := demuxer.tracks[len(demuxer.tracks)-1] - track.stbltable.stco = co64.stco - return + co64 := ChunkLargeOffsetBox{box: new(FullBox)} + if _, err = co64.Decode(demuxer.reader); err != nil { + return + } + track := demuxer.tracks[len(demuxer.tracks)-1] + track.stbltable.stco = co64.stco + return } From 1f151476aea8a89ebdd136ae1c051ef9a4072652 Mon Sep 17 00:00:00 2001 From: Tim Mattison Date: Sat, 18 May 2024 09:13:28 -0400 Subject: [PATCH 2/2] Added H264VuiParameters and left out problematic fields --- go-codec/h264.go | 698 +++++++++++++++++++++------------- go-mp4/mp4track.go | 928 ++++++++++++++++++++++----------------------- 2 files changed, 899 insertions(+), 727 deletions(-) diff --git a/go-codec/h264.go b/go-codec/h264.go index 7fa3543..fe21b09 100644 --- a/go-codec/h264.go +++ b/go-codec/h264.go @@ -1,8 +1,8 @@ package codec import ( - "encoding/binary" - "errors" + "encoding/binary" + "errors" ) // nal_unit( NumBytesInNALunit ) { @@ -12,241 +12,253 @@ import ( // } type H264NaluHdr struct { - Forbidden_zero_bit uint8 - Nal_ref_idc uint8 - Nal_unit_type uint8 + Forbidden_zero_bit uint8 + Nal_ref_idc uint8 + Nal_unit_type uint8 } func (hdr *H264NaluHdr) Decode(bs *BitStream) { - hdr.Forbidden_zero_bit = bs.GetBit() - hdr.Nal_ref_idc = bs.Uint8(2) - hdr.Nal_unit_type = bs.Uint8(5) + hdr.Forbidden_zero_bit = bs.GetBit() + hdr.Nal_ref_idc = bs.Uint8(2) + hdr.Nal_unit_type = bs.Uint8(5) } type SliceHeader struct { - First_mb_in_slice uint64 - Slice_type uint64 - Pic_parameter_set_id uint64 - Frame_num uint64 + First_mb_in_slice uint64 + Slice_type uint64 + Pic_parameter_set_id uint64 + Frame_num uint64 } -//调用方根据sps中的log2_max_frame_num_minus4的值来解析Frame_num +// 调用方根据sps中的log2_max_frame_num_minus4的值来解析Frame_num func (sh *SliceHeader) Decode(bs *BitStream) { - sh.First_mb_in_slice = bs.ReadUE() - sh.Slice_type = bs.ReadUE() - sh.Pic_parameter_set_id = bs.ReadUE() + sh.First_mb_in_slice = bs.ReadUE() + sh.Slice_type = bs.ReadUE() + sh.Pic_parameter_set_id = bs.ReadUE() } type SPS struct { - Profile_idc uint8 - Constraint_set0_flag uint8 - Constraint_set1_flag uint8 - Constraint_set2_flag uint8 - Constraint_set3_flag uint8 - Constraint_set4_flag uint8 - Constraint_set5_flag uint8 - Reserved_zero_2bits uint8 - Level_idc uint8 - Seq_parameter_set_id uint64 - Chroma_format_idc uint64 - Separate_colour_plane_flag uint8 - Bit_depth_luma_minus8 uint64 - Bit_depth_chroma_minus8 uint64 - Log2_max_frame_num_minus4 uint64 - Pic_order_cnt_type uint64 - Max_num_ref_frames uint64 - Gaps_in_frame_num_value_allowed_flag uint8 - Pic_width_in_mbs_minus1 uint64 - Pic_height_in_map_units_minus1 uint64 - Frame_mbs_only_flag uint8 - Direct_8x8_inference_flag uint8 - Frame_cropping_flag uint8 - Frame_crop_left_offset uint64 - Frame_crop_right_offset uint64 - Frame_crop_top_offset uint64 - Frame_crop_bottom_offset uint64 - Vui_parameters_present_flag uint8 + Profile_idc uint8 + Constraint_set0_flag uint8 + Constraint_set1_flag uint8 + Constraint_set2_flag uint8 + Constraint_set3_flag uint8 + Constraint_set4_flag uint8 + Constraint_set5_flag uint8 + Reserved_zero_2bits uint8 + Level_idc uint8 + Seq_parameter_set_id uint64 + Chroma_format_idc uint64 + Separate_colour_plane_flag uint8 + Bit_depth_luma_minus8 uint64 + Bit_depth_chroma_minus8 uint64 + Log2_max_frame_num_minus4 uint64 + Pic_order_cnt_type uint64 + Log2_max_pic_order_cnt_lsb_minus4 uint64 + Delta_pic_order_always_zero_flag uint8 + Offset_for_non_ref_pic int64 + Offset_for_top_to_bottom_field int64 + Offset_for_ref_frame []int64 + Max_num_ref_frames uint64 + Gaps_in_frame_num_value_allowed_flag uint8 + Pic_width_in_mbs_minus1 uint64 + Pic_height_in_map_units_minus1 uint64 + Frame_mbs_only_flag uint8 + Mb_adaptive_frame_field_flag uint8 + Direct_8x8_inference_flag uint8 + Frame_cropping_flag uint8 + Frame_crop_left_offset uint64 + Frame_crop_right_offset uint64 + Frame_crop_top_offset uint64 + Frame_crop_bottom_offset uint64 + Vui_parameters_present_flag uint8 + VuiParameters H264VuiParameters } func (sps *SPS) Decode(bs *BitStream) { - sps.Profile_idc = bs.Uint8(8) - sps.Constraint_set0_flag = bs.GetBit() - sps.Constraint_set1_flag = bs.GetBit() - sps.Constraint_set2_flag = bs.GetBit() - sps.Constraint_set3_flag = bs.GetBit() - sps.Constraint_set4_flag = bs.GetBit() - sps.Constraint_set5_flag = bs.GetBit() - sps.Reserved_zero_2bits = bs.Uint8(2) - sps.Level_idc = bs.Uint8(8) - sps.Seq_parameter_set_id = bs.ReadUE() - if sps.Profile_idc == 100 || sps.Profile_idc == 110 || - sps.Profile_idc == 122 || sps.Profile_idc == 244 || - sps.Profile_idc == 44 || sps.Profile_idc == 83 || - sps.Profile_idc == 86 || sps.Profile_idc == 118 || sps.Profile_idc == 128 { - sps.Chroma_format_idc = bs.ReadUE() - if sps.Chroma_format_idc == 3 { - sps.Separate_colour_plane_flag = bs.Uint8(1) //separate_colour_plane_flag - } - sps.Bit_depth_luma_minus8 = bs.ReadUE() //bit_depth_luma_minus8 - sps.Bit_depth_chroma_minus8 = bs.ReadUE() //bit_depth_chroma_minus8 - bs.SkipBits(1) //qpprime_y_zero_transform_bypass_flag - seq_scaling_matrix_present_flag := bs.GetBit() - if seq_scaling_matrix_present_flag == 1 { - //seq_scaling_list_present_flag[i] - if sps.Chroma_format_idc == 3 { - bs.SkipBits(12) - } else { - bs.SkipBits(8) - } - } - } - sps.Log2_max_frame_num_minus4 = bs.ReadUE() - sps.Pic_order_cnt_type = bs.ReadUE() - if sps.Pic_order_cnt_type == 0 { - bs.ReadUE() // log2_max_pic_order_cnt_lsb_minus4 - } else if sps.Pic_order_cnt_type == 1 { - bs.SkipBits(1) //delta_pic_order_always_zero_flag - bs.ReadSE() //offset_for_non_ref_pic - bs.ReadSE() //offset_for_top_to_bottom_field - num_ref_frames_in_pic_order_cnt_cycle := bs.ReadUE() - for i := 0; i < int(num_ref_frames_in_pic_order_cnt_cycle); i++ { - bs.ReadSE() //offset_for_ref_frame - } - } - sps.Max_num_ref_frames = bs.ReadUE() - sps.Gaps_in_frame_num_value_allowed_flag = bs.GetBit() - sps.Pic_width_in_mbs_minus1 = bs.ReadUE() - sps.Pic_height_in_map_units_minus1 = bs.ReadUE() - sps.Frame_mbs_only_flag = bs.GetBit() - if sps.Frame_mbs_only_flag == 0 { - bs.SkipBits(1) // mb_adaptive_frame_field_flag - } - sps.Direct_8x8_inference_flag = bs.GetBit() - sps.Frame_cropping_flag = bs.GetBit() - if sps.Frame_cropping_flag == 1 { - sps.Frame_crop_left_offset = bs.ReadUE() //frame_crop_left_offset - sps.Frame_crop_right_offset = bs.ReadUE() //frame_crop_right_offset - sps.Frame_crop_top_offset = bs.ReadUE() //frame_crop_top_offset - sps.Frame_crop_bottom_offset = bs.ReadUE() //frame_crop_bottom_offset - } - sps.Vui_parameters_present_flag = bs.GetBit() + sps.Profile_idc = bs.Uint8(8) + sps.Constraint_set0_flag = bs.GetBit() + sps.Constraint_set1_flag = bs.GetBit() + sps.Constraint_set2_flag = bs.GetBit() + sps.Constraint_set3_flag = bs.GetBit() + sps.Constraint_set4_flag = bs.GetBit() + sps.Constraint_set5_flag = bs.GetBit() + sps.Reserved_zero_2bits = bs.Uint8(2) + sps.Level_idc = bs.Uint8(8) + sps.Seq_parameter_set_id = bs.ReadUE() + if sps.Profile_idc == 100 || sps.Profile_idc == 110 || + sps.Profile_idc == 122 || sps.Profile_idc == 244 || sps.Profile_idc == 44 || + sps.Profile_idc == 83 || sps.Profile_idc == 86 || sps.Profile_idc == 118 || + sps.Profile_idc == 128 || sps.Profile_idc == 138 || sps.Profile_idc == 139 || + sps.Profile_idc == 134 || sps.Profile_idc == 135 { + sps.Chroma_format_idc = bs.ReadUE() + if sps.Chroma_format_idc == 3 { + sps.Separate_colour_plane_flag = bs.Uint8(1) //separate_colour_plane_flag + } + sps.Bit_depth_luma_minus8 = bs.ReadUE() //bit_depth_luma_minus8 + sps.Bit_depth_chroma_minus8 = bs.ReadUE() //bit_depth_chroma_minus8 + bs.SkipBits(1) //qpprime_y_zero_transform_bypass_flag + seq_scaling_matrix_present_flag := bs.GetBit() + if seq_scaling_matrix_present_flag == 1 { + //seq_scaling_list_present_flag[i] + if sps.Chroma_format_idc == 3 { + bs.SkipBits(12) + } else { + bs.SkipBits(8) + } + } + } + sps.Log2_max_frame_num_minus4 = bs.ReadUE() + sps.Pic_order_cnt_type = bs.ReadUE() + if sps.Pic_order_cnt_type == 0 { + sps.Log2_max_pic_order_cnt_lsb_minus4 = bs.ReadUE() + } else if sps.Pic_order_cnt_type == 1 { + sps.Delta_pic_order_always_zero_flag = bs.GetBit() + sps.Offset_for_non_ref_pic = bs.ReadSE() // offset_for_non_ref_pic + sps.Offset_for_top_to_bottom_field = bs.ReadSE() // offset_for_top_to_bottom_field + num_ref_frames_in_pic_order_cnt_cycle := bs.ReadUE() + for i := 0; i < int(num_ref_frames_in_pic_order_cnt_cycle); i++ { + sps.Offset_for_ref_frame[i] = bs.ReadSE() // offset_for_ref_frame + } + } + sps.Max_num_ref_frames = bs.ReadUE() + sps.Gaps_in_frame_num_value_allowed_flag = bs.GetBit() + sps.Pic_width_in_mbs_minus1 = bs.ReadUE() + sps.Pic_height_in_map_units_minus1 = bs.ReadUE() + sps.Frame_mbs_only_flag = bs.GetBit() + if sps.Frame_mbs_only_flag == 0 { + sps.Mb_adaptive_frame_field_flag = bs.GetBit() + } + sps.Direct_8x8_inference_flag = bs.GetBit() + sps.Frame_cropping_flag = bs.GetBit() + if sps.Frame_cropping_flag == 1 { + sps.Frame_crop_left_offset = bs.ReadUE() //frame_crop_left_offset + sps.Frame_crop_right_offset = bs.ReadUE() //frame_crop_right_offset + sps.Frame_crop_top_offset = bs.ReadUE() //frame_crop_top_offset + sps.Frame_crop_bottom_offset = bs.ReadUE() //frame_crop_bottom_offset + } + sps.Vui_parameters_present_flag = bs.GetBit() + + if sps.Vui_parameters_present_flag == 1 { + sps.VuiParameters.Decode(bs) + } } type PPS struct { - Pic_parameter_set_id uint64 - Seq_parameter_set_id uint64 - Entropy_coding_mode_flag uint8 - Bottom_field_pic_order_in_frame_present_flag uint8 - Num_slice_groups_minus1 uint64 + Pic_parameter_set_id uint64 + Seq_parameter_set_id uint64 + Entropy_coding_mode_flag uint8 + Bottom_field_pic_order_in_frame_present_flag uint8 + Num_slice_groups_minus1 uint64 } func (pps *PPS) Decode(bs *BitStream) { - pps.Pic_parameter_set_id = bs.ReadUE() - pps.Seq_parameter_set_id = bs.ReadUE() - pps.Entropy_coding_mode_flag = bs.GetBit() - pps.Bottom_field_pic_order_in_frame_present_flag = bs.GetBit() - pps.Num_slice_groups_minus1 = bs.ReadUE() + pps.Pic_parameter_set_id = bs.ReadUE() + pps.Seq_parameter_set_id = bs.ReadUE() + pps.Entropy_coding_mode_flag = bs.GetBit() + pps.Bottom_field_pic_order_in_frame_present_flag = bs.GetBit() + pps.Num_slice_groups_minus1 = bs.ReadUE() } type SEIReaderWriter interface { - Read(size uint16, bs *BitStream) - Write(bsw *BitStreamWriter) + Read(size uint16, bs *BitStream) + Write(bsw *BitStreamWriter) } type UserDataUnregistered struct { - UUID []byte - UserData []byte + UUID []byte + UserData []byte } func (udu *UserDataUnregistered) Read(size uint16, bs *BitStream) { - udu.UUID = bs.GetBytes(16) - udu.UserData = bs.GetBytes(int(size - 16)) + udu.UUID = bs.GetBytes(16) + udu.UserData = bs.GetBytes(int(size - 16)) } func (udu *UserDataUnregistered) Write(bsw *BitStreamWriter) { - bsw.PutBytes(udu.UUID) - bsw.PutBytes(udu.UserData) + bsw.PutBytes(udu.UUID) + bsw.PutBytes(udu.UserData) } type SEI struct { - PayloadType uint16 - PayloadSize uint16 - Sei_payload SEIReaderWriter + PayloadType uint16 + PayloadSize uint16 + Sei_payload SEIReaderWriter } func (sei *SEI) Decode(bs *BitStream) { - for bs.NextBits(8) == 0xFF { - sei.PayloadType += 255 - } - sei.PayloadType += uint16(bs.Uint8(8)) - for bs.NextBits(8) == 0xFF { - sei.PayloadSize += 255 - } - sei.PayloadSize += uint16(bs.Uint8(8)) - if sei.PayloadType == 5 { - sei.Sei_payload = new(UserDataUnregistered) - sei.Sei_payload.Read(sei.PayloadSize, bs) - } + for bs.NextBits(8) == 0xFF { + sei.PayloadType += 255 + } + sei.PayloadType += uint16(bs.Uint8(8)) + for bs.NextBits(8) == 0xFF { + sei.PayloadSize += 255 + } + sei.PayloadSize += uint16(bs.Uint8(8)) + if sei.PayloadType == 5 { + sei.Sei_payload = new(UserDataUnregistered) + sei.Sei_payload.Read(sei.PayloadSize, bs) + } } func (sei *SEI) Encode(bsw *BitStreamWriter) []byte { - payloadType := sei.PayloadType - payloadSize := sei.PayloadSize - for payloadType >= 0xFF { - bsw.PutByte(0xFF) - payloadType -= 255 - } - bsw.PutByte(uint8(payloadType)) - for payloadSize >= 0xFF { - bsw.PutByte(0xFF) - payloadSize -= 255 - } - bsw.PutByte(uint8(payloadSize)) - sei.Sei_payload.Write(bsw) - return bsw.Bits() + payloadType := sei.PayloadType + payloadSize := sei.PayloadSize + for payloadType >= 0xFF { + bsw.PutByte(0xFF) + payloadType -= 255 + } + bsw.PutByte(uint8(payloadType)) + for payloadSize >= 0xFF { + bsw.PutByte(0xFF) + payloadSize -= 255 + } + bsw.PutByte(uint8(payloadSize)) + sei.Sei_payload.Write(bsw) + return bsw.Bits() } func GetSPSIdWithStartCode(sps []byte) uint64 { - start, sc := FindStartCode(sps, 0) - return GetSPSId(sps[start+int(sc):]) + start, sc := FindStartCode(sps, 0) + return GetSPSId(sps[start+int(sc):]) } func GetSPSId(sps []byte) uint64 { - sps = sps[1:] - bs := NewBitStream(sps) - bs.SkipBits(24) - return bs.ReadUE() + sps = sps[1:] + bs := NewBitStream(sps) + bs.SkipBits(24) + return bs.ReadUE() } func GetPPSIdWithStartCode(pps []byte) uint64 { - start, sc := FindStartCode(pps, 0) - return GetPPSId(pps[start+int(sc):]) + start, sc := FindStartCode(pps, 0) + return GetPPSId(pps[start+int(sc):]) } func GetPPSId(pps []byte) uint64 { - pps = pps[1:] - bs := NewBitStream(pps) - return bs.ReadUE() + pps = pps[1:] + bs := NewBitStream(pps) + return bs.ReadUE() } -//https://stackoverflow.com/questions/12018535/get-the-width-height-of-the-video-from-h-264-nalu -//int Width = ((pic_width_in_mbs_minus1 +1)*16) - frame_crop_right_offset *2 - frame_crop_left_offset *2; -//int Height = ((2 - frame_mbs_only_flag)* (pic_height_in_map_units_minus1 +1) * 16) - (frame_crop_bottom_offset* 2) - (frame_crop_top_offset* 2); +// https://stackoverflow.com/questions/12018535/get-the-width-height-of-the-video-from-h-264-nalu +// int Width = ((pic_width_in_mbs_minus1 +1)*16) - frame_crop_right_offset *2 - frame_crop_left_offset *2; +// int Height = ((2 - frame_mbs_only_flag)* (pic_height_in_map_units_minus1 +1) * 16) - (frame_crop_bottom_offset* 2) - (frame_crop_top_offset* 2); func GetH264Resolution(sps []byte) (width uint32, height uint32) { - start, sc := FindStartCode(sps, 0) - bs := NewBitStream(sps[start+int(sc)+1:]) - var s SPS - s.Decode(bs) + start, sc := FindStartCode(sps, 0) + bs := NewBitStream(sps[start+int(sc)+1:]) + var s SPS + s.Decode(bs) - widthInSample := (uint32(s.Pic_width_in_mbs_minus1) + 1) * 16 - widthCrop := uint32(s.Frame_crop_left_offset)*2 + uint32(s.Frame_crop_right_offset)*2 - width = widthInSample - widthCrop + widthInSample := (uint32(s.Pic_width_in_mbs_minus1) + 1) * 16 + widthCrop := uint32(s.Frame_crop_left_offset)*2 + uint32(s.Frame_crop_right_offset)*2 + width = widthInSample - widthCrop - heightInSample := ((2 - uint32(s.Frame_mbs_only_flag)) * (uint32(s.Pic_height_in_map_units_minus1) + 1) * 16) - heightCrop := uint32(s.Frame_crop_bottom_offset)*2 - uint32(s.Frame_crop_top_offset)*2 - height = heightInSample - heightCrop + heightInSample := ((2 - uint32(s.Frame_mbs_only_flag)) * (uint32(s.Pic_height_in_map_units_minus1) + 1) * 16) + heightCrop := uint32(s.Frame_crop_bottom_offset)*2 - uint32(s.Frame_crop_top_offset)*2 + height = heightInSample - heightCrop - return + return } // aligned(8) class AVCDecoderConfigurationRecord { @@ -306,99 +318,259 @@ func GetH264Resolution(sps []byte) (width uint32, height uint32) { func CreateH264AVCCExtradata(spss [][]byte, ppss [][]byte) ([]byte, error) { - if len(spss) == 0 || len(ppss) == 0 { - return nil, errors.New("lack of sps or pps") - } - - extradata := make([]byte, 6, 256) - for i, sps := range spss { - start, sc := FindStartCode(sps, 0) - spss[i] = sps[start+int(sc):] - } - - for i, pps := range ppss { - start, sc := FindStartCode(pps, 0) - ppss[i] = pps[start+int(sc):] - } - - extradata[0] = 0x01 - extradata[1] = spss[0][1] - extradata[2] = spss[0][2] - extradata[3] = spss[0][3] - extradata[4] = 0xFF - extradata[5] = 0xE0 | uint8(len(spss)) - for _, sps := range spss { - spssize := make([]byte, 2) - binary.BigEndian.PutUint16(spssize, uint16(len(sps))) - extradata = append(extradata, spssize...) - extradata = append(extradata, sps...) - } - extradata = append(extradata, uint8(len(ppss))) - for _, pps := range ppss { - ppssize := make([]byte, 2) - binary.BigEndian.PutUint16(ppssize, uint16(len(pps))) - extradata = append(extradata, ppssize...) - extradata = append(extradata, pps...) - } - var h264sps SPS - h264sps.Decode(NewBitStream(spss[0][1:])) - if h264sps.Profile_idc == 100 || - h264sps.Profile_idc == 110 || - h264sps.Profile_idc == 122 || - h264sps.Profile_idc == 144 { - tmp := make([]byte, 4) - tmp[0] = 0xFC | uint8(h264sps.Chroma_format_idc&0x03) - tmp[1] = 0xF8 | uint8(h264sps.Bit_depth_luma_minus8&0x07) - tmp[2] = 0xF8 | uint8(h264sps.Bit_depth_chroma_minus8&0x07) - tmp[3] = 0 - extradata = append(extradata, tmp...) - } - - return extradata, nil + if len(spss) == 0 || len(ppss) == 0 { + return nil, errors.New("lack of sps or pps") + } + + extradata := make([]byte, 6, 256) + for i, sps := range spss { + start, sc := FindStartCode(sps, 0) + spss[i] = sps[start+int(sc):] + } + + for i, pps := range ppss { + start, sc := FindStartCode(pps, 0) + ppss[i] = pps[start+int(sc):] + } + + extradata[0] = 0x01 + extradata[1] = spss[0][1] + extradata[2] = spss[0][2] + extradata[3] = spss[0][3] + extradata[4] = 0xFF + extradata[5] = 0xE0 | uint8(len(spss)) + for _, sps := range spss { + spssize := make([]byte, 2) + binary.BigEndian.PutUint16(spssize, uint16(len(sps))) + extradata = append(extradata, spssize...) + extradata = append(extradata, sps...) + } + extradata = append(extradata, uint8(len(ppss))) + for _, pps := range ppss { + ppssize := make([]byte, 2) + binary.BigEndian.PutUint16(ppssize, uint16(len(pps))) + extradata = append(extradata, ppssize...) + extradata = append(extradata, pps...) + } + var h264sps SPS + h264sps.Decode(NewBitStream(spss[0][1:])) + if h264sps.Profile_idc == 100 || + h264sps.Profile_idc == 110 || + h264sps.Profile_idc == 122 || + h264sps.Profile_idc == 144 { + tmp := make([]byte, 4) + tmp[0] = 0xFC | uint8(h264sps.Chroma_format_idc&0x03) + tmp[1] = 0xF8 | uint8(h264sps.Bit_depth_luma_minus8&0x07) + tmp[2] = 0xF8 | uint8(h264sps.Bit_depth_chroma_minus8&0x07) + tmp[3] = 0 + extradata = append(extradata, tmp...) + } + + return extradata, nil } func CovertExtradata(extraData []byte) ([][]byte, [][]byte) { - spsnum := extraData[5] & 0x1F - spss := make([][]byte, spsnum) - offset := 6 - for i := 0; i < int(spsnum); i++ { - spssize := binary.BigEndian.Uint16(extraData[offset:]) - sps := make([]byte, spssize+4) - copy(sps, []byte{0x00, 0x00, 0x00, 0x01}) - copy(sps[4:], extraData[offset+2:offset+2+int(spssize)]) - offset += 2 + int(spssize) - spss[i] = sps - } - ppsnum := extraData[offset] - ppss := make([][]byte, ppsnum) - offset++ - for i := 0; i < int(ppsnum); i++ { - ppssize := binary.BigEndian.Uint16(extraData[offset:]) - pps := make([]byte, ppssize+4) - copy(pps, []byte{0x00, 0x00, 0x00, 0x01}) - copy(pps[4:], extraData[offset+2:offset+2+int(ppssize)]) - offset += 2 + int(ppssize) - ppss[i] = pps - } - return spss, ppss + spsnum := extraData[5] & 0x1F + spss := make([][]byte, spsnum) + offset := 6 + for i := 0; i < int(spsnum); i++ { + spssize := binary.BigEndian.Uint16(extraData[offset:]) + sps := make([]byte, spssize+4) + copy(sps, []byte{0x00, 0x00, 0x00, 0x01}) + copy(sps[4:], extraData[offset+2:offset+2+int(spssize)]) + offset += 2 + int(spssize) + spss[i] = sps + } + ppsnum := extraData[offset] + ppss := make([][]byte, ppsnum) + offset++ + for i := 0; i < int(ppsnum); i++ { + ppssize := binary.BigEndian.Uint16(extraData[offset:]) + pps := make([]byte, ppssize+4) + copy(pps, []byte{0x00, 0x00, 0x00, 0x01}) + copy(pps[4:], extraData[offset+2:offset+2+int(ppssize)]) + offset += 2 + int(ppssize) + ppss[i] = pps + } + return spss, ppss } func ConvertAnnexBToAVCC(annexb []byte) []byte { - start, sc := FindStartCode(annexb, 0) - if sc == START_CODE_4 { - binary.BigEndian.PutUint32(annexb[start:], uint32(len(annexb)-4)) - return annexb - } else { - avcc := make([]byte, 1+len(annexb)) - binary.BigEndian.PutUint32(avcc, uint32(len(annexb)-3)) - copy(avcc[4:], annexb[start+3:]) - return avcc - } + start, sc := FindStartCode(annexb, 0) + if sc == START_CODE_4 { + binary.BigEndian.PutUint32(annexb[start:], uint32(len(annexb)-4)) + return annexb + } else { + avcc := make([]byte, 1+len(annexb)) + binary.BigEndian.PutUint32(avcc, uint32(len(annexb)-3)) + copy(avcc[4:], annexb[start+3:]) + return avcc + } } func CovertAVCCToAnnexB(avcc []byte) { - avcc[0] = 0x00 - avcc[1] = 0x00 - avcc[2] = 0x00 - avcc[3] = 0x01 + avcc[0] = 0x00 + avcc[1] = 0x00 + avcc[2] = 0x00 + avcc[3] = 0x01 +} + +// H264VuiParameters +// From ITU-T - https://www.itu.int/ITU-T/recommendations/rec.aspx?id=14659 +// +// Version 14 - https://www.itu.int/rec/T-REC-H.264-202108-I +// Page 422 - E.1.1 VUI parameters syntax +type H264VuiParameters struct { + AspectRatioInfoPresentFlag uint8 // u(1) + AspectRatioIdc uint8 // u(8) + SarWidth uint16 // u(16) + SarHeight uint16 // u(16) + OverscanInfoPresentFlag uint8 // u(1) + OverscanAppropriateFlag uint8 // u(1) + VideoSignalTypePresentFlag uint8 // u(1) + VideoFormat uint8 // u(3) + VideoFullRangeFlag uint8 // u(1) + ColourDescriptionPresentFlag uint8 // u(1) + ColourPrimaries uint8 // u(8) + TransferCharacteristics uint8 // u(8) + MatrixCoefficients uint8 // u(8) + ChromaLocInfoPresentFlag uint8 // u(1) + ChromaSampleLocTypeTopField uint64 // ue(v) + ChromaSampleLocTypeBottomField uint64 // ue(v) + TimingInfoPresentFlag uint8 // u(1) + NumUnitsInTick uint32 // u(32) + TimeScale uint32 // u(32) + FixedFrameRateFlag uint8 // u(1) + NalHrdParametersPresentFlag uint8 // u(1) + NalHrdParameters H264HrdParameters + VclHrdParametersPresentFlag uint8 // u(1) + VclHrdParameters H264HrdParameters + LowDelayHrdFlag uint8 // u(1) + PicStructPresentFlag uint8 // u(1) + BitstreamRestrictionFlag uint8 // u(1) + MotionVectorsOverPicBoundaries uint8 // u(1) + MaxBytesPerPicDenom uint64 // ue(v) + MaxBitsPerMbDenom uint64 // ue(v) + Log2MaxMvLengthHorizontal uint64 // ue(v) + Log2MaxMvLengthVertical uint64 // ue(v) + NumReorderFrames uint64 // ue(v) + MaxDecFrameBuffering uint64 // ue(v) +} + +type H264HrdParameters struct { + CpbCntMinus1 uint64 // ue(v) + BitRateScale uint8 // u(4) + CpbSizeScale uint8 // u(4) + H264BitRateCpbSizeCbrFlag []H264BitRateCpbSizeCbrFlag // 0..cpb_cnt_minus1 + InitialCpbRemovalDelayLengthMinus1 uint8 // u(5) + CpbRemovalDelayLengthMinus1 uint8 // u(5) + DpbOutputDelayLengthMinus1 uint8 // u(5) + TimeOffsetLength uint8 // u(5) +} + +type H264BitRateCpbSizeCbrFlag struct { + BitRateValueMinus1 uint64 // ue(v) + CpbSizeValueMinus1 uint64 // ue(v) + CbrFlag uint8 // u(1) +} + +const ExtendedSar = 255 + +func (h264Vui *H264VuiParameters) Decode(bs *BitStream) { + h264Vui.AspectRatioInfoPresentFlag = bs.Uint8(1) + + if h264Vui.AspectRatioInfoPresentFlag == 1 { + h264Vui.AspectRatioIdc = bs.Uint8(8) + + if h264Vui.AspectRatioIdc == ExtendedSar { + h264Vui.SarWidth = bs.Uint16(16) + h264Vui.SarWidth = bs.Uint16(16) + } + } + + h264Vui.OverscanInfoPresentFlag = bs.Uint8(1) + + if h264Vui.OverscanInfoPresentFlag == 1 { + h264Vui.OverscanAppropriateFlag = bs.Uint8(1) + } + + h264Vui.VideoSignalTypePresentFlag = bs.Uint8(1) + + if h264Vui.VideoSignalTypePresentFlag == 1 { + h264Vui.VideoFormat = bs.Uint8(3) + h264Vui.VideoFullRangeFlag = bs.Uint8(1) + h264Vui.ColourDescriptionPresentFlag = bs.Uint8(1) + + if h264Vui.ColourDescriptionPresentFlag == 1 { + h264Vui.ColourPrimaries = bs.Uint8(8) + h264Vui.TransferCharacteristics = bs.Uint8(8) + h264Vui.MatrixCoefficients = bs.Uint8(8) + } + } + + h264Vui.ChromaLocInfoPresentFlag = bs.Uint8(1) + + if h264Vui.ChromaLocInfoPresentFlag == 1 { + h264Vui.ChromaSampleLocTypeTopField = bs.ReadUE() + h264Vui.ChromaSampleLocTypeBottomField = bs.ReadUE() + } + + h264Vui.TimingInfoPresentFlag = bs.Uint8(1) + + if h264Vui.TimingInfoPresentFlag == 1 { + h264Vui.NumUnitsInTick = bs.Uint32(32) + h264Vui.TimeScale = bs.Uint32(32) + h264Vui.FixedFrameRateFlag = bs.Uint8(1) + } + + h264Vui.NalHrdParametersPresentFlag = bs.Uint8(1) + + if h264Vui.NalHrdParametersPresentFlag == 1 { + h264Vui.NalHrdParameters.Decode(bs) + } + + h264Vui.VclHrdParametersPresentFlag = bs.Uint8(1) + + if h264Vui.VclHrdParametersPresentFlag == 1 { + h264Vui.VclHrdParameters.Decode(bs) + } + + if h264Vui.NalHrdParametersPresentFlag == 1 || h264Vui.VclHrdParametersPresentFlag == 1 { + h264Vui.LowDelayHrdFlag = bs.Uint8(1) + } + + /* + TODO - These fields were causing problems because we'd run out of bits when parsing. Maybe they're optional in certain versions/levels/configurations? + h264Vui.PicStructPresentFlag = bs.GetBit() + h264Vui.BitstreamRestrictionFlag = bs.GetBit() + + if h264Vui.BitstreamRestrictionFlag == 1 { + h264Vui.MotionVectorsOverPicBoundaries = bs.GetBit() + h264Vui.MaxBytesPerPicDenom = bs.ReadUE() + h264Vui.MaxBitsPerMbDenom = bs.ReadUE() + h264Vui.Log2MaxMvLengthHorizontal = bs.ReadUE() + h264Vui.Log2MaxMvLengthVertical = bs.ReadUE() + h264Vui.NumReorderFrames = bs.ReadUE() + h264Vui.MaxDecFrameBuffering = bs.ReadUE() + } + */ +} + +func (h264Hrd *H264HrdParameters) Decode(bs *BitStream) { + h264Hrd.CpbCntMinus1 = bs.ReadUE() + h264Hrd.BitRateScale = bs.Uint8(4) + h264Hrd.CpbSizeScale = bs.Uint8(4) + + h264Hrd.H264BitRateCpbSizeCbrFlag = make([]H264BitRateCpbSizeCbrFlag, h264Hrd.CpbCntMinus1+1) + + for i := 0; i <= int(h264Hrd.CpbCntMinus1); i++ { + h264Hrd.H264BitRateCpbSizeCbrFlag[i].BitRateValueMinus1 = bs.ReadUE() + h264Hrd.H264BitRateCpbSizeCbrFlag[i].CpbSizeValueMinus1 = bs.ReadUE() + h264Hrd.H264BitRateCpbSizeCbrFlag[i].CbrFlag = bs.Uint8(1) + } + + h264Hrd.InitialCpbRemovalDelayLengthMinus1 = bs.Uint8(5) + h264Hrd.CpbRemovalDelayLengthMinus1 = bs.Uint8(5) + h264Hrd.DpbOutputDelayLengthMinus1 = bs.Uint8(5) + //h264Hrd.TimeOffsetLength = bs.Uint8(5) } diff --git a/go-mp4/mp4track.go b/go-mp4/mp4track.go index e6af245..6ae1988 100644 --- a/go-mp4/mp4track.go +++ b/go-mp4/mp4track.go @@ -1,127 +1,127 @@ package mp4 import ( - "errors" - "io" + "errors" + "io" - "github.com/yapingcat/gomedia/go-codec" + "github.com/yapingcat/gomedia/go-codec" ) type sampleCache struct { - pts uint64 - dts uint64 - hasVcl bool - isKey bool - cache []byte + pts uint64 + dts uint64 + hasVcl bool + isKey bool + cache []byte } type sampleEntry struct { - pts uint64 - dts uint64 - offset uint64 - size uint64 - isKeyFrame bool - SampleDescriptionIndex uint32 //always should be 1 + pts uint64 + dts uint64 + offset uint64 + size uint64 + isKeyFrame bool + SampleDescriptionIndex uint32 //always should be 1 } type movchunk struct { - chunknum uint32 - samplenum uint32 - chunkoffset uint64 + chunknum uint32 + samplenum uint32 + chunkoffset uint64 } type extraData interface { - export() []byte - load(data []byte) + export() []byte + load(data []byte) } type h264ExtraData struct { - spss [][]byte - ppss [][]byte + spss [][]byte + ppss [][]byte } func (extra *h264ExtraData) export() []byte { - data, _ := codec.CreateH264AVCCExtradata(extra.spss, extra.ppss) - return data + data, _ := codec.CreateH264AVCCExtradata(extra.spss, extra.ppss) + return data } func (extra *h264ExtraData) load(data []byte) { - extra.spss, extra.ppss = codec.CovertExtradata(data) + extra.spss, extra.ppss = codec.CovertExtradata(data) } type h265ExtraData struct { - hvccExtra *codec.HEVCRecordConfiguration + hvccExtra *codec.HEVCRecordConfiguration } func newh265ExtraData() *h265ExtraData { - return &h265ExtraData{ - hvccExtra: codec.NewHEVCRecordConfiguration(), - } + return &h265ExtraData{ + hvccExtra: codec.NewHEVCRecordConfiguration(), + } } func (extra *h265ExtraData) export() []byte { - if extra.hvccExtra == nil { - panic("extra.hvccExtra must init") - } - data, _ := extra.hvccExtra.Encode() - return data + if extra.hvccExtra == nil { + panic("extra.hvccExtra must init") + } + data, _ := extra.hvccExtra.Encode() + return data } func (extra *h265ExtraData) load(data []byte) { - if extra.hvccExtra == nil { - panic("extra.hvccExtra must init") - } - extra.hvccExtra.Decode(data) + if extra.hvccExtra == nil { + panic("extra.hvccExtra must init") + } + extra.hvccExtra.Decode(data) } type aacExtraData struct { - asc []byte + asc []byte } func (extra *aacExtraData) export() []byte { - return extra.asc + return extra.asc } func (extra *aacExtraData) load(data []byte) { - extra.asc = make([]byte, len(data)) - copy(extra.asc, data) + extra.asc = make([]byte, len(data)) + copy(extra.asc, data) } type movFragment struct { - offset uint64 - duration uint32 - firstDts uint64 - firstPts uint64 - lastPts uint64 - lastDts uint64 + offset uint64 + duration uint32 + firstDts uint64 + firstPts uint64 + lastPts uint64 + lastDts uint64 } type mp4track struct { - cid MP4_CODEC_TYPE - trackId uint32 - stbltable *movstbl - duration uint32 - timescale uint32 - width uint32 - height uint32 - sampleRate uint32 - sampleBits uint8 - chanelCount uint8 - samplelist []sampleEntry - elst *movelst - extra extraData - lastSample *sampleCache - writer io.WriteSeeker - fragments []movFragment - - //for fmp4 - extraData []byte - startDts uint64 - startPts uint64 - defaultSize uint32 - defaultDuration uint32 - defaultSampleFlags uint32 - baseDataOffset uint64 + cid MP4_CODEC_TYPE + trackId uint32 + stbltable *movstbl + duration uint32 + timescale uint32 + width uint32 + height uint32 + sampleRate uint32 + sampleBits uint8 + chanelCount uint8 + samplelist []sampleEntry + elst *movelst + extra extraData + lastSample *sampleCache + writer io.WriteSeeker + fragments []movFragment + + //for fmp4 + extraData []byte + startDts uint64 + startPts uint64 + defaultSize uint32 + defaultDuration uint32 + defaultSampleFlags uint32 + baseDataOffset uint64 //for subsample defaultIsProtected uint8 @@ -135,432 +135,432 @@ type mp4track struct { } func newmp4track(cid MP4_CODEC_TYPE, writer io.WriteSeeker) *mp4track { - track := &mp4track{ - cid: cid, - timescale: 1000, - stbltable: nil, - samplelist: make([]sampleEntry, 0), - lastSample: &sampleCache{ - hasVcl: false, - cache: make([]byte, 0, 128), - }, - writer: writer, - fragments: make([]movFragment, 0, 32), - startDts: 0, - } - - if cid == MP4_CODEC_H264 { - track.extra = new(h264ExtraData) - } else if cid == MP4_CODEC_H265 { - track.extra = newh265ExtraData() - } else if cid == MP4_CODEC_AAC { - track.extra = new(aacExtraData) - } - return track + track := &mp4track{ + cid: cid, + timescale: 1000, + stbltable: nil, + samplelist: make([]sampleEntry, 0), + lastSample: &sampleCache{ + hasVcl: false, + cache: make([]byte, 0, 128), + }, + writer: writer, + fragments: make([]movFragment, 0, 32), + startDts: 0, + } + + if cid == MP4_CODEC_H264 { + track.extra = new(h264ExtraData) + } else if cid == MP4_CODEC_H265 { + track.extra = newh265ExtraData() + } else if cid == MP4_CODEC_AAC { + track.extra = new(aacExtraData) + } + return track } func (track *mp4track) addSampleEntry(entry sampleEntry) { - if len(track.samplelist) <= 1 { - track.duration = 0 - } else { - delta := int64(entry.dts - track.samplelist[len(track.samplelist)-1].dts) - if delta < 0 { - track.duration += 1 - } else { - track.duration += uint32(delta) - } - } - track.samplelist = append(track.samplelist, entry) + if len(track.samplelist) <= 1 { + track.duration = 0 + } else { + delta := int64(entry.dts - track.samplelist[len(track.samplelist)-1].dts) + if delta < 0 { + track.duration -= uint32(-delta) + } else { + track.duration += uint32(delta) + } + } + track.samplelist = append(track.samplelist, entry) } func (track *mp4track) makeStblTable() { - if track.stbltable == nil { - track.stbltable = new(movstbl) - } - sameSize := true - stts := new(movstts) - stts.entrys = make([]sttsEntry, 0) - movchunks := make([]movchunk, 0) - ctts := new(movctts) - ctts.entrys = make([]cttsEntry, 0) - ckn := uint32(0) - for i, sample := range track.samplelist { - sttsEntry := sttsEntry{sampleCount: 1, sampleDelta: 1} - cttsEntry := cttsEntry{sampleCount: 1, sampleOffset: uint32(sample.pts) - uint32(sample.dts)} - if i == len(track.samplelist)-1 { - stts.entrys = append(stts.entrys, sttsEntry) - stts.entryCount++ - } else { - var delta uint64 = 1 - if track.samplelist[i+1].dts >= sample.dts { - delta = track.samplelist[i+1].dts - sample.dts - } - - if len(stts.entrys) > 0 && delta == uint64(stts.entrys[len(stts.entrys)-1].sampleDelta) { - stts.entrys[len(stts.entrys)-1].sampleCount++ - } else { - sttsEntry.sampleDelta = uint32(delta) - stts.entrys = append(stts.entrys, sttsEntry) - stts.entryCount++ - } - } - - if len(ctts.entrys) == 0 { - ctts.entrys = append(ctts.entrys, cttsEntry) - ctts.entryCount++ - } else { - if ctts.entrys[len(ctts.entrys)-1].sampleOffset == cttsEntry.sampleOffset { - ctts.entrys[len(ctts.entrys)-1].sampleCount++ - } else { - ctts.entrys = append(ctts.entrys, cttsEntry) - ctts.entryCount++ - } - } - if sameSize && i < len(track.samplelist)-1 && track.samplelist[i+1].size != track.samplelist[i].size { - sameSize = false - } - if i > 0 && sample.offset == track.samplelist[i-1].offset+track.samplelist[i-1].size { - movchunks[ckn-1].samplenum++ - } else { - ck := movchunk{chunknum: ckn, samplenum: 1, chunkoffset: sample.offset} - movchunks = append(movchunks, ck) - ckn++ - } - } - stsz := &movstsz{ - sampleSize: 0, - sampleCount: uint32(len(track.samplelist)), - } - if sameSize { - stsz.sampleSize = uint32(track.samplelist[0].size) - } else { - stsz.entrySizelist = make([]uint32, stsz.sampleCount) - for i := 0; i < len(stsz.entrySizelist); i++ { - stsz.entrySizelist[i] = uint32(track.samplelist[i].size) - } - } - - stsc := &movstsc{ - entrys: make([]stscEntry, len(movchunks)), - entryCount: 0, - } - for i, chunk := range movchunks { - if i == 0 || chunk.samplenum != movchunks[i-1].samplenum { - stsc.entrys[stsc.entryCount].firstChunk = chunk.chunknum + 1 - stsc.entrys[stsc.entryCount].sampleDescriptionIndex = 1 - stsc.entrys[stsc.entryCount].samplesPerChunk = chunk.samplenum - stsc.entryCount++ - } - } - stco := &movstco{entryCount: ckn, chunkOffsetlist: make([]uint64, ckn)} - for i := 0; i < int(stco.entryCount); i++ { - stco.chunkOffsetlist[i] = movchunks[i].chunkoffset - } - track.stbltable.stts = stts - track.stbltable.stsc = stsc - track.stbltable.stco = stco - track.stbltable.stsz = stsz - if track.cid == MP4_CODEC_H264 || track.cid == MP4_CODEC_H265 { - track.stbltable.ctts = ctts - } + if track.stbltable == nil { + track.stbltable = new(movstbl) + } + sameSize := true + stts := new(movstts) + stts.entrys = make([]sttsEntry, 0) + movchunks := make([]movchunk, 0) + ctts := new(movctts) + ctts.entrys = make([]cttsEntry, 0) + ckn := uint32(0) + for i, sample := range track.samplelist { + sttsEntry := sttsEntry{sampleCount: 1, sampleDelta: 1} + cttsEntry := cttsEntry{sampleCount: 1, sampleOffset: uint32(sample.pts) - uint32(sample.dts)} + if i == len(track.samplelist)-1 { + stts.entrys = append(stts.entrys, sttsEntry) + stts.entryCount++ + } else { + var delta uint64 = 1 + if track.samplelist[i+1].dts >= sample.dts { + delta = track.samplelist[i+1].dts - sample.dts + } + + if len(stts.entrys) > 0 && delta == uint64(stts.entrys[len(stts.entrys)-1].sampleDelta) { + stts.entrys[len(stts.entrys)-1].sampleCount++ + } else { + sttsEntry.sampleDelta = uint32(delta) + stts.entrys = append(stts.entrys, sttsEntry) + stts.entryCount++ + } + } + + if len(ctts.entrys) == 0 { + ctts.entrys = append(ctts.entrys, cttsEntry) + ctts.entryCount++ + } else { + if ctts.entrys[len(ctts.entrys)-1].sampleOffset == cttsEntry.sampleOffset { + ctts.entrys[len(ctts.entrys)-1].sampleCount++ + } else { + ctts.entrys = append(ctts.entrys, cttsEntry) + ctts.entryCount++ + } + } + if sameSize && i < len(track.samplelist)-1 && track.samplelist[i+1].size != track.samplelist[i].size { + sameSize = false + } + if i > 0 && sample.offset == track.samplelist[i-1].offset+track.samplelist[i-1].size { + movchunks[ckn-1].samplenum++ + } else { + ck := movchunk{chunknum: ckn, samplenum: 1, chunkoffset: sample.offset} + movchunks = append(movchunks, ck) + ckn++ + } + } + stsz := &movstsz{ + sampleSize: 0, + sampleCount: uint32(len(track.samplelist)), + } + if sameSize { + stsz.sampleSize = uint32(track.samplelist[0].size) + } else { + stsz.entrySizelist = make([]uint32, stsz.sampleCount) + for i := 0; i < len(stsz.entrySizelist); i++ { + stsz.entrySizelist[i] = uint32(track.samplelist[i].size) + } + } + + stsc := &movstsc{ + entrys: make([]stscEntry, len(movchunks)), + entryCount: 0, + } + for i, chunk := range movchunks { + if i == 0 || chunk.samplenum != movchunks[i-1].samplenum { + stsc.entrys[stsc.entryCount].firstChunk = chunk.chunknum + 1 + stsc.entrys[stsc.entryCount].sampleDescriptionIndex = 1 + stsc.entrys[stsc.entryCount].samplesPerChunk = chunk.samplenum + stsc.entryCount++ + } + } + stco := &movstco{entryCount: ckn, chunkOffsetlist: make([]uint64, ckn)} + for i := 0; i < int(stco.entryCount); i++ { + stco.chunkOffsetlist[i] = movchunks[i].chunkoffset + } + track.stbltable.stts = stts + track.stbltable.stsc = stsc + track.stbltable.stco = stco + track.stbltable.stsz = stsz + if track.cid == MP4_CODEC_H264 || track.cid == MP4_CODEC_H265 { + track.stbltable.ctts = ctts + } } func (track *mp4track) makeEmptyStblTable() { - track.stbltable = new(movstbl) - track.stbltable.stts = &movstts{} - track.stbltable.stsc = &movstsc{} - track.stbltable.stco = &movstco{} - track.stbltable.stsz = &movstsz{} - track.stbltable.stss = &movstss{} + track.stbltable = new(movstbl) + track.stbltable.stts = &movstts{} + track.stbltable.stsc = &movstsc{} + track.stbltable.stco = &movstco{} + track.stbltable.stsz = &movstsz{} + track.stbltable.stss = &movstss{} } func (track *mp4track) writeSample(sample []byte, pts, dts uint64) (err error) { - switch track.cid { - case MP4_CODEC_H264: - err = track.writeH264(sample, pts, dts) - case MP4_CODEC_H265: - err = track.writeH265(sample, pts, dts) - case MP4_CODEC_AAC: - err = track.writeAAC(sample, pts, dts) - case MP4_CODEC_G711A, MP4_CODEC_G711U: - err = track.writeG711(sample, pts, dts) - case MP4_CODEC_MP2, MP4_CODEC_MP3: - err = track.writeMP3(sample, pts, dts) - case MP4_CODEC_OPUS: - err = track.writeOPUS(sample, pts, dts) - } - return err + switch track.cid { + case MP4_CODEC_H264: + err = track.writeH264(sample, pts, dts) + case MP4_CODEC_H265: + err = track.writeH265(sample, pts, dts) + case MP4_CODEC_AAC: + err = track.writeAAC(sample, pts, dts) + case MP4_CODEC_G711A, MP4_CODEC_G711U: + err = track.writeG711(sample, pts, dts) + case MP4_CODEC_MP2, MP4_CODEC_MP3: + err = track.writeMP3(sample, pts, dts) + case MP4_CODEC_OPUS: + err = track.writeOPUS(sample, pts, dts) + } + return err } func (track *mp4track) writeH264(h264 []byte, pts, dts uint64) (err error) { - h264extra, ok := track.extra.(*h264ExtraData) - if !ok { - panic("must init h264ExtraData first") - } - codec.SplitFrameWithStartCode(h264, func(nalu []byte) bool { - nalu_type := codec.H264NaluType(nalu) - switch nalu_type { - case codec.H264_NAL_SPS: - spsid := codec.GetSPSIdWithStartCode(nalu) - for _, sps := range h264extra.spss { - if spsid == codec.GetSPSIdWithStartCode(sps) { - return true - } - } - tmp := make([]byte, len(nalu)) - copy(tmp, nalu) - h264extra.spss = append(h264extra.spss, tmp) - if track.width == 0 || track.height == 0 { - width, height := codec.GetH264Resolution(h264extra.spss[0]) - if track.width == 0 { - track.width = width - } - if track.height == 0 { - track.height = height - } - } - case codec.H264_NAL_PPS: - ppsid := codec.GetPPSIdWithStartCode(nalu) - for _, pps := range h264extra.ppss { - if ppsid == codec.GetPPSIdWithStartCode(pps) { - return true - } - } - tmp := make([]byte, len(nalu)) - copy(tmp, nalu) - h264extra.ppss = append(h264extra.ppss, tmp) - } - //aud/sps/pps/sei 为帧间隔 - //通过first_slice_in_mb来判断,改nalu是否为一帧的开头 - if track.lastSample.hasVcl && isH264NewAccessUnit(nalu) { - var currentOffset int64 - if currentOffset, err = track.writer.Seek(0, io.SeekCurrent); err != nil { - return false - } - entry := sampleEntry{ - pts: track.lastSample.pts, - dts: track.lastSample.dts, - size: 0, - isKeyFrame: track.lastSample.isKey, - SampleDescriptionIndex: 1, - offset: uint64(currentOffset), - } - n := 0 - if n, err = track.writer.Write(track.lastSample.cache); err != nil { - return false - } - entry.size = uint64(n) - track.addSampleEntry(entry) - track.lastSample.cache = track.lastSample.cache[:0] - track.lastSample.hasVcl = false - } - if codec.IsH264VCLNaluType(nalu_type) { - track.lastSample.pts = pts - track.lastSample.dts = dts - track.lastSample.hasVcl = true - track.lastSample.isKey = false - if nalu_type == codec.H264_NAL_I_SLICE { - track.lastSample.isKey = true - } - } - track.lastSample.cache = append(track.lastSample.cache, codec.ConvertAnnexBToAVCC(nalu)...) - return true - }) - return + h264extra, ok := track.extra.(*h264ExtraData) + if !ok { + panic("must init h264ExtraData first") + } + codec.SplitFrameWithStartCode(h264, func(nalu []byte) bool { + nalu_type := codec.H264NaluType(nalu) + switch nalu_type { + case codec.H264_NAL_SPS: + spsid := codec.GetSPSIdWithStartCode(nalu) + for _, sps := range h264extra.spss { + if spsid == codec.GetSPSIdWithStartCode(sps) { + return true + } + } + tmp := make([]byte, len(nalu)) + copy(tmp, nalu) + h264extra.spss = append(h264extra.spss, tmp) + if track.width == 0 || track.height == 0 { + width, height := codec.GetH264Resolution(h264extra.spss[0]) + if track.width == 0 { + track.width = width + } + if track.height == 0 { + track.height = height + } + } + case codec.H264_NAL_PPS: + ppsid := codec.GetPPSIdWithStartCode(nalu) + for _, pps := range h264extra.ppss { + if ppsid == codec.GetPPSIdWithStartCode(pps) { + return true + } + } + tmp := make([]byte, len(nalu)) + copy(tmp, nalu) + h264extra.ppss = append(h264extra.ppss, tmp) + } + //aud/sps/pps/sei 为帧间隔 + //通过first_slice_in_mb来判断,改nalu是否为一帧的开头 + if track.lastSample.hasVcl && isH264NewAccessUnit(nalu) { + var currentOffset int64 + if currentOffset, err = track.writer.Seek(0, io.SeekCurrent); err != nil { + return false + } + entry := sampleEntry{ + pts: track.lastSample.pts, + dts: track.lastSample.dts, + size: 0, + isKeyFrame: track.lastSample.isKey, + SampleDescriptionIndex: 1, + offset: uint64(currentOffset), + } + n := 0 + if n, err = track.writer.Write(track.lastSample.cache); err != nil { + return false + } + entry.size = uint64(n) + track.addSampleEntry(entry) + track.lastSample.cache = track.lastSample.cache[:0] + track.lastSample.hasVcl = false + } + if codec.IsH264VCLNaluType(nalu_type) { + track.lastSample.pts = pts + track.lastSample.dts = dts + track.lastSample.hasVcl = true + track.lastSample.isKey = false + if nalu_type == codec.H264_NAL_I_SLICE { + track.lastSample.isKey = true + } + } + track.lastSample.cache = append(track.lastSample.cache, codec.ConvertAnnexBToAVCC(nalu)...) + return true + }) + return } func (track *mp4track) writeH265(h265 []byte, pts, dts uint64) (err error) { - h265extra, ok := track.extra.(*h265ExtraData) - if !ok { - panic("must init h265ExtraData first") - } - codec.SplitFrameWithStartCode(h265, func(nalu []byte) bool { - nalu_type := codec.H265NaluType(nalu) - switch nalu_type { - case codec.H265_NAL_SPS: - h265extra.hvccExtra.UpdateSPS(nalu) - if track.width == 0 || track.height == 0 { - width, height := codec.GetH265Resolution(nalu) - if track.width == 0 { - track.width = width - } - if track.height == 0 { - track.height = height - } - } - case codec.H265_NAL_PPS: - h265extra.hvccExtra.UpdatePPS(nalu) - case codec.H265_NAL_VPS: - h265extra.hvccExtra.UpdateVPS(nalu) - } - - if track.lastSample.hasVcl && isH265NewAccessUnit(nalu) { - var currentOffset int64 - if currentOffset, err = track.writer.Seek(0, io.SeekCurrent); err != nil { - return false - } - entry := sampleEntry{ - pts: track.lastSample.pts, - dts: track.lastSample.dts, - size: 0, - isKeyFrame: track.lastSample.isKey, - SampleDescriptionIndex: 1, - offset: uint64(currentOffset), - } - n := 0 - if n, err = track.writer.Write(track.lastSample.cache); err != nil { - return false - } - entry.size = uint64(n) - track.addSampleEntry(entry) - track.lastSample.cache = track.lastSample.cache[:0] - track.lastSample.hasVcl = false - } - if codec.IsH265VCLNaluType(nalu_type) { - track.lastSample.pts = pts - track.lastSample.dts = dts - track.lastSample.hasVcl = true - track.lastSample.isKey = false - if nalu_type >= codec.H265_NAL_SLICE_BLA_W_LP && nalu_type <= codec.H265_NAL_SLICE_CRA { - track.lastSample.isKey = true - } - } - track.lastSample.cache = append(track.lastSample.cache, codec.ConvertAnnexBToAVCC(nalu)...) - return true - }) - return + h265extra, ok := track.extra.(*h265ExtraData) + if !ok { + panic("must init h265ExtraData first") + } + codec.SplitFrameWithStartCode(h265, func(nalu []byte) bool { + nalu_type := codec.H265NaluType(nalu) + switch nalu_type { + case codec.H265_NAL_SPS: + h265extra.hvccExtra.UpdateSPS(nalu) + if track.width == 0 || track.height == 0 { + width, height := codec.GetH265Resolution(nalu) + if track.width == 0 { + track.width = width + } + if track.height == 0 { + track.height = height + } + } + case codec.H265_NAL_PPS: + h265extra.hvccExtra.UpdatePPS(nalu) + case codec.H265_NAL_VPS: + h265extra.hvccExtra.UpdateVPS(nalu) + } + + if track.lastSample.hasVcl && isH265NewAccessUnit(nalu) { + var currentOffset int64 + if currentOffset, err = track.writer.Seek(0, io.SeekCurrent); err != nil { + return false + } + entry := sampleEntry{ + pts: track.lastSample.pts, + dts: track.lastSample.dts, + size: 0, + isKeyFrame: track.lastSample.isKey, + SampleDescriptionIndex: 1, + offset: uint64(currentOffset), + } + n := 0 + if n, err = track.writer.Write(track.lastSample.cache); err != nil { + return false + } + entry.size = uint64(n) + track.addSampleEntry(entry) + track.lastSample.cache = track.lastSample.cache[:0] + track.lastSample.hasVcl = false + } + if codec.IsH265VCLNaluType(nalu_type) { + track.lastSample.pts = pts + track.lastSample.dts = dts + track.lastSample.hasVcl = true + track.lastSample.isKey = false + if nalu_type >= codec.H265_NAL_SLICE_BLA_W_LP && nalu_type <= codec.H265_NAL_SLICE_CRA { + track.lastSample.isKey = true + } + } + track.lastSample.cache = append(track.lastSample.cache, codec.ConvertAnnexBToAVCC(nalu)...) + return true + }) + return } func (track *mp4track) writeAAC(aacframes []byte, pts, dts uint64) (err error) { - aacextra, ok := track.extra.(*aacExtraData) - if !ok { - return errors.New("must init aacExtraData first") - } - if aacextra.asc == nil || len(aacextra.asc) <= 0 { - asc, err := codec.ConvertADTSToASC(aacframes) - if err != nil { - return err - } - aacextra.asc = asc.Encode() - - if track.chanelCount == 0 { - track.chanelCount = asc.Channel_configuration - } - if track.sampleRate == 0 { - track.sampleRate = uint32(codec.AACSampleIdxToSample(int(asc.Sample_freq_index))) - } - if track.sampleBits == 0 { - // aac has no fixed bit depth, so we just set it to the default of 16 - // see AudioSampleEntry (stsd-box) and https://superuser.com/a/1173507 - track.sampleBits = 16 - } - } - - var currentOffset int64 - if currentOffset, err = track.writer.Seek(0, io.SeekCurrent); err != nil { - return - } - //某些情况下,aacframes 可能由多个aac帧组成需要分帧,否则quicktime 貌似播放有问题 - codec.SplitAACFrame(aacframes, func(aac []byte) { - entry := sampleEntry{ - pts: pts, - dts: dts, - size: 0, - SampleDescriptionIndex: 1, - offset: uint64(currentOffset), - } - n := 0 - n, err = track.writer.Write(aac[7:]) - if err != nil { - return - } - currentOffset += int64(n) - entry.size = uint64(n) - track.addSampleEntry(entry) - }) - - return + aacextra, ok := track.extra.(*aacExtraData) + if !ok { + return errors.New("must init aacExtraData first") + } + if aacextra.asc == nil || len(aacextra.asc) <= 0 { + asc, err := codec.ConvertADTSToASC(aacframes) + if err != nil { + return err + } + aacextra.asc = asc.Encode() + + if track.chanelCount == 0 { + track.chanelCount = asc.Channel_configuration + } + if track.sampleRate == 0 { + track.sampleRate = uint32(codec.AACSampleIdxToSample(int(asc.Sample_freq_index))) + } + if track.sampleBits == 0 { + // aac has no fixed bit depth, so we just set it to the default of 16 + // see AudioSampleEntry (stsd-box) and https://superuser.com/a/1173507 + track.sampleBits = 16 + } + } + + var currentOffset int64 + if currentOffset, err = track.writer.Seek(0, io.SeekCurrent); err != nil { + return + } + //某些情况下,aacframes 可能由多个aac帧组成需要分帧,否则quicktime 貌似播放有问题 + codec.SplitAACFrame(aacframes, func(aac []byte) { + entry := sampleEntry{ + pts: pts, + dts: dts, + size: 0, + SampleDescriptionIndex: 1, + offset: uint64(currentOffset), + } + n := 0 + n, err = track.writer.Write(aac[7:]) + if err != nil { + return + } + currentOffset += int64(n) + entry.size = uint64(n) + track.addSampleEntry(entry) + }) + + return } func (track *mp4track) writeG711(g711 []byte, pts, dts uint64) (err error) { - var currentOffset int64 - if currentOffset, err = track.writer.Seek(0, io.SeekCurrent); err != nil { - return - } - entry := sampleEntry{ - pts: pts, - dts: dts, - size: 0, - SampleDescriptionIndex: 1, - offset: uint64(currentOffset), - } - n := 0 - n, err = track.writer.Write(g711) - entry.size = uint64(n) - track.addSampleEntry(entry) - return + var currentOffset int64 + if currentOffset, err = track.writer.Seek(0, io.SeekCurrent); err != nil { + return + } + entry := sampleEntry{ + pts: pts, + dts: dts, + size: 0, + SampleDescriptionIndex: 1, + offset: uint64(currentOffset), + } + n := 0 + n, err = track.writer.Write(g711) + entry.size = uint64(n) + track.addSampleEntry(entry) + return } func (track *mp4track) writeMP3(mp3 []byte, pts, dts uint64) (err error) { - if track.sampleRate == 0 { - codec.SplitMp3Frames(mp3, func(head *codec.MP3FrameHead, frame []byte) { - track.sampleRate = uint32(head.GetSampleRate()) - track.chanelCount = uint8(head.GetChannelCount()) - track.sampleBits = 16 - }) - if track.sampleRate > 24000 { - track.cid = MP4_CODEC_MP2 - } else { - track.cid = MP4_CODEC_MP3 - } - } - return track.writeG711(mp3, pts, dts) + if track.sampleRate == 0 { + codec.SplitMp3Frames(mp3, func(head *codec.MP3FrameHead, frame []byte) { + track.sampleRate = uint32(head.GetSampleRate()) + track.chanelCount = uint8(head.GetChannelCount()) + track.sampleBits = 16 + }) + if track.sampleRate > 24000 { + track.cid = MP4_CODEC_MP2 + } else { + track.cid = MP4_CODEC_MP3 + } + } + return track.writeG711(mp3, pts, dts) } func (track *mp4track) writeOPUS(opus []byte, pts, dts uint64) (err error) { - if track.sampleRate == 0 { - opusPacket := codec.DecodeOpusPacket(opus) - track.sampleRate = 48000 // TODO: fixed? - if opusPacket.Stereo != 0 { - track.chanelCount = 1 - } else { - track.chanelCount = 2 - } - track.sampleBits = 16 // TODO: fixed - } - - return track.writeG711(opus, pts, dts) + if track.sampleRate == 0 { + opusPacket := codec.DecodeOpusPacket(opus) + track.sampleRate = 48000 // TODO: fixed? + if opusPacket.Stereo != 0 { + track.chanelCount = 1 + } else { + track.chanelCount = 2 + } + track.sampleBits = 16 // TODO: fixed + } + + return track.writeG711(opus, pts, dts) } func (track *mp4track) flush() (err error) { - var currentOffset int64 - if track.lastSample != nil && len(track.lastSample.cache) > 0 { - if currentOffset, err = track.writer.Seek(0, io.SeekCurrent); err != nil { - return err - } - entry := sampleEntry{ - pts: track.lastSample.pts, - dts: track.lastSample.dts, - isKeyFrame: track.lastSample.isKey, - size: 0, - SampleDescriptionIndex: 1, - offset: uint64(currentOffset), - } - n := 0 - if n, err = track.writer.Write(track.lastSample.cache); err != nil { - return err - } - entry.size = uint64(n) - track.addSampleEntry(entry) - track.lastSample.cache = track.lastSample.cache[:0] - track.lastSample.hasVcl = false - track.lastSample.isKey = false - track.lastSample.dts = 0 - track.lastSample.pts = 0 - } - return nil + var currentOffset int64 + if track.lastSample != nil && len(track.lastSample.cache) > 0 { + if currentOffset, err = track.writer.Seek(0, io.SeekCurrent); err != nil { + return err + } + entry := sampleEntry{ + pts: track.lastSample.pts, + dts: track.lastSample.dts, + isKeyFrame: track.lastSample.isKey, + size: 0, + SampleDescriptionIndex: 1, + offset: uint64(currentOffset), + } + n := 0 + if n, err = track.writer.Write(track.lastSample.cache); err != nil { + return err + } + entry.size = uint64(n) + track.addSampleEntry(entry) + track.lastSample.cache = track.lastSample.cache[:0] + track.lastSample.hasVcl = false + track.lastSample.isKey = false + track.lastSample.dts = 0 + track.lastSample.pts = 0 + } + return nil } func (track *mp4track) clearSamples() { - track.samplelist = track.samplelist[:0] + track.samplelist = track.samplelist[:0] }