Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mp4: read segment without init #115

Open
3052 opened this issue Dec 15, 2023 · 5 comments
Open

mp4: read segment without init #115

3052 opened this issue Dec 15, 2023 · 5 comments

Comments

@3052
Copy link

3052 commented Dec 15, 2023

other tools can read segment without init:

mp4ff-info index_video_5_0_1.mp4

https://github.com/Eyevinn/mp4ff

mp4tool dump index_video_5_0_1.mp4

https://github.com/abema/go-mp4

but this module cannot:

go run example_demux_fmp4.go index_video_5_0_1.mp4
go run example_demux_mp4.go -mp4file index_video_5_0_1.mp4
go run example_demux_mp4_memeory_io.go -mp4file index_video_5_0_1.mp4
@3052 3052 changed the title mp4.CreateMp4Demuxer: ReBindReader mp4: read segment without init Dec 16, 2023
@yapingcat
Copy link
Owner

yapingcat commented Dec 16, 2023

  • please play this mp4 file with ffplay
  • if you get mp4 samples from fragment mp4 file, but not vidoe/audio frame , moov box is not neccessary

@3052
Copy link
Author

3052 commented Dec 16, 2023

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)

@yapingcat
Copy link
Owner

yapingcat commented Dec 17, 2023

gomedia is used to extract video/audio frame from mp4,so gomedia need moov box to parse dash segment.

@3052
Copy link
Author

3052 commented Dec 27, 2023

right, but with the current module, you have to parse the init data for EACH segment, which is really wasteful

@3052 3052 closed this as completed Jan 3, 2024
@3052 3052 reopened this Jan 4, 2024
@3052
Copy link
Author

3052 commented Apr 22, 2024

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()
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants