Open 3052 opened 6 months ago
I need to parse segment only:
https://github.com/yapingcat/gomedia/files/13692755/index_video_5_0_1.zip
> mp4ff-info index_video_5_0_1.mp4
[moof] size=2574
[mfhd] size=16 version=0 flags=000000
- sequenceNumber: 1
[pssh] size=634 version=0 flags=000000
- systemID: 9a04f079-9840-4286-ab92-e65be0885f95 (PlayReady)
[pssh] size=67 version=0 flags=000000
- systemID: edef8ba9-79d6-4ace-a3c8-27dcd51d21ed (Widevine)
[traf] size=1849
[tfhd] size=20 version=0 flags=020020
- trackID: 1
- defaultBaseIsMoof: true
- defaultSampleFlags: 00610000 (isLeading=0 dependsOn=0 isDependedOn=1 hasRedundancy=2 padding=0 isNonSync=true degradationPriority=0)
[tfdt] size=20 version=1 flags=000000
- baseMediaDecodeTime: 3158
[trun] size=600 version=1 flags=000b05
- sampleCount: 48
[senc] size=1072 version=0 flags=000002
- sampleCount: 48
- perSampleIVSize: 8
[saio] size=32 version=1 flags=000001
- auxInfoType: cenc
- auxInfoTypeParameter: 0
- sampleCount: 1
- offset[1]=1389
[saiz] size=25 version=0 flags=000001
- auxInfoType: cenc
- auxInfoTypeParameter: 0
- defaultSampleInfoSize: 22
- sampleCount: 48
[sbgp] size=28 version=0 flags=000000
- groupingType: seig
- entryCount: 1
[sgpd] size=44 version=1 flags=000000
groupingType: seig
- defaultLength: 20
- entryCount: 1
- GroupingType "seig" size=20
- * cryptByteBlock: 0
- * skipByteBlock: 0
- * isProtected: 1
- * perSampleIVSize: 8
- * KID: bdfa4d6c-db39-702e-5b68-1f90617f9a7e
[mdat] size=1279712
[styp] size=24
- majorBrand: msdh
- minorVersion: 0
- compatibleBrand: msdh
- compatibleBrand: msix
[sidx] size=52 version=1 flags=000000
- referenceID: 1
- timeScale: 24000
- earliestPresentationTime: 3158
- firstOffset: 0
and:
> mp4tool dump index_video_5_0_1.mp4
[moof] Size=2574
[mfhd] Size=16 Version=0 Flags=0x000000 SequenceNumber=1
[pssh] Size=634 ... (use "-full pssh" to show all)
[pssh] Size=67 ... (use "-full pssh" to show all)
[traf] Size=1849
[tfhd] Size=20 Version=0 Flags=0x020020 TrackID=1 DefaultSampleFlags=0x610000
[tfdt] Size=20 Version=1 Flags=0x000000 BaseMediaDecodeTimeV1=3158
[trun] Size=600 ... (use "-full trun" to show all)
[senc] (unsupported box type) Size=1072 Data=[...] (use "-full senc" to show all)
[saio] Size=32 Version=1 Flags=0x000001 AuxInfoType="cenc" AuxInfoTypeParameter=0x0 EntryCount=1 OffsetV1=[1389]
[saiz] Size=25 Version=0 Flags=0x000001 AuxInfoType="cenc" AuxInfoTypeParameter=0x0 DefaultSampleInfoSize=22 SampleCount=48
[sbgp] Size=28 Version=0 Flags=0x000000 GroupingType=1936025959 EntryCount=1 Entries=[{SampleCount=48 GroupDescriptionIndex=65537}]
[sgpd] Size=44 ... (use "-full sgpd" to show all)
[mdat] Size=1279712 Data=[...] (use "-full mdat" to show all)
[styp] Size=24 MajorBrand="msdh" MinorVersion=0 CompatibleBrands=[{CompatibleBrand="msdh"}, {CompatibleBrand="msix"}]
[sidx] Size=52 ... (use "-full sidx" to show all)
gomedia is used to extract video/audio frame from mp4,so gomedia need moov box to parse dash segment.
right, but with the current module, you have to parse the init data for EACH segment, which is really wasteful
OK I think this works, but it seems like bad code to have to call mp4.CreateMp4Demuxer
for every segment:
package sidx
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"errors"
"fmt"
"github.com/yapingcat/gomedia/go-mp4"
"io"
"net/http"
)
// github.com/Eyevinn/mp4ff/blob/v0.40.2/mp4/crypto.go#L101
func Decrypt_CENC(sample []byte, key []byte, subSample *mp4.SubSample) error {
block, err := aes.NewCipher(key)
if err != nil {
return err
}
stream := cipher.NewCTR(block, subSample.IV[:])
if len(subSample.Patterns) != 0 {
var pos uint32 = 0
for j := 0; j < len(subSample.Patterns); j++ {
ss := subSample.Patterns[j]
nrClear := uint32(ss.BytesClear)
if nrClear > 0 {
pos += nrClear
}
nrEnc := ss.BytesProtected
if nrEnc > 0 {
stream.XORKeyStream(sample[pos:pos+nrEnc], sample[pos:pos+nrEnc])
pos += nrEnc
}
}
} else {
stream.XORKeyStream(sample, sample)
}
return nil
}
func get(url string, start, end uint32) ([]byte, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Range", fmt.Sprintf("bytes=%v-%v", start, end))
fmt.Println(start, end)
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusPartialContent {
return nil, errors.New(res.Status)
}
return io.ReadAll(res.Body)
}
func byte_ranges(r io.Reader, start uint32) ([][]uint32, error) {
sidx := mp4.SegmentIndexBox{
Box: &mp4.FullBox{
Box: &mp4.BasicBox{},
},
}
if _, err := sidx.Box.Box.Decode(r); err != nil {
return nil, err
}
if _, err := sidx.Decode(r); err != nil {
return nil, err
}
var rs [][]uint32
for _, e := range sidx.Entrys {
r := []uint32{start, start + e.ReferencedSize - 1}
rs = append(rs, r)
start += e.ReferencedSize
}
return rs, nil
}
func mux(
dst io.WriteSeeker,
url string,
start_sidx, start_segment uint32,
key []byte,
) error {
muxer, err := mp4.CreateMp4Muxer(dst)
if err != nil {
return err
}
vid := muxer.AddVideoTrack(mp4.MP4_CODEC_H264)
init, err := get(url, 0, start_sidx-1)
if err != nil {
return err
}
sidx, err := get(url, start_sidx, start_segment-1)
if err != nil {
return err
}
ranges, err := byte_ranges(bytes.NewReader(sidx), start_segment)
if err != nil {
return err
}
for _, r := range ranges {
segment, err := get(url, r[0], r[1])
if err != nil {
return err
}
segment = append(init, segment...)
demuxer := mp4.CreateMp4Demuxer(bytes.NewReader(segment))
if _, err := demuxer.ReadHead(); err != nil {
return err
}
demuxer.OnRawSample = func(_ mp4.MP4_CODEC_TYPE, sample []byte, subSample *mp4.SubSample) error {
return Decrypt_CENC(sample, key, subSample)
}
for {
pkg, err := demuxer.ReadPacket()
if err == io.EOF {
break
} else if err != nil {
return err
}
if err := muxer.Write(vid, pkg.Data, pkg.Pts, pkg.Dts); err != nil {
return err
}
}
}
return muxer.WriteTrailer()
}
other tools can read segment without init:
https://github.com/Eyevinn/mp4ff
https://github.com/abema/go-mp4
but this module cannot: