diff --git a/README.md b/README.md
index 2e0bd42a..7bc86ea9 100644
--- a/README.md
+++ b/README.md
@@ -1162,6 +1162,7 @@ For more advanced options, you can create and serve a custom web page by startin
Related projects
* gortsplib (RTSP library used internally) https://github.com/aler9/gortsplib
+* gohlslib (HLS library used internally) https://github.com/bluenviron/gohlslib
* pion/sdp (SDP library used internally) https://github.com/pion/sdp
* pion/rtp (RTP library used internally) https://github.com/pion/rtp
* pion/rtcp (RTCP library used internally) https://github.com/pion/rtcp
diff --git a/go.mod b/go.mod
index d1122f92..66c30096 100644
--- a/go.mod
+++ b/go.mod
@@ -4,42 +4,46 @@ go 1.18
require (
code.cloudfoundry.org/bytefmt v0.0.0
- github.com/abema/go-mp4 v0.10.0
github.com/alecthomas/kong v0.7.1
github.com/aler9/gortsplib/v2 v2.1.3
github.com/asticode/go-astits v1.11.0
+ github.com/bluenviron/gohlslib v0.0.0-20230310115623-ec8d496cca25
github.com/fsnotify/fsnotify v1.4.9
- github.com/gin-gonic/gin v1.8.1
+ github.com/gin-gonic/gin v1.9.0
github.com/google/uuid v1.3.0
github.com/gookit/color v1.4.2
github.com/gorilla/websocket v1.5.0
- github.com/grafov/m3u8 v0.11.1
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/notedit/rtmp v0.0.2
- github.com/orcaman/writerseeker v0.0.0
github.com/pion/ice/v2 v2.2.11
github.com/pion/interceptor v0.1.11
github.com/pion/rtp v1.7.13
github.com/pion/webrtc/v3 v3.1.47
- github.com/stretchr/testify v1.8.1
+ github.com/stretchr/testify v1.8.2
golang.org/x/crypto v0.5.0
gopkg.in/yaml.v2 v2.4.0
)
require (
+ github.com/abema/go-mp4 v0.10.1 // indirect
github.com/asticode/go-astikit v0.30.0 // indirect
+ github.com/bytedance/sonic v1.8.0 // indirect
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
- github.com/go-playground/locales v0.14.0 // indirect
- github.com/go-playground/universal-translator v0.18.0 // indirect
- github.com/go-playground/validator/v10 v10.10.0 // indirect
- github.com/goccy/go-json v0.9.7 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
+ github.com/go-playground/universal-translator v0.18.1 // indirect
+ github.com/go-playground/validator/v10 v10.11.2 // indirect
+ github.com/goccy/go-json v0.10.0 // indirect
+ github.com/grafov/m3u8 v0.11.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
+ github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
- github.com/mattn/go-isatty v0.0.14 // indirect
+ github.com/mattn/go-isatty v0.0.17 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
- github.com/pelletier/go-toml/v2 v2.0.1 // indirect
+ github.com/orcaman/writerseeker v0.0.0 // indirect
+ github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pion/datachannel v1.5.2 // indirect
github.com/pion/dtls/v2 v2.2.4 // indirect
github.com/pion/logging v0.2.2 // indirect
@@ -55,12 +59,14 @@ require (
github.com/pion/turn/v2 v2.0.8 // indirect
github.com/pion/udp v0.1.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/ugorji/go/codec v1.2.7 // indirect
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ github.com/ugorji/go/codec v1.2.9 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
+ golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
- google.golang.org/protobuf v1.28.0 // indirect
+ google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index a666c11e..2c924432 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-github.com/abema/go-mp4 v0.10.0 h1:76eRo2PlJQUHAKKsQJnROsdIHdt+aPXhJkmPFMjj3+4=
-github.com/abema/go-mp4 v0.10.0/go.mod h1:vPl9t5ZK7K0x68jh12/+ECWBCXoWuIDtNgPtU2f04ws=
+github.com/abema/go-mp4 v0.10.1 h1:wOhZgNxjduc8r4FJdwPa5x/gdBSSX+8MTnfNj/xkJaE=
+github.com/abema/go-mp4 v0.10.1/go.mod h1:vPl9t5ZK7K0x68jh12/+ECWBCXoWuIDtNgPtU2f04ws=
github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0=
github.com/alecthomas/kong v0.7.1 h1:azoTh0IOfwlAX3qN9sHWTxACE2oV8Bg2gAwBsMwDQY4=
github.com/alecthomas/kong v0.7.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U=
@@ -12,10 +12,17 @@ github.com/asticode/go-astikit v0.30.0 h1:DkBkRQRIxYcknlaU7W7ksNfn4gMFsB0tqMJflx
github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
github.com/asticode/go-astits v1.11.0 h1:GTHUXht0ZXAJXsVbsLIcyfHr1Bchi4QQwMARw2ZWAng=
github.com/asticode/go-astits v1.11.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI=
+github.com/bluenviron/gohlslib v0.0.0-20230310115623-ec8d496cca25 h1:F5rtyEy7a8yILctfOrWjuCAFQhL9MG3E+rWrG8gKZV8=
+github.com/bluenviron/gohlslib v0.0.0-20230310115623-ec8d496cca25/go.mod h1:lIJHdX5oH3VLUfH3cjA+w3HGIswtgiriB9koNv6ZKCc=
+github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
+github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
+github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
+github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/cloudfoundry/bytefmt v0.0.0-20211005130812-5bb3c17173e5 h1:xB7KkA98BcUdzVcwyZxb5R0FGIHxNPHgZOzkjPEY5gM=
github.com/cloudfoundry/bytefmt v0.0.0-20211005130812-5bb3c17173e5/go.mod h1:v4VVB6oBMz/c9fRY6vZrwr5xKRWOH5NPDjQZlPk0Gbs=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -24,19 +31,18 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
-github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
-github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
-github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
-github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
-github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
-github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
-github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
-github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
-github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
-github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
+github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
+github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
+github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
-github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
-github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
+github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -67,19 +73,18 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
+github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
-github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
-github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
-github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
+github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
@@ -99,8 +104,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
-github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
-github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
+github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
+github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E=
github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ=
github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY=
@@ -144,13 +149,10 @@ github.com/pion/udp v0.1.4 h1:OowsTmu1Od3sD6i3fQUJxJn2fEvJO6L1TidgadtbTI8=
github.com/pion/udp v0.1.4/go.mod h1:G8LDo56HsFwC24LIcnT4YIDU5qcB6NepqqjP0keL2us=
github.com/pion/webrtc/v3 v3.1.47 h1:2dFEKRI1rzFvehXDq43hK9OGGyTGJSusUi3j6QKHC5s=
github.com/pion/webrtc/v3 v3.1.47/go.mod h1:8U39MYZCLVV4sIBn01htASVNkWQN2zDa/rx5xisEXWs=
-github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
-github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@@ -162,20 +164,23 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/sunfish-shogi/bufseekio v0.0.0-20210207115823-a4185644b365/go.mod h1:dEzdXgvImkQ3WLI+0KQpmEx8T/C/ma9KeS3AfmU899I=
-github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
-github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
-github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
+github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@@ -221,14 +226,13 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -264,14 +268,12 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
-google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg=
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
@@ -284,6 +286,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/internal/conf/hlsvariant.go b/internal/conf/hlsvariant.go
index 133e0f63..33629ffe 100644
--- a/internal/conf/hlsvariant.go
+++ b/internal/conf/hlsvariant.go
@@ -4,7 +4,7 @@ import (
"encoding/json"
"fmt"
- "github.com/aler9/rtsp-simple-server/internal/hls"
+ "github.com/bluenviron/gohlslib"
)
// HLSVariant is the hlsVariant parameter.
diff --git a/internal/core/hls_muxer.go b/internal/core/hls_muxer.go
index 94cc1ab2..a5c9a2f6 100644
--- a/internal/core/hls_muxer.go
+++ b/internal/core/hls_muxer.go
@@ -20,8 +20,8 @@ import (
"github.com/aler9/rtsp-simple-server/internal/conf"
"github.com/aler9/rtsp-simple-server/internal/formatprocessor"
- "github.com/aler9/rtsp-simple-server/internal/hls"
"github.com/aler9/rtsp-simple-server/internal/logger"
+ "github.com/bluenviron/gohlslib"
)
const (
diff --git a/internal/core/hls_source.go b/internal/core/hls_source.go
index cdfda106..d3e296fb 100644
--- a/internal/core/hls_source.go
+++ b/internal/core/hls_source.go
@@ -9,8 +9,9 @@ import (
"github.com/aler9/rtsp-simple-server/internal/conf"
"github.com/aler9/rtsp-simple-server/internal/formatprocessor"
- "github.com/aler9/rtsp-simple-server/internal/hls"
"github.com/aler9/rtsp-simple-server/internal/logger"
+ "github.com/bluenviron/gohlslib"
+ hlslogger "github.com/bluenviron/gohlslib/pkg/logger"
)
type hlsSourceParent interface {
@@ -23,6 +24,12 @@ type hlsSource struct {
parent hlsSourceParent
}
+type hlsLoggerWrapper func(level logger.Level, format string, args ...interface{})
+
+func (w hlsLoggerWrapper) Log(level hlslogger.Level, format string, args ...interface{}) {
+ w(logger.Level(level), format, args)
+}
+
func newHLSSource(
parent hlsSourceParent,
) *hlsSource {
@@ -48,7 +55,7 @@ func (s *hlsSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan
c, err := hls.NewClient(
cnf.Source,
cnf.SourceFingerprint,
- s,
+ hlsLoggerWrapper(s.Log),
)
if err != nil {
return err
diff --git a/internal/hls/client.go b/internal/hls/client.go
deleted file mode 100644
index b30baccd..00000000
--- a/internal/hls/client.go
+++ /dev/null
@@ -1,127 +0,0 @@
-package hls
-
-import (
- "context"
- "fmt"
- "net/url"
- "time"
-
- "github.com/aler9/gortsplib/v2/pkg/format"
-
- "github.com/aler9/rtsp-simple-server/internal/logger"
-)
-
-const (
- clientMPEGTSEntryQueueSize = 100
- clientFMP4MaxPartTracksPerSegment = 200
- clientLiveStartingInvPosition = 3
- clientLiveMaxInvPosition = 5
- clientMaxDTSRTCDiff = 10 * time.Second
-)
-
-func clientAbsoluteURL(base *url.URL, relative string) (*url.URL, error) {
- u, err := url.Parse(relative)
- if err != nil {
- return nil, err
- }
- return base.ResolveReference(u), nil
-}
-
-// ClientLogger allows to receive log lines.
-type ClientLogger interface {
- Log(level logger.Level, format string, args ...interface{})
-}
-
-// Client is a HLS client.
-type Client struct {
- fingerprint string
- logger ClientLogger
-
- ctx context.Context
- ctxCancel func()
- onTracks func([]format.Format) error
- onData map[format.Format]func(time.Duration, interface{})
- playlistURL *url.URL
-
- // out
- outErr chan error
-}
-
-// NewClient allocates a Client.
-func NewClient(
- playlistURLStr string,
- fingerprint string,
- logger ClientLogger,
-) (*Client, error) {
- playlistURL, err := url.Parse(playlistURLStr)
- if err != nil {
- return nil, err
- }
-
- ctx, ctxCancel := context.WithCancel(context.Background())
-
- c := &Client{
- fingerprint: fingerprint,
- logger: logger,
- ctx: ctx,
- ctxCancel: ctxCancel,
- playlistURL: playlistURL,
- onData: make(map[format.Format]func(time.Duration, interface{})),
- outErr: make(chan error, 1),
- }
-
- return c, nil
-}
-
-// Start starts the client.
-func (c *Client) Start() {
- go c.run()
-}
-
-// Close closes all the Client resources.
-func (c *Client) Close() {
- c.ctxCancel()
-}
-
-// Wait waits for any error of the Client.
-func (c *Client) Wait() chan error {
- return c.outErr
-}
-
-// OnTracks sets a callback that is called when tracks are read.
-func (c *Client) OnTracks(cb func([]format.Format) error) {
- c.onTracks = cb
-}
-
-// OnData sets a callback that is called when data arrives.
-func (c *Client) OnData(forma format.Format, cb func(time.Duration, interface{})) {
- c.onData[forma] = cb
-}
-
-func (c *Client) run() {
- c.outErr <- c.runInner()
-}
-
-func (c *Client) runInner() error {
- rp := newClientRoutinePool()
-
- dl := newClientDownloaderPrimary(
- c.playlistURL,
- c.fingerprint,
- c.logger,
- rp,
- c.onTracks,
- c.onData,
- )
- rp.add(dl)
-
- select {
- case err := <-rp.errorChan():
- rp.close()
- return err
-
- case <-c.ctx.Done():
- rp.close()
- return fmt.Errorf("terminated")
- }
-}
diff --git a/internal/hls/client_downloader_primary.go b/internal/hls/client_downloader_primary.go
deleted file mode 100644
index 0851e90e..00000000
--- a/internal/hls/client_downloader_primary.go
+++ /dev/null
@@ -1,297 +0,0 @@
-package hls
-
-import (
- "context"
- "crypto/sha256"
- "crypto/tls"
- "encoding/hex"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "strings"
- "time"
-
- "github.com/aler9/gortsplib/v2/pkg/format"
- gm3u8 "github.com/grafov/m3u8"
-
- "github.com/aler9/rtsp-simple-server/internal/hls/m3u8"
- "github.com/aler9/rtsp-simple-server/internal/logger"
-)
-
-func clientDownloadPlaylist(ctx context.Context, httpClient *http.Client, ur *url.URL) (m3u8.Playlist, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, ur.String(), nil)
- if err != nil {
- return nil, err
- }
-
- res, err := httpClient.Do(req)
- if err != nil {
- return nil, err
- }
- defer res.Body.Close()
-
- if res.StatusCode != http.StatusOK {
- return nil, fmt.Errorf("bad status code: %d", res.StatusCode)
- }
-
- byts, err := io.ReadAll(res.Body)
- if err != nil {
- return nil, err
- }
-
- return m3u8.Unmarshal(byts)
-}
-
-func pickLeadingPlaylist(variants []*gm3u8.Variant) *gm3u8.Variant {
- var candidates []*gm3u8.Variant //nolint:prealloc
- for _, v := range variants {
- if v.Codecs != "" && !codecParametersAreSupported(v.Codecs) {
- continue
- }
- candidates = append(candidates, v)
- }
- if candidates == nil {
- return nil
- }
-
- // pick the variant with the greatest bandwidth
- var leadingPlaylist *gm3u8.Variant
- for _, v := range candidates {
- if leadingPlaylist == nil ||
- v.VariantParams.Bandwidth > leadingPlaylist.VariantParams.Bandwidth {
- leadingPlaylist = v
- }
- }
- return leadingPlaylist
-}
-
-func pickAudioPlaylist(alternatives []*gm3u8.Alternative, groupID string) *gm3u8.Alternative {
- candidates := func() []*gm3u8.Alternative {
- var ret []*gm3u8.Alternative
- for _, alt := range alternatives {
- if alt.GroupId == groupID {
- ret = append(ret, alt)
- }
- }
- return ret
- }()
- if candidates == nil {
- return nil
- }
-
- // pick the default audio playlist
- for _, alt := range candidates {
- if alt.Default {
- return alt
- }
- }
-
- // alternatively, pick the first one
- return candidates[0]
-}
-
-type clientTimeSync interface{}
-
-type clientDownloaderPrimary struct {
- primaryPlaylistURL *url.URL
- logger ClientLogger
- onTracks func([]format.Format) error
- onData map[format.Format]func(time.Duration, interface{})
- rp *clientRoutinePool
-
- httpClient *http.Client
- leadingTimeSync clientTimeSync
-
- // in
- streamTracks chan []format.Format
-
- // out
- startStreaming chan struct{}
- leadingTimeSyncReady chan struct{}
-}
-
-func newClientDownloaderPrimary(
- primaryPlaylistURL *url.URL,
- fingerprint string,
- logger ClientLogger,
- rp *clientRoutinePool,
- onTracks func([]format.Format) error,
- onData map[format.Format]func(time.Duration, interface{}),
-) *clientDownloaderPrimary {
- var tlsConfig *tls.Config
- if fingerprint != "" {
- tlsConfig = &tls.Config{
- InsecureSkipVerify: true,
- VerifyConnection: func(cs tls.ConnectionState) error {
- h := sha256.New()
- h.Write(cs.PeerCertificates[0].Raw)
- hstr := hex.EncodeToString(h.Sum(nil))
- fingerprintLower := strings.ToLower(fingerprint)
-
- if hstr != fingerprintLower {
- return fmt.Errorf("server fingerprint do not match: expected %s, got %s",
- fingerprintLower, hstr)
- }
-
- return nil
- },
- }
- }
-
- return &clientDownloaderPrimary{
- primaryPlaylistURL: primaryPlaylistURL,
- logger: logger,
- onTracks: onTracks,
- onData: onData,
- rp: rp,
- httpClient: &http.Client{
- Transport: &http.Transport{
- TLSClientConfig: tlsConfig,
- },
- },
- streamTracks: make(chan []format.Format),
- startStreaming: make(chan struct{}),
- leadingTimeSyncReady: make(chan struct{}),
- }
-}
-
-func (d *clientDownloaderPrimary) run(ctx context.Context) error {
- d.logger.Log(logger.Debug, "downloading primary playlist %s", d.primaryPlaylistURL)
-
- pl, err := clientDownloadPlaylist(ctx, d.httpClient, d.primaryPlaylistURL)
- if err != nil {
- return err
- }
-
- streamCount := 0
-
- switch plt := pl.(type) {
- case *m3u8.MediaPlaylist:
- d.logger.Log(logger.Debug, "primary playlist is a stream playlist")
- ds := newClientDownloaderStream(
- true,
- d.httpClient,
- d.primaryPlaylistURL,
- plt,
- d.logger,
- d.rp,
- d.onStreamTracks,
- d.onSetLeadingTimeSync,
- d.onGetLeadingTimeSync,
- d.onData,
- )
- d.rp.add(ds)
- streamCount++
-
- case *m3u8.MasterPlaylist:
- leadingPlaylist := pickLeadingPlaylist(plt.Variants)
- if leadingPlaylist == nil {
- return fmt.Errorf("no variants with supported codecs found")
- }
-
- u, err := clientAbsoluteURL(d.primaryPlaylistURL, leadingPlaylist.URI)
- if err != nil {
- return err
- }
-
- ds := newClientDownloaderStream(
- true,
- d.httpClient,
- u,
- nil,
- d.logger,
- d.rp,
- d.onStreamTracks,
- d.onSetLeadingTimeSync,
- d.onGetLeadingTimeSync,
- d.onData,
- )
- d.rp.add(ds)
- streamCount++
-
- if leadingPlaylist.Audio != "" {
- audioPlaylist := pickAudioPlaylist(plt.Alternatives, leadingPlaylist.Audio)
- if audioPlaylist == nil {
- return fmt.Errorf("audio playlist with id \"%s\" not found", leadingPlaylist.Audio)
- }
-
- u, err := clientAbsoluteURL(d.primaryPlaylistURL, audioPlaylist.URI)
- if err != nil {
- return err
- }
-
- ds := newClientDownloaderStream(
- false,
- d.httpClient,
- u,
- nil,
- d.logger,
- d.rp,
- d.onStreamTracks,
- d.onSetLeadingTimeSync,
- d.onGetLeadingTimeSync,
- d.onData,
- )
- d.rp.add(ds)
- streamCount++
- }
-
- default:
- return fmt.Errorf("invalid playlist")
- }
-
- var tracks []format.Format
-
- for i := 0; i < streamCount; i++ {
- select {
- case streamTracks := <-d.streamTracks:
- tracks = append(tracks, streamTracks...)
- case <-ctx.Done():
- return fmt.Errorf("terminated")
- }
- }
-
- if len(tracks) == 0 {
- return fmt.Errorf("no supported tracks found")
- }
-
- err = d.onTracks(tracks)
- if err != nil {
- return err
- }
-
- close(d.startStreaming)
-
- return nil
-}
-
-func (d *clientDownloaderPrimary) onStreamTracks(ctx context.Context, tracks []format.Format) bool {
- select {
- case d.streamTracks <- tracks:
- case <-ctx.Done():
- return false
- }
-
- select {
- case <-d.startStreaming:
- case <-ctx.Done():
- return false
- }
-
- return true
-}
-
-func (d *clientDownloaderPrimary) onSetLeadingTimeSync(ts clientTimeSync) {
- d.leadingTimeSync = ts
- close(d.leadingTimeSyncReady)
-}
-
-func (d *clientDownloaderPrimary) onGetLeadingTimeSync(ctx context.Context) (clientTimeSync, bool) {
- select {
- case <-d.leadingTimeSyncReady:
- case <-ctx.Done():
- return nil, false
- }
- return d.leadingTimeSync, true
-}
diff --git a/internal/hls/client_downloader_stream.go b/internal/hls/client_downloader_stream.go
deleted file mode 100644
index 9b91797c..00000000
--- a/internal/hls/client_downloader_stream.go
+++ /dev/null
@@ -1,260 +0,0 @@
-package hls
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "strconv"
- "time"
-
- "github.com/aler9/gortsplib/v2/pkg/format"
- gm3u8 "github.com/grafov/m3u8"
-
- "github.com/aler9/rtsp-simple-server/internal/hls/m3u8"
- "github.com/aler9/rtsp-simple-server/internal/logger"
-)
-
-func segmentsLen(segments []*gm3u8.MediaSegment) int {
- for i, seg := range segments {
- if seg == nil {
- return i
- }
- }
- return 0
-}
-
-func findSegmentWithInvPosition(segments []*gm3u8.MediaSegment, pos int) *gm3u8.MediaSegment {
- index := len(segments) - pos
- if index < 0 {
- return nil
- }
-
- return segments[index]
-}
-
-func findSegmentWithID(seqNo uint64, segments []*gm3u8.MediaSegment, id uint64) (*gm3u8.MediaSegment, int) {
- index := int(int64(id) - int64(seqNo))
- if index < 0 || index >= len(segments) {
- return nil, 0
- }
-
- return segments[index], len(segments) - index
-}
-
-type clientDownloaderStream struct {
- isLeading bool
- httpClient *http.Client
- playlistURL *url.URL
- initialPlaylist *m3u8.MediaPlaylist
- logger ClientLogger
- rp *clientRoutinePool
- onStreamTracks func(context.Context, []format.Format) bool
- onSetLeadingTimeSync func(clientTimeSync)
- onGetLeadingTimeSync func(context.Context) (clientTimeSync, bool)
- onData map[format.Format]func(time.Duration, interface{})
-
- curSegmentID *uint64
-}
-
-func newClientDownloaderStream(
- isLeading bool,
- httpClient *http.Client,
- playlistURL *url.URL,
- initialPlaylist *m3u8.MediaPlaylist,
- logger ClientLogger,
- rp *clientRoutinePool,
- onStreamTracks func(context.Context, []format.Format) bool,
- onSetLeadingTimeSync func(clientTimeSync),
- onGetLeadingTimeSync func(context.Context) (clientTimeSync, bool),
- onData map[format.Format]func(time.Duration, interface{}),
-) *clientDownloaderStream {
- return &clientDownloaderStream{
- isLeading: isLeading,
- httpClient: httpClient,
- playlistURL: playlistURL,
- initialPlaylist: initialPlaylist,
- logger: logger,
- rp: rp,
- onStreamTracks: onStreamTracks,
- onSetLeadingTimeSync: onSetLeadingTimeSync,
- onGetLeadingTimeSync: onGetLeadingTimeSync,
- onData: onData,
- }
-}
-
-func (d *clientDownloaderStream) run(ctx context.Context) error {
- initialPlaylist := d.initialPlaylist
- d.initialPlaylist = nil
- if initialPlaylist == nil {
- var err error
- initialPlaylist, err = d.downloadPlaylist(ctx)
- if err != nil {
- return err
- }
- }
-
- segmentQueue := newClientSegmentQueue()
-
- if initialPlaylist.Map != nil && initialPlaylist.Map.URI != "" {
- byts, err := d.downloadSegment(ctx, initialPlaylist.Map.URI, initialPlaylist.Map.Offset, initialPlaylist.Map.Limit)
- if err != nil {
- return err
- }
-
- proc, err := newClientProcessorFMP4(
- ctx,
- d.isLeading,
- byts,
- segmentQueue,
- d.logger,
- d.rp,
- d.onStreamTracks,
- d.onSetLeadingTimeSync,
- d.onGetLeadingTimeSync,
- d.onData,
- )
- if err != nil {
- return err
- }
-
- d.rp.add(proc)
- } else {
- proc := newClientProcessorMPEGTS(
- d.isLeading,
- segmentQueue,
- d.logger,
- d.rp,
- d.onStreamTracks,
- d.onSetLeadingTimeSync,
- d.onGetLeadingTimeSync,
- d.onData,
- )
- d.rp.add(proc)
- }
-
- err := d.fillSegmentQueue(ctx, initialPlaylist, segmentQueue)
- if err != nil {
- return err
- }
-
- for {
- ok := segmentQueue.waitUntilSizeIsBelow(ctx, 1)
- if !ok {
- return fmt.Errorf("terminated")
- }
-
- pl, err := d.downloadPlaylist(ctx)
- if err != nil {
- return err
- }
-
- err = d.fillSegmentQueue(ctx, pl, segmentQueue)
- if err != nil {
- return err
- }
- }
-}
-
-func (d *clientDownloaderStream) downloadPlaylist(ctx context.Context) (*m3u8.MediaPlaylist, error) {
- d.logger.Log(logger.Debug, "downloading stream playlist %s", d.playlistURL.String())
-
- pl, err := clientDownloadPlaylist(ctx, d.httpClient, d.playlistURL)
- if err != nil {
- return nil, err
- }
-
- plt, ok := pl.(*m3u8.MediaPlaylist)
- if !ok {
- return nil, fmt.Errorf("invalid playlist")
- }
-
- return plt, nil
-}
-
-func (d *clientDownloaderStream) downloadSegment(ctx context.Context,
- uri string, offset int64, limit int64,
-) ([]byte, error) {
- u, err := clientAbsoluteURL(d.playlistURL, uri)
- if err != nil {
- return nil, err
- }
-
- d.logger.Log(logger.Debug, "downloading segment %s", u)
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
- if err != nil {
- return nil, err
- }
-
- if limit != 0 {
- req.Header.Add("Range", "bytes="+strconv.FormatInt(offset, 10)+"-"+strconv.FormatInt(offset+limit-1, 10))
- }
-
- res, err := d.httpClient.Do(req)
- if err != nil {
- return nil, err
- }
- defer res.Body.Close()
-
- if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusPartialContent {
- return nil, fmt.Errorf("bad status code: %d", res.StatusCode)
- }
-
- byts, err := io.ReadAll(res.Body)
- if err != nil {
- return nil, err
- }
-
- return byts, nil
-}
-
-func (d *clientDownloaderStream) fillSegmentQueue(ctx context.Context,
- pl *m3u8.MediaPlaylist, segmentQueue *clientSegmentQueue,
-) error {
- pl.Segments = pl.Segments[:segmentsLen(pl.Segments)]
- var seg *gm3u8.MediaSegment
-
- if d.curSegmentID == nil {
- if !pl.Closed { // live stream: start from clientLiveStartingInvPosition
- seg = findSegmentWithInvPosition(pl.Segments, clientLiveStartingInvPosition)
- if seg == nil {
- return fmt.Errorf("there aren't enough segments to fill the buffer")
- }
- } else { // VOD stream: start from beginning
- if len(pl.Segments) == 0 {
- return fmt.Errorf("no segments found")
- }
- seg = pl.Segments[0]
- }
- } else {
- var invPos int
- seg, invPos = findSegmentWithID(pl.SeqNo, pl.Segments, *d.curSegmentID+1)
- if seg == nil {
- return fmt.Errorf("following segment not found or not ready yet")
- }
-
- d.logger.Log(logger.Debug, "segment inverse position: %d", invPos)
-
- if !pl.Closed && invPos > clientLiveMaxInvPosition {
- return fmt.Errorf("playback is too late")
- }
- }
-
- v := seg.SeqId
- d.curSegmentID = &v
-
- byts, err := d.downloadSegment(ctx, seg.URI, seg.Offset, seg.Limit)
- if err != nil {
- return err
- }
-
- segmentQueue.push(byts)
-
- if pl.Closed && pl.Segments[len(pl.Segments)-1] == seg {
- <-ctx.Done()
- return fmt.Errorf("stream has ended")
- }
-
- return nil
-}
diff --git a/internal/hls/client_processor_fmp4.go b/internal/hls/client_processor_fmp4.go
deleted file mode 100644
index 7060a413..00000000
--- a/internal/hls/client_processor_fmp4.go
+++ /dev/null
@@ -1,221 +0,0 @@
-package hls
-
-import (
- "context"
- "fmt"
- "time"
-
- "github.com/aler9/gortsplib/v2/pkg/codecs/h264"
- "github.com/aler9/gortsplib/v2/pkg/format"
-
- "github.com/aler9/rtsp-simple-server/internal/hls/fmp4"
-)
-
-func fmp4PickLeadingTrack(init *fmp4.Init) int {
- // pick first video track
- for _, track := range init.Tracks {
- switch track.Format.(type) {
- case *format.H264, *format.H265:
- return track.ID
- }
- }
-
- // otherwise, pick first track
- return init.Tracks[0].ID
-}
-
-type clientProcessorFMP4 struct {
- isLeading bool
- segmentQueue *clientSegmentQueue
- logger ClientLogger
- rp *clientRoutinePool
- onSetLeadingTimeSync func(clientTimeSync)
- onGetLeadingTimeSync func(context.Context) (clientTimeSync, bool)
- onData map[format.Format]func(time.Duration, interface{})
-
- init fmp4.Init
- leadingTrackID int
- trackProcs map[int]*clientProcessorFMP4Track
-
- // in
- subpartProcessed chan struct{}
-}
-
-func newClientProcessorFMP4(
- ctx context.Context,
- isLeading bool,
- initFile []byte,
- segmentQueue *clientSegmentQueue,
- logger ClientLogger,
- rp *clientRoutinePool,
- onStreamFormats func(context.Context, []format.Format) bool,
- onSetLeadingTimeSync func(clientTimeSync),
- onGetLeadingTimeSync func(context.Context) (clientTimeSync, bool),
- onData map[format.Format]func(time.Duration, interface{}),
-) (*clientProcessorFMP4, error) {
- p := &clientProcessorFMP4{
- isLeading: isLeading,
- segmentQueue: segmentQueue,
- logger: logger,
- rp: rp,
- onSetLeadingTimeSync: onSetLeadingTimeSync,
- onGetLeadingTimeSync: onGetLeadingTimeSync,
- onData: onData,
- subpartProcessed: make(chan struct{}, clientFMP4MaxPartTracksPerSegment),
- }
-
- err := p.init.Unmarshal(initFile)
- if err != nil {
- return nil, err
- }
-
- p.leadingTrackID = fmp4PickLeadingTrack(&p.init)
-
- tracks := make([]format.Format, len(p.init.Tracks))
- for i, track := range p.init.Tracks {
- tracks[i] = track.Format
- }
-
- ok := onStreamFormats(ctx, tracks)
- if !ok {
- return nil, fmt.Errorf("terminated")
- }
-
- return p, nil
-}
-
-func (p *clientProcessorFMP4) run(ctx context.Context) error {
- for {
- seg, ok := p.segmentQueue.pull(ctx)
- if !ok {
- return fmt.Errorf("terminated")
- }
-
- err := p.processSegment(ctx, seg)
- if err != nil {
- return err
- }
- }
-}
-
-func (p *clientProcessorFMP4) processSegment(ctx context.Context, byts []byte) error {
- var parts fmp4.Parts
- err := parts.Unmarshal(byts)
- if err != nil {
- return err
- }
-
- processingCount := 0
-
- for _, part := range parts {
- for _, track := range part.Tracks {
- if p.trackProcs == nil {
- var ts *clientTimeSyncFMP4
-
- if p.isLeading {
- if track.ID != p.leadingTrackID {
- continue
- }
-
- timeScale := func() uint32 {
- for _, track := range p.init.Tracks {
- if track.ID == p.leadingTrackID {
- return track.TimeScale
- }
- }
- return 0
- }()
- ts = newClientTimeSyncFMP4(timeScale, track.BaseTime)
- p.onSetLeadingTimeSync(ts)
- } else {
- rawTS, ok := p.onGetLeadingTimeSync(ctx)
- if !ok {
- return fmt.Errorf("terminated")
- }
-
- ts, ok = rawTS.(*clientTimeSyncFMP4)
- if !ok {
- return fmt.Errorf("stream playlists are mixed MPEGTS/FMP4")
- }
- }
-
- p.initializeTrackProcs(ts)
- }
-
- proc, ok := p.trackProcs[track.ID]
- if !ok {
- continue
- }
-
- if processingCount >= (clientFMP4MaxPartTracksPerSegment - 1) {
- return fmt.Errorf("too many part tracks at once")
- }
-
- select {
- case proc.queue <- track:
- case <-ctx.Done():
- return fmt.Errorf("terminated")
- }
- processingCount++
- }
- }
-
- for i := 0; i < processingCount; i++ {
- select {
- case <-p.subpartProcessed:
- case <-ctx.Done():
- return fmt.Errorf("terminated")
- }
- }
-
- return nil
-}
-
-func (p *clientProcessorFMP4) onPartTrackProcessed(ctx context.Context) {
- select {
- case p.subpartProcessed <- struct{}{}:
- case <-ctx.Done():
- }
-}
-
-func (p *clientProcessorFMP4) initializeTrackProcs(ts *clientTimeSyncFMP4) {
- p.trackProcs = make(map[int]*clientProcessorFMP4Track)
-
- for _, track := range p.init.Tracks {
- var cb func(time.Duration, []byte) error
-
- cb2, ok := p.onData[track.Format]
- if !ok {
- cb2 = func(time.Duration, interface{}) {
- }
- }
-
- switch track.Format.(type) {
- case *format.H264, *format.H265:
- cb = func(pts time.Duration, payload []byte) error {
- nalus, err := h264.AVCCUnmarshal(payload)
- if err != nil {
- return err
- }
-
- cb2(pts, nalus)
- return nil
- }
-
- case *format.MPEG4Audio, *format.Opus:
- cb = func(pts time.Duration, payload []byte) error {
- cb2(pts, payload)
- return nil
- }
- }
-
- proc := newClientProcessorFMP4Track(
- track.TimeScale,
- ts,
- p.onPartTrackProcessed,
- cb,
- )
- p.rp.add(proc)
- p.trackProcs[track.ID] = proc
- }
-}
diff --git a/internal/hls/client_processor_fmp4_track.go b/internal/hls/client_processor_fmp4_track.go
deleted file mode 100644
index 58408fc6..00000000
--- a/internal/hls/client_processor_fmp4_track.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package hls
-
-import (
- "context"
- "time"
-
- "github.com/aler9/rtsp-simple-server/internal/hls/fmp4"
-)
-
-type clientProcessorFMP4Track struct {
- timeScale uint32
- ts *clientTimeSyncFMP4
- onPartTrackProcessed func(context.Context)
- onEntry func(time.Duration, []byte) error
-
- // in
- queue chan *fmp4.PartTrack
-}
-
-func newClientProcessorFMP4Track(
- timeScale uint32,
- ts *clientTimeSyncFMP4,
- onPartTrackProcessed func(context.Context),
- onEntry func(time.Duration, []byte) error,
-) *clientProcessorFMP4Track {
- return &clientProcessorFMP4Track{
- timeScale: timeScale,
- ts: ts,
- onPartTrackProcessed: onPartTrackProcessed,
- onEntry: onEntry,
- queue: make(chan *fmp4.PartTrack, clientFMP4MaxPartTracksPerSegment),
- }
-}
-
-func (t *clientProcessorFMP4Track) run(ctx context.Context) error {
- for {
- select {
- case entry := <-t.queue:
- err := t.processPartTrack(ctx, entry)
- if err != nil {
- return err
- }
-
- t.onPartTrackProcessed(ctx)
-
- case <-ctx.Done():
- return nil
- }
- }
-}
-
-func (t *clientProcessorFMP4Track) processPartTrack(ctx context.Context, pt *fmp4.PartTrack) error {
- rawDTS := pt.BaseTime
-
- for _, sample := range pt.Samples {
- pts, err := t.ts.convertAndSync(ctx, t.timeScale, rawDTS, sample.PTSOffset)
- if err != nil {
- return err
- }
-
- if pts >= 0 { // silently discard packets prior to the first packet of the leading track
- err = t.onEntry(pts, sample.Payload)
- if err != nil {
- return err
- }
- }
-
- rawDTS += uint64(sample.Duration)
- }
-
- return nil
-}
diff --git a/internal/hls/client_processor_mpegts.go b/internal/hls/client_processor_mpegts.go
deleted file mode 100644
index 62451221..00000000
--- a/internal/hls/client_processor_mpegts.go
+++ /dev/null
@@ -1,221 +0,0 @@
-package hls
-
-import (
- "bytes"
- "context"
- "fmt"
- "strings"
- "time"
-
- "github.com/aler9/gortsplib/v2/pkg/codecs/h264"
- "github.com/aler9/gortsplib/v2/pkg/codecs/mpeg4audio"
- "github.com/aler9/gortsplib/v2/pkg/format"
- "github.com/asticode/go-astits"
-
- "github.com/aler9/rtsp-simple-server/internal/hls/mpegts"
- "github.com/aler9/rtsp-simple-server/internal/logger"
-)
-
-func mpegtsPickLeadingTrack(mpegtsTracks []*mpegts.Track) uint16 {
- // pick first video track
- for _, mt := range mpegtsTracks {
- if _, ok := mt.Format.(*format.H264); ok {
- return mt.ES.ElementaryPID
- }
- }
-
- // otherwise, pick first track
- return mpegtsTracks[0].ES.ElementaryPID
-}
-
-type clientProcessorMPEGTS struct {
- isLeading bool
- segmentQueue *clientSegmentQueue
- logger ClientLogger
- rp *clientRoutinePool
- onStreamFormats func(context.Context, []format.Format) bool
- onSetLeadingTimeSync func(clientTimeSync)
- onGetLeadingTimeSync func(context.Context) (clientTimeSync, bool)
- onData map[format.Format]func(time.Duration, interface{})
-
- mpegtsTracks []*mpegts.Track
- leadingTrackPID uint16
- trackProcs map[uint16]*clientProcessorMPEGTSTrack
-}
-
-func newClientProcessorMPEGTS(
- isLeading bool,
- segmentQueue *clientSegmentQueue,
- logger ClientLogger,
- rp *clientRoutinePool,
- onStreamFormats func(context.Context, []format.Format) bool,
- onSetLeadingTimeSync func(clientTimeSync),
- onGetLeadingTimeSync func(context.Context) (clientTimeSync, bool),
- onData map[format.Format]func(time.Duration, interface{}),
-) *clientProcessorMPEGTS {
- return &clientProcessorMPEGTS{
- isLeading: isLeading,
- segmentQueue: segmentQueue,
- logger: logger,
- rp: rp,
- onStreamFormats: onStreamFormats,
- onSetLeadingTimeSync: onSetLeadingTimeSync,
- onGetLeadingTimeSync: onGetLeadingTimeSync,
- onData: onData,
- }
-}
-
-func (p *clientProcessorMPEGTS) run(ctx context.Context) error {
- for {
- seg, ok := p.segmentQueue.pull(ctx)
- if !ok {
- return fmt.Errorf("terminated")
- }
-
- err := p.processSegment(ctx, seg)
- if err != nil {
- return err
- }
- }
-}
-
-func (p *clientProcessorMPEGTS) processSegment(ctx context.Context, byts []byte) error {
- if p.mpegtsTracks == nil {
- var err error
- p.mpegtsTracks, err = mpegts.FindTracks(byts)
- if err != nil {
- return err
- }
-
- p.leadingTrackPID = mpegtsPickLeadingTrack(p.mpegtsTracks)
-
- tracks := make([]format.Format, len(p.mpegtsTracks))
- for i, mt := range p.mpegtsTracks {
- tracks[i] = mt.Format
- }
-
- ok := p.onStreamFormats(ctx, tracks)
- if !ok {
- return fmt.Errorf("terminated")
- }
- }
-
- dem := astits.NewDemuxer(context.Background(), bytes.NewReader(byts))
-
- for {
- data, err := dem.NextData()
- if err != nil {
- if err == astits.ErrNoMorePackets {
- return nil
- }
- if strings.HasPrefix(err.Error(), "astits: parsing PES data failed") {
- continue
- }
- return err
- }
-
- if data.PES == nil {
- continue
- }
-
- if data.PES.Header.OptionalHeader == nil ||
- data.PES.Header.OptionalHeader.PTSDTSIndicator == astits.PTSDTSIndicatorNoPTSOrDTS ||
- data.PES.Header.OptionalHeader.PTSDTSIndicator == astits.PTSDTSIndicatorIsForbidden {
- return fmt.Errorf("PTS is missing")
- }
-
- if p.trackProcs == nil {
- var ts *clientTimeSyncMPEGTS
-
- if p.isLeading {
- if data.PID != p.leadingTrackPID {
- continue
- }
-
- var dts int64
- if data.PES.Header.OptionalHeader.PTSDTSIndicator == astits.PTSDTSIndicatorBothPresent {
- dts = data.PES.Header.OptionalHeader.DTS.Base
- } else {
- dts = data.PES.Header.OptionalHeader.PTS.Base
- }
-
- ts = newClientTimeSyncMPEGTS(dts)
- p.onSetLeadingTimeSync(ts)
- } else {
- rawTS, ok := p.onGetLeadingTimeSync(ctx)
- if !ok {
- return fmt.Errorf("terminated")
- }
-
- ts, ok = rawTS.(*clientTimeSyncMPEGTS)
- if !ok {
- return fmt.Errorf("stream playlists are mixed MPEGTS/FMP4")
- }
- }
-
- p.initializeTrackProcs(ts)
- }
-
- proc, ok := p.trackProcs[data.PID]
- if !ok {
- continue
- }
-
- select {
- case proc.queue <- data.PES:
- case <-ctx.Done():
- }
- }
-}
-
-func (p *clientProcessorMPEGTS) initializeTrackProcs(ts *clientTimeSyncMPEGTS) {
- p.trackProcs = make(map[uint16]*clientProcessorMPEGTSTrack)
-
- for _, track := range p.mpegtsTracks {
- var cb func(time.Duration, []byte) error
-
- cb2, ok := p.onData[track.Format]
- if !ok {
- cb2 = func(time.Duration, interface{}) {
- }
- }
-
- switch track.Format.(type) {
- case *format.H264:
- cb = func(pts time.Duration, payload []byte) error {
- nalus, err := h264.AnnexBUnmarshal(payload)
- if err != nil {
- p.logger.Log(logger.Warn, "unable to decode Annex-B: %s", err)
- return nil
- }
-
- cb2(pts, nalus)
- return nil
- }
-
- case *format.MPEG4Audio:
- cb = func(pts time.Duration, payload []byte) error {
- var adtsPkts mpeg4audio.ADTSPackets
- err := adtsPkts.Unmarshal(payload)
- if err != nil {
- return fmt.Errorf("unable to decode ADTS: %s", err)
- }
-
- for i, pkt := range adtsPkts {
- cb2(
- pts+time.Duration(i)*mpeg4audio.SamplesPerAccessUnit*time.Second/time.Duration(pkt.SampleRate),
- pkt.AU)
- }
-
- return nil
- }
- }
-
- proc := newClientProcessorMPEGTSTrack(
- ts,
- cb,
- )
- p.rp.add(proc)
- p.trackProcs[track.ES.ElementaryPID] = proc
- }
-}
diff --git a/internal/hls/client_processor_mpegts_track.go b/internal/hls/client_processor_mpegts_track.go
deleted file mode 100644
index 5831ed25..00000000
--- a/internal/hls/client_processor_mpegts_track.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package hls
-
-import (
- "context"
- "time"
-
- "github.com/asticode/go-astits"
-)
-
-type clientProcessorMPEGTSTrack struct {
- ts *clientTimeSyncMPEGTS
- onEntry func(time.Duration, []byte) error
-
- queue chan *astits.PESData
-}
-
-func newClientProcessorMPEGTSTrack(
- ts *clientTimeSyncMPEGTS,
- onEntry func(time.Duration, []byte) error,
-) *clientProcessorMPEGTSTrack {
- return &clientProcessorMPEGTSTrack{
- ts: ts,
- onEntry: onEntry,
- queue: make(chan *astits.PESData, clientMPEGTSEntryQueueSize),
- }
-}
-
-func (t *clientProcessorMPEGTSTrack) run(ctx context.Context) error {
- for {
- select {
- case pes := <-t.queue:
- err := t.processEntry(ctx, pes)
- if err != nil {
- return err
- }
-
- case <-ctx.Done():
- return nil
- }
- }
-}
-
-func (t *clientProcessorMPEGTSTrack) processEntry(ctx context.Context, pes *astits.PESData) error {
- rawPTS := pes.Header.OptionalHeader.PTS.Base
- var rawDTS int64
- if pes.Header.OptionalHeader.PTSDTSIndicator == astits.PTSDTSIndicatorBothPresent {
- rawDTS = pes.Header.OptionalHeader.DTS.Base
- } else {
- rawDTS = rawPTS
- }
-
- pts, err := t.ts.convertAndSync(ctx, rawDTS, rawPTS)
- if err != nil {
- return err
- }
-
- // silently discard packets prior to the first packet of the leading track
- if pts < 0 {
- return nil
- }
-
- return t.onEntry(pts, pes.Data)
-}
diff --git a/internal/hls/client_routine_pool.go b/internal/hls/client_routine_pool.go
deleted file mode 100644
index ca7f5944..00000000
--- a/internal/hls/client_routine_pool.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package hls
-
-import (
- "context"
- "sync"
-)
-
-type clientRoutinePoolRunnable interface {
- run(context.Context) error
-}
-
-type clientRoutinePool struct {
- ctx context.Context
- ctxCancel func()
- wg sync.WaitGroup
-
- err chan error
-}
-
-func newClientRoutinePool() *clientRoutinePool {
- ctx, ctxCancel := context.WithCancel(context.Background())
-
- return &clientRoutinePool{
- ctx: ctx,
- ctxCancel: ctxCancel,
- err: make(chan error),
- }
-}
-
-func (rp *clientRoutinePool) close() {
- rp.ctxCancel()
- rp.wg.Wait()
-}
-
-func (rp *clientRoutinePool) errorChan() chan error {
- return rp.err
-}
-
-func (rp *clientRoutinePool) add(r clientRoutinePoolRunnable) {
- rp.wg.Add(1)
- go func() {
- defer rp.wg.Done()
-
- err := r.run(rp.ctx)
- if err != nil {
- select {
- case rp.err <- err:
- case <-rp.ctx.Done():
- }
- }
- }()
-}
diff --git a/internal/hls/client_segment_queue.go b/internal/hls/client_segment_queue.go
deleted file mode 100644
index 67a4e43d..00000000
--- a/internal/hls/client_segment_queue.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package hls
-
-import (
- "context"
- "sync"
-)
-
-type clientSegmentQueue struct {
- mutex sync.Mutex
- queue [][]byte
- didPush chan struct{}
- didPull chan struct{}
-}
-
-func newClientSegmentQueue() *clientSegmentQueue {
- return &clientSegmentQueue{
- didPush: make(chan struct{}),
- didPull: make(chan struct{}),
- }
-}
-
-func (q *clientSegmentQueue) push(seg []byte) {
- q.mutex.Lock()
-
- queueWasEmpty := (len(q.queue) == 0)
- q.queue = append(q.queue, seg)
-
- if queueWasEmpty {
- close(q.didPush)
- q.didPush = make(chan struct{})
- }
-
- q.mutex.Unlock()
-}
-
-func (q *clientSegmentQueue) waitUntilSizeIsBelow(ctx context.Context, n int) bool {
- q.mutex.Lock()
-
- for len(q.queue) > n {
- q.mutex.Unlock()
-
- select {
- case <-q.didPull:
- case <-ctx.Done():
- return false
- }
-
- q.mutex.Lock()
- }
-
- q.mutex.Unlock()
- return true
-}
-
-func (q *clientSegmentQueue) pull(ctx context.Context) ([]byte, bool) {
- q.mutex.Lock()
-
- for len(q.queue) == 0 {
- didPush := q.didPush
- q.mutex.Unlock()
-
- select {
- case <-didPush:
- case <-ctx.Done():
- return nil, false
- }
-
- q.mutex.Lock()
- }
-
- var seg []byte
- seg, q.queue = q.queue[0], q.queue[1:]
-
- close(q.didPull)
- q.didPull = make(chan struct{})
-
- q.mutex.Unlock()
- return seg, true
-}
diff --git a/internal/hls/client_test.go b/internal/hls/client_test.go
deleted file mode 100644
index 1ec10dfd..00000000
--- a/internal/hls/client_test.go
+++ /dev/null
@@ -1,445 +0,0 @@
-package hls
-
-import (
- "bytes"
- "context"
- "io"
- "log"
- "net"
- "net/http"
- "os"
- "testing"
- "time"
-
- "github.com/aler9/gortsplib/v2/pkg/codecs/h264"
- "github.com/aler9/gortsplib/v2/pkg/format"
- "github.com/asticode/go-astits"
- "github.com/gin-gonic/gin"
- "github.com/stretchr/testify/require"
-
- "github.com/aler9/rtsp-simple-server/internal/hls/fmp4"
- "github.com/aler9/rtsp-simple-server/internal/logger"
-)
-
-type testLogger struct{}
-
-func (testLogger) Log(level logger.Level, format string, args ...interface{}) {
- log.Printf(format, args...)
-}
-
-var serverCert = []byte(`-----BEGIN CERTIFICATE-----
-MIIDazCCAlOgAwIBAgIUXw1hEC3LFpTsllv7D3ARJyEq7sIwDQYJKoZIhvcNAQEL
-BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
-GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDEyMTMxNzQ0NThaFw0zMDEy
-MTExNzQ0NThaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
-HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
-AQUAA4IBDwAwggEKAoIBAQDG8DyyS51810GsGwgWr5rjJK7OE1kTTLSNEEKax8Bj
-zOyiaz8rA2JGl2VUEpi2UjDr9Cm7nd+YIEVs91IIBOb7LGqObBh1kGF3u5aZxLkv
-NJE+HrLVvUhaDobK2NU+Wibqc/EI3DfUkt1rSINvv9flwTFu1qHeuLWhoySzDKEp
-OzYxpFhwjVSokZIjT4Red3OtFz7gl2E6OAWe2qoh5CwLYVdMWtKR0Xuw3BkDPk9I
-qkQKx3fqv97LPEzhyZYjDT5WvGrgZ1WDAN3booxXF3oA1H3GHQc4m/vcLatOtb8e
-nI59gMQLEbnp08cl873bAuNuM95EZieXTHNbwUnq5iybAgMBAAGjUzBRMB0GA1Ud
-DgQWBBQBKhJh8eWu0a4au9X/2fKhkFX2vjAfBgNVHSMEGDAWgBQBKhJh8eWu0a4a
-u9X/2fKhkFX2vjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBj
-3aCW0YPKukYgVK9cwN0IbVy/D0C1UPT4nupJcy/E0iC7MXPZ9D/SZxYQoAkdptdO
-xfI+RXkpQZLdODNx9uvV+cHyZHZyjtE5ENu/i5Rer2cWI/mSLZm5lUQyx+0KZ2Yu
-tEI1bsebDK30msa8QSTn0WidW9XhFnl3gRi4wRdimcQapOWYVs7ih+nAlSvng7NI
-XpAyRs8PIEbpDDBMWnldrX4TP6EWYUi49gCp8OUDRREKX3l6Ls1vZ02F34yHIt/7
-7IV/XSKG096bhW+icKBWV0IpcEsgTzPK1J1hMxgjhzIMxGboAeUU+kidthOob6Sd
-XQxaORfgM//NzX9LhUPk
------END CERTIFICATE-----
-`)
-
-var serverKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
-MIIEogIBAAKCAQEAxvA8skudfNdBrBsIFq+a4ySuzhNZE0y0jRBCmsfAY8zsoms/
-KwNiRpdlVBKYtlIw6/Qpu53fmCBFbPdSCATm+yxqjmwYdZBhd7uWmcS5LzSRPh6y
-1b1IWg6GytjVPlom6nPxCNw31JLda0iDb7/X5cExbtah3ri1oaMkswyhKTs2MaRY
-cI1UqJGSI0+EXndzrRc+4JdhOjgFntqqIeQsC2FXTFrSkdF7sNwZAz5PSKpECsd3
-6r/eyzxM4cmWIw0+Vrxq4GdVgwDd26KMVxd6ANR9xh0HOJv73C2rTrW/HpyOfYDE
-CxG56dPHJfO92wLjbjPeRGYnl0xzW8FJ6uYsmwIDAQABAoIBACi0BKcyQ3HElSJC
-kaAao+Uvnzh4yvPg8Nwf5JDIp/uDdTMyIEWLtrLczRWrjGVZYbsVROinP5VfnPTT
-kYwkfKINj2u+gC6lsNuPnRuvHXikF8eO/mYvCTur1zZvsQnF5kp4GGwIqr+qoPUP
-bB0UMndG1PdpoMryHe+JcrvTrLHDmCeH10TqOwMsQMLHYLkowvxwJWsmTY7/Qr5S
-Wm3PPpOcW2i0uyPVuyuv4yD1368fqnqJ8QFsQp1K6QtYsNnJ71Hut1/IoxK/e6hj
-5Z+byKtHVtmcLnABuoOT7BhleJNFBksX9sh83jid4tMBgci+zXNeGmgqo2EmaWAb
-agQslkECgYEA8B1rzjOHVQx/vwSzDa4XOrpoHQRfyElrGNz9JVBvnoC7AorezBXQ
-M9WTHQIFTGMjzD8pb+YJGi3gj93VN51r0SmJRxBaBRh1ZZI9kFiFzngYev8POgD3
-ygmlS3kTHCNxCK/CJkB+/jMBgtPj5ygDpCWVcTSuWlQFphePkW7jaaECgYEA1Blz
-ulqgAyJHZaqgcbcCsI2q6m527hVr9pjzNjIVmkwu38yS9RTCgdlbEVVDnS0hoifl
-+jVMEGXjF3xjyMvL50BKbQUH+KAa+V4n1WGlnZOxX9TMny8MBjEuSX2+362vQ3BX
-4vOlX00gvoc+sY+lrzvfx/OdPCHQGVYzoKCxhLsCgYA07HcviuIAV/HsO2/vyvhp
-xF5gTu+BqNUHNOZDDDid+ge+Jre2yfQLCL8VPLXIQW3Jff53IH/PGl+NtjphuLvj
-7UDJvgvpZZuymIojP6+2c3gJ3CASC9aR3JBnUzdoE1O9s2eaoMqc4scpe+SWtZYf
-3vzSZ+cqF6zrD/Rf/M35IQKBgHTU4E6ShPm09CcoaeC5sp2WK8OevZw/6IyZi78a
-r5Oiy18zzO97U/k6xVMy6F+38ILl/2Rn31JZDVJujniY6eSkIVsUHmPxrWoXV1HO
-y++U32uuSFiXDcSLarfIsE992MEJLSAynbF1Rsgsr3gXbGiuToJRyxbIeVy7gwzD
-94TpAoGAY4/PejWQj9psZfAhyk5dRGra++gYRQ/gK1IIc1g+Dd2/BxbT/RHr05GK
-6vwrfjsoRyMWteC1SsNs/CurjfQ/jqCfHNP5XPvxgd5Ec8sRJIiV7V5RTuWJsPu1
-+3K6cnKEyg+0ekYmLertRFIY6SwWmY1fyKgTvxudMcsBY7dC4xs=
------END RSA PRIVATE KEY-----
-`)
-
-func writeTempFile(byts []byte) (string, error) {
- tmpf, err := os.CreateTemp(os.TempDir(), "rtsp-")
- if err != nil {
- return "", err
- }
- defer tmpf.Close()
-
- _, err = tmpf.Write(byts)
- if err != nil {
- return "", err
- }
-
- return tmpf.Name(), nil
-}
-
-func mpegtsSegment(w io.Writer) {
- mux := astits.NewMuxer(context.Background(), w)
- mux.AddElementaryStream(astits.PMTElementaryStream{
- ElementaryPID: 256,
- StreamType: astits.StreamTypeH264Video,
- })
- mux.SetPCRPID(256)
- mux.WriteTables()
-
- enc, _ := h264.AnnexBMarshal([][]byte{
- {7, 1, 2, 3}, // SPS
- {8}, // PPS
- {5}, // IDR
- })
-
- mux.WriteData(&astits.MuxerData{
- PID: 256,
- PES: &astits.PESData{
- Header: &astits.PESHeader{
- OptionalHeader: &astits.PESOptionalHeader{
- MarkerBits: 2,
- PTSDTSIndicator: astits.PTSDTSIndicatorBothPresent,
- PTS: &astits.ClockReference{Base: 90000}, // +1 sec
- DTS: &astits.ClockReference{Base: 0x1FFFFFFFF - 90000 + 1}, // -1 sec
- },
- StreamID: 224, // = video
- },
- Data: enc,
- },
- })
-}
-
-func mp4Init(t *testing.T, w io.Writer) {
- i := &fmp4.Init{
- Tracks: []*fmp4.InitTrack{
- {
- ID: 1,
- TimeScale: 90000,
- Format: &format.H264{
- SPS: []byte{
- 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
- 0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04,
- 0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9,
- 0x20,
- },
- PPS: []byte{0x01, 0x02, 0x03, 0x04},
- },
- },
- },
- }
-
- byts, err := i.Marshal()
- require.NoError(t, err)
-
- _, err = w.Write(byts)
- require.NoError(t, err)
-}
-
-func mp4Segment(t *testing.T, w io.Writer) {
- payload, _ := h264.AVCCMarshal([][]byte{
- {7, 1, 2, 3}, // SPS
- {8}, // PPS
- {5}, // IDR
- })
-
- p := &fmp4.Part{
- Tracks: []*fmp4.PartTrack{
- {
- ID: 1,
- IsVideo: true,
- Samples: []*fmp4.PartSample{{
- Duration: 90000 / 30,
- PTSOffset: 90000 * 2,
- Payload: payload,
- }},
- },
- },
- }
-
- byts, err := p.Marshal()
- require.NoError(t, err)
-
- _, err = w.Write(byts)
- require.NoError(t, err)
-}
-
-type testHLSServer struct {
- s *http.Server
-}
-
-func newTestHLSServer(router http.Handler, isTLS bool) (*testHLSServer, error) {
- ln, err := net.Listen("tcp", "localhost:5780")
- if err != nil {
- return nil, err
- }
-
- s := &testHLSServer{
- s: &http.Server{Handler: router},
- }
-
- if isTLS {
- go func() {
- serverCertFpath, err := writeTempFile(serverCert)
- if err != nil {
- panic(err)
- }
- defer os.Remove(serverCertFpath)
-
- serverKeyFpath, err := writeTempFile(serverKey)
- if err != nil {
- panic(err)
- }
- defer os.Remove(serverKeyFpath)
-
- s.s.ServeTLS(ln, serverCertFpath, serverKeyFpath)
- }()
- } else {
- go s.s.Serve(ln)
- }
-
- return s, nil
-}
-
-func (s *testHLSServer) close() {
- s.s.Shutdown(context.Background())
-}
-
-func TestClientMPEGTS(t *testing.T) {
- for _, ca := range []string{
- "plain",
- "tls",
- "segment with query",
- } {
- t.Run(ca, func(t *testing.T) {
- gin.SetMode(gin.ReleaseMode)
- router := gin.New()
-
- segment := "segment.ts"
- if ca == "segment with query" {
- segment = "segment.ts?key=val"
- }
- sent := false
-
- router.GET("/stream.m3u8", func(ctx *gin.Context) {
- if sent {
- return
- }
- sent = true
-
- ctx.Writer.Header().Set("Content-Type", `application/x-mpegURL`)
- io.Copy(ctx.Writer, bytes.NewReader([]byte(`#EXTM3U
- #EXT-X-VERSION:3
- #EXT-X-ALLOW-CACHE:NO
- #EXT-X-TARGETDURATION:2
- #EXT-X-MEDIA-SEQUENCE:0
- #EXTINF:2,
- `+segment+`
- #EXT-X-ENDLIST
- `)))
- })
-
- router.GET("/segment.ts", func(ctx *gin.Context) {
- if ca == "segment with query" {
- require.Equal(t, "val", ctx.Query("key"))
- }
- ctx.Writer.Header().Set("Content-Type", `video/MP2T`)
- mpegtsSegment(ctx.Writer)
- })
-
- s, err := newTestHLSServer(router, ca == "tls")
- require.NoError(t, err)
- defer s.close()
-
- packetRecv := make(chan struct{})
-
- prefix := "http"
- if ca == "tls" {
- prefix = "https"
- }
-
- c, err := NewClient(
- prefix+"://localhost:5780/stream.m3u8",
- "33949E05FFFB5FF3E8AA16F8213A6251B4D9363804BA53233C4DA9A46D6F2739",
- testLogger{},
- )
- require.NoError(t, err)
-
- onH264 := func(pts time.Duration, unit interface{}) {
- require.Equal(t, 2*time.Second, pts)
- require.Equal(t, [][]byte{
- {7, 1, 2, 3},
- {8},
- {5},
- }, unit)
- close(packetRecv)
- }
-
- c.OnTracks(func(tracks []format.Format) error {
- require.Equal(t, 1, len(tracks))
- require.Equal(t, &format.H264{
- PayloadTyp: 96,
- PacketizationMode: 1,
- }, tracks[0])
- c.OnData(tracks[0], onH264)
- return nil
- })
-
- c.Start()
-
- <-packetRecv
-
- c.Close()
- <-c.Wait()
- })
- }
-}
-
-func TestClientFMP4(t *testing.T) {
- gin.SetMode(gin.ReleaseMode)
- router := gin.New()
-
- router.GET("/stream.m3u8", func(ctx *gin.Context) {
- ctx.Writer.Header().Set("Content-Type", `application/x-mpegURL`)
- io.Copy(ctx.Writer, bytes.NewReader([]byte(`#EXTM3U
- #EXT-X-VERSION:7
- #EXT-X-MEDIA-SEQUENCE:20
- #EXT-X-INDEPENDENT-SEGMENTS
- #EXT-X-MAP:URI="init.mp4"
- #EXTINF:2,
- segment.mp4
- #EXT-X-ENDLIST
- `)))
- })
-
- router.GET("/init.mp4", func(ctx *gin.Context) {
- ctx.Writer.Header().Set("Content-Type", `video/mp4`)
- mp4Init(t, ctx.Writer)
- })
-
- router.GET("/segment.mp4", func(ctx *gin.Context) {
- ctx.Writer.Header().Set("Content-Type", `video/mp4`)
- mp4Segment(t, ctx.Writer)
- })
-
- s, err := newTestHLSServer(router, false)
- require.NoError(t, err)
- defer s.close()
-
- packetRecv := make(chan struct{})
-
- onH264 := func(pts time.Duration, unit interface{}) {
- require.Equal(t, 2*time.Second, pts)
- require.Equal(t, [][]byte{
- {7, 1, 2, 3},
- {8},
- {5},
- }, unit)
- close(packetRecv)
- }
-
- c, err := NewClient(
- "http://localhost:5780/stream.m3u8",
- "",
- testLogger{},
- )
- require.NoError(t, err)
-
- c.OnTracks(func(tracks []format.Format) error {
- require.Equal(t, 1, len(tracks))
- _, ok := tracks[0].(*format.H264)
- require.Equal(t, true, ok)
- c.OnData(tracks[0], onH264)
- return nil
- })
-
- c.Start()
-
- <-packetRecv
-
- c.Close()
- <-c.Wait()
-}
-
-func TestClientInvalidSequenceID(t *testing.T) {
- router := gin.New()
- firstPlaylist := true
-
- router.GET("/stream.m3u8", func(ctx *gin.Context) {
- ctx.Writer.Header().Set("Content-Type", `application/x-mpegURL`)
-
- if firstPlaylist {
- firstPlaylist = false
- io.Copy(ctx.Writer, bytes.NewReader([]byte(
- `#EXTM3U
- #EXT-X-VERSION:3
- #EXT-X-ALLOW-CACHE:NO
- #EXT-X-TARGETDURATION:2
- #EXT-X-MEDIA-SEQUENCE:2
- #EXTINF:2,
- segment1.ts
- #EXTINF:2,
- segment1.ts
- #EXTINF:2,
- segment1.ts
- `)))
- } else {
- io.Copy(ctx.Writer, bytes.NewReader([]byte(
- `#EXTM3U
- #EXT-X-VERSION:3
- #EXT-X-ALLOW-CACHE:NO
- #EXT-X-TARGETDURATION:2
- #EXT-X-MEDIA-SEQUENCE:4
- #EXTINF:2,
- segment1.ts
- #EXTINF:2,
- segment1.ts
- #EXTINF:2,
- segment1.ts
- `)))
- }
- })
-
- router.GET("/segment1.ts", func(ctx *gin.Context) {
- ctx.Writer.Header().Set("Content-Type", `video/MP2T`)
- mpegtsSegment(ctx.Writer)
- })
-
- s, err := newTestHLSServer(router, false)
- require.NoError(t, err)
- defer s.close()
-
- c, err := NewClient(
- "http://localhost:5780/stream.m3u8",
- "",
- testLogger{},
- )
- require.NoError(t, err)
-
- c.OnTracks(func(tracks []format.Format) error {
- return nil
- })
-
- c.Start()
-
- err = <-c.Wait()
- require.EqualError(t, err, "following segment not found or not ready yet")
-
- c.Close()
-}
diff --git a/internal/hls/client_timesync_fmp4.go b/internal/hls/client_timesync_fmp4.go
deleted file mode 100644
index 5289c689..00000000
--- a/internal/hls/client_timesync_fmp4.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package hls
-
-import (
- "context"
- "fmt"
- "time"
-)
-
-func durationGoToMp4(v time.Duration, timeScale uint32) uint64 {
- timeScale64 := uint64(timeScale)
- secs := v / time.Second
- dec := v % time.Second
- return uint64(secs)*timeScale64 + uint64(dec)*timeScale64/uint64(time.Second)
-}
-
-func durationMp4ToGo(v uint64, timeScale uint32) time.Duration {
- timeScale64 := uint64(timeScale)
- secs := v / timeScale64
- dec := v % timeScale64
- return time.Duration(secs)*time.Second + time.Duration(dec)*time.Second/time.Duration(timeScale64)
-}
-
-type clientTimeSyncFMP4 struct {
- startRTC time.Time
- startDTS time.Duration
-}
-
-func newClientTimeSyncFMP4(timeScale uint32, baseTime uint64) *clientTimeSyncFMP4 {
- return &clientTimeSyncFMP4{
- startRTC: time.Now(),
- startDTS: durationMp4ToGo(baseTime, timeScale),
- }
-}
-
-func (ts *clientTimeSyncFMP4) convertAndSync(ctx context.Context, timeScale uint32,
- rawDTS uint64, ptsOffset int32,
-) (time.Duration, error) {
- pts := durationMp4ToGo(rawDTS+uint64(ptsOffset), timeScale)
- dts := durationMp4ToGo(rawDTS, timeScale)
-
- pts -= ts.startDTS
- dts -= ts.startDTS
-
- elapsed := time.Since(ts.startRTC)
- if dts > elapsed {
- diff := dts - elapsed
- if diff > clientMaxDTSRTCDiff {
- return 0, fmt.Errorf("difference between DTS and RTC is too big")
- }
-
- select {
- case <-time.After(diff):
- case <-ctx.Done():
- return 0, fmt.Errorf("terminated")
- }
- }
-
- return pts, nil
-}
diff --git a/internal/hls/client_timesync_mpegts.go b/internal/hls/client_timesync_mpegts.go
deleted file mode 100644
index 9ffcc0b1..00000000
--- a/internal/hls/client_timesync_mpegts.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package hls
-
-import (
- "context"
- "fmt"
- "sync"
- "time"
-
- "github.com/aler9/rtsp-simple-server/internal/hls/mpegtstimedec"
-)
-
-type clientTimeSyncMPEGTS struct {
- startRTC time.Time
- td *mpegtstimedec.Decoder
- mutex sync.Mutex
-}
-
-func newClientTimeSyncMPEGTS(startDTS int64) *clientTimeSyncMPEGTS {
- return &clientTimeSyncMPEGTS{
- startRTC: time.Now(),
- td: mpegtstimedec.New(startDTS),
- }
-}
-
-func (ts *clientTimeSyncMPEGTS) convertAndSync(ctx context.Context, rawDTS int64, rawPTS int64) (time.Duration, error) {
- ts.mutex.Lock()
- dts := ts.td.Decode(rawDTS)
- pts := ts.td.Decode(rawPTS)
- ts.mutex.Unlock()
-
- elapsed := time.Since(ts.startRTC)
- if dts > elapsed {
- diff := dts - elapsed
- if diff > clientMaxDTSRTCDiff {
- return 0, fmt.Errorf("difference between DTS and RTC is too big")
- }
-
- select {
- case <-time.After(diff):
- case <-ctx.Done():
- return 0, fmt.Errorf("terminated")
- }
- }
-
- return pts, nil
-}
diff --git a/internal/hls/codecparameters.go b/internal/hls/codecparameters.go
deleted file mode 100644
index cbc26538..00000000
--- a/internal/hls/codecparameters.go
+++ /dev/null
@@ -1,143 +0,0 @@
-package hls
-
-import (
- "encoding/hex"
- "fmt"
- "strconv"
- "strings"
-
- "github.com/aler9/gortsplib/v2/pkg/codecs/h265"
- "github.com/aler9/gortsplib/v2/pkg/format"
-)
-
-func encodeProfileSpace(v uint8) string {
- switch v {
- case 1:
- return "A"
- case 2:
- return "B"
- case 3:
- return "C"
- }
- return ""
-}
-
-func encodeCompatibilityFlag(v [32]bool) string {
- var o uint32
- for i, b := range v {
- if b {
- o |= 1 << i
- }
- }
- return fmt.Sprintf("%x", o)
-}
-
-func encodeGeneralTierFlag(v uint8) string {
- if v > 0 {
- return "H"
- }
- return "L"
-}
-
-func encodeGeneralConstraintIndicatorFlags(v *h265.SPS_ProfileTierLevel) string {
- var ret []string
-
- var o1 uint8
- if v.GeneralProgressiveSourceFlag {
- o1 |= 1 << 7
- }
- if v.GeneralInterlacedSourceFlag {
- o1 |= 1 << 6
- }
- if v.GeneralNonPackedConstraintFlag {
- o1 |= 1 << 5
- }
- if v.GeneralFrameOnlyConstraintFlag {
- o1 |= 1 << 4
- }
- if v.GeneralMax12bitConstraintFlag {
- o1 |= 1 << 3
- }
- if v.GeneralMax10bitConstraintFlag {
- o1 |= 1 << 2
- }
- if v.GeneralMax8bitConstraintFlag {
- o1 |= 1 << 1
- }
- if v.GeneralMax422ChromeConstraintFlag {
- o1 |= 1 << 0
- }
-
- ret = append(ret, fmt.Sprintf("%x", o1))
-
- var o2 uint8
- if v.GeneralMax420ChromaConstraintFlag {
- o2 |= 1 << 7
- }
- if v.GeneralMaxMonochromeConstraintFlag {
- o2 |= 1 << 6
- }
- if v.GeneralIntraConstraintFlag {
- o2 |= 1 << 5
- }
- if v.GeneralOnePictureOnlyConstraintFlag {
- o2 |= 1 << 4
- }
- if v.GeneralLowerBitRateConstraintFlag {
- o2 |= 1 << 3
- }
- if v.GeneralMax14BitConstraintFlag {
- o2 |= 1 << 2
- }
-
- if o2 != 0 {
- ret = append(ret, fmt.Sprintf("%x", o2))
- }
-
- return strings.Join(ret, ".")
-}
-
-func codecParametersGenerate(track format.Format) string {
- switch ttrack := track.(type) {
- case *format.H264:
- sps := ttrack.SafeSPS()
- if len(sps) >= 4 {
- return "avc1." + hex.EncodeToString(sps[1:4])
- }
-
- case *format.H265:
- var sps h265.SPS
- err := sps.Unmarshal(ttrack.SafeSPS())
- if err == nil {
- return "hvc1." +
- encodeProfileSpace(sps.ProfileTierLevel.GeneralProfileSpace) +
- strconv.FormatInt(int64(sps.ProfileTierLevel.GeneralProfileIdc), 10) + "." +
- encodeCompatibilityFlag(sps.ProfileTierLevel.GeneralProfileCompatibilityFlag) + "." +
- encodeGeneralTierFlag(sps.ProfileTierLevel.GeneralTierFlag) +
- strconv.FormatInt(int64(sps.ProfileTierLevel.GeneralLevelIdc), 10) + "." +
- encodeGeneralConstraintIndicatorFlags(&sps.ProfileTierLevel)
- }
-
- case *format.MPEG4Audio:
- // https://developer.mozilla.org/en-US/docs/Web/Media/Formats/codecs_parameter
- return "mp4a.40." + strconv.FormatInt(int64(ttrack.Config.Type), 10)
-
- case *format.Opus:
- return "opus"
- }
-
- return ""
-}
-
-func codecParametersAreSupported(codecs string) bool {
- for _, codec := range strings.Split(codecs, ",") {
- if !strings.HasPrefix(codec, "avc1.") &&
- !strings.HasPrefix(codec, "hvc1.") &&
- !strings.HasPrefix(codec, "hev1.") &&
- !strings.HasPrefix(codec, "mp4a.") &&
- codec != "opus" {
- return false
- }
- }
- return true
-}
diff --git a/internal/hls/fmp4/boxes_opus.go b/internal/hls/fmp4/boxes_opus.go
deleted file mode 100644
index 131a5a00..00000000
--- a/internal/hls/fmp4/boxes_opus.go
+++ /dev/null
@@ -1,53 +0,0 @@
-//nolint:gochecknoinits,revive,gocritic
-package fmp4
-
-import (
- gomp4 "github.com/abema/go-mp4"
-)
-
-func BoxTypeOpus() gomp4.BoxType { return gomp4.StrToBoxType("Opus") }
-
-func init() {
- gomp4.AddAnyTypeBoxDef(&gomp4.AudioSampleEntry{}, BoxTypeOpus())
-}
-
-func BoxTypeDOps() gomp4.BoxType { return gomp4.StrToBoxType("dOps") }
-
-func init() {
- gomp4.AddBoxDef(&DOps{})
-}
-
-type DOpsChannelMappingTable struct{}
-
-type DOps struct {
- gomp4.Box
- Version uint8 `mp4:"0,size=8"`
- OutputChannelCount uint8 `mp4:"1,size=8"`
- PreSkip uint16 `mp4:"2,size=16"`
- InputSampleRate uint32 `mp4:"3,size=32"`
- OutputGain int16 `mp4:"4,size=16"`
- ChannelMappingFamily uint8 `mp4:"5,size=8"`
- StreamCount uint8 `mp4:"6,opt=dynamic,size=8"`
- CoupledCount uint8 `mp4:"7,opt=dynamic,size=8"`
- ChannelMapping []uint8 `mp4:"8,opt=dynamic,size=8,len=dynamic"`
-}
-
-func (DOps) GetType() gomp4.BoxType {
- return BoxTypeDOps()
-}
-
-func (dops DOps) IsOptFieldEnabled(name string, ctx gomp4.Context) bool {
- switch name {
- case "StreamCount", "CoupledCount", "ChannelMapping":
- return dops.ChannelMappingFamily != 0
- }
- return false
-}
-
-func (ops DOps) GetFieldLength(name string, ctx gomp4.Context) uint {
- switch name {
- case "ChannelMapping":
- return uint(ops.OutputChannelCount)
- }
- return 0
-}
diff --git a/internal/hls/fmp4/fmp4.go b/internal/hls/fmp4/fmp4.go
deleted file mode 100644
index 53be064e..00000000
--- a/internal/hls/fmp4/fmp4.go
+++ /dev/null
@@ -1,2 +0,0 @@
-// Package fmp4 contains a fMP4 reader and writer.
-package fmp4
diff --git a/internal/hls/fmp4/init.go b/internal/hls/fmp4/init.go
deleted file mode 100644
index c321cabe..00000000
--- a/internal/hls/fmp4/init.go
+++ /dev/null
@@ -1,363 +0,0 @@
-package fmp4
-
-import (
- "bytes"
- "fmt"
-
- gomp4 "github.com/abema/go-mp4"
- "github.com/aler9/gortsplib/v2/pkg/codecs/h265"
- "github.com/aler9/gortsplib/v2/pkg/codecs/mpeg4audio"
- "github.com/aler9/gortsplib/v2/pkg/format"
-)
-
-// Init is a FMP4 initialization file.
-type Init struct {
- Tracks []*InitTrack
-}
-
-// Unmarshal decodes a FMP4 initialization file.
-func (i *Init) Unmarshal(byts []byte) error {
- type readState int
-
- const (
- waitingTrak readState = iota
- waitingTkhd
- waitingMdhd
- waitingCodec
- waitingAvcC
- waitingHvcC
- waitingEsds
- waitingDOps
- )
-
- state := waitingTrak
- var curTrack *InitTrack
-
- _, err := gomp4.ReadBoxStructure(bytes.NewReader(byts), func(h *gomp4.ReadHandle) (interface{}, error) {
- switch h.BoxInfo.Type.String() {
- case "trak":
- if state != waitingTrak {
- return nil, fmt.Errorf("unexpected box 'trak'")
- }
-
- curTrack = &InitTrack{}
- i.Tracks = append(i.Tracks, curTrack)
- state = waitingTkhd
-
- case "tkhd":
- if state != waitingTkhd {
- return nil, fmt.Errorf("unexpected box 'tkhd'")
- }
-
- box, _, err := h.ReadPayload()
- if err != nil {
- return nil, err
- }
- tkhd := box.(*gomp4.Tkhd)
-
- curTrack.ID = int(tkhd.TrackID)
- state = waitingMdhd
-
- case "mdhd":
- if state != waitingMdhd {
- return nil, fmt.Errorf("unexpected box 'mdhd'")
- }
-
- box, _, err := h.ReadPayload()
- if err != nil {
- return nil, err
- }
- mdhd := box.(*gomp4.Mdhd)
-
- curTrack.TimeScale = mdhd.Timescale
- state = waitingCodec
-
- case "avc1":
- if state != waitingCodec {
- return nil, fmt.Errorf("unexpected box 'avc1'")
- }
- state = waitingAvcC
-
- case "avcC":
- if state != waitingAvcC {
- return nil, fmt.Errorf("unexpected box 'avcC'")
- }
-
- box, _, err := h.ReadPayload()
- if err != nil {
- return nil, err
- }
- avcc := box.(*gomp4.AVCDecoderConfiguration)
-
- if len(avcc.SequenceParameterSets) > 1 {
- return nil, fmt.Errorf("multiple SPS are not supported")
- }
-
- var sps []byte
- if len(avcc.SequenceParameterSets) == 1 {
- sps = avcc.SequenceParameterSets[0].NALUnit
- }
-
- if len(avcc.PictureParameterSets) > 1 {
- return nil, fmt.Errorf("multiple PPS are not supported")
- }
-
- var pps []byte
- if len(avcc.PictureParameterSets) == 1 {
- pps = avcc.PictureParameterSets[0].NALUnit
- }
-
- curTrack.Format = &format.H264{
- PayloadTyp: 96,
- SPS: sps,
- PPS: pps,
- PacketizationMode: 1,
- }
- state = waitingTrak
-
- case "hev1", "hvc1":
- if state != waitingCodec {
- return nil, fmt.Errorf("unexpected box 'hev1'")
- }
- state = waitingHvcC
-
- case "hvcC":
- if state != waitingHvcC {
- return nil, fmt.Errorf("unexpected box 'hvcC'")
- }
-
- box, _, err := h.ReadPayload()
- if err != nil {
- return nil, err
- }
- hvcc := box.(*gomp4.HvcC)
-
- var vps []byte
- var sps []byte
- var pps []byte
-
- for _, arr := range hvcc.NaluArrays {
- switch h265.NALUType(arr.NaluType) {
- case h265.NALUType_VPS_NUT, h265.NALUType_SPS_NUT, h265.NALUType_PPS_NUT:
- if arr.NumNalus != 1 {
- return nil, fmt.Errorf("multiple VPS/SPS/PPS are not supported")
- }
- }
-
- switch h265.NALUType(arr.NaluType) {
- case h265.NALUType_VPS_NUT:
- vps = arr.Nalus[0].NALUnit
-
- case h265.NALUType_SPS_NUT:
- sps = arr.Nalus[0].NALUnit
-
- case h265.NALUType_PPS_NUT:
- pps = arr.Nalus[0].NALUnit
- }
- }
-
- if vps == nil {
- return nil, fmt.Errorf("VPS not provided")
- }
-
- if sps == nil {
- return nil, fmt.Errorf("SPS not provided")
- }
-
- if pps == nil {
- return nil, fmt.Errorf("PPS not provided")
- }
-
- curTrack.Format = &format.H265{
- PayloadTyp: 96,
- VPS: vps,
- SPS: sps,
- PPS: pps,
- }
- state = waitingTrak
-
- case "mp4a":
- if state != waitingCodec {
- return nil, fmt.Errorf("unexpected box 'mp4a'")
- }
- state = waitingEsds
-
- case "esds":
- if state != waitingEsds {
- return nil, fmt.Errorf("unexpected box 'esds'")
- }
-
- box, _, err := h.ReadPayload()
- if err != nil {
- return nil, err
- }
- esds := box.(*gomp4.Esds)
-
- encodedConf := func() []byte {
- for _, desc := range esds.Descriptors {
- if desc.Tag == gomp4.DecSpecificInfoTag {
- return desc.Data
- }
- }
- return nil
- }()
- if encodedConf == nil {
- return nil, fmt.Errorf("unable to find MPEG4-audio configuration")
- }
-
- var c mpeg4audio.Config
- err = c.Unmarshal(encodedConf)
- if err != nil {
- return nil, fmt.Errorf("invalid MPEG4-audio configuration: %s", err)
- }
-
- curTrack.Format = &format.MPEG4Audio{
- PayloadTyp: 96,
- Config: &c,
- SizeLength: 13,
- IndexLength: 3,
- IndexDeltaLength: 3,
- }
- state = waitingTrak
-
- case "Opus":
- if state != waitingCodec {
- return nil, fmt.Errorf("unexpected box 'Opus'")
- }
- state = waitingDOps
-
- case "dOps":
- if state != waitingDOps {
- return nil, fmt.Errorf("unexpected box 'dOps'")
- }
-
- box, _, err := h.ReadPayload()
- if err != nil {
- return nil, err
- }
- dops := box.(*DOps)
-
- curTrack.Format = &format.Opus{
- PayloadTyp: 96,
- SampleRate: int(dops.InputSampleRate),
- ChannelCount: int(dops.OutputChannelCount),
- }
- state = waitingTrak
-
- case "ac-3": // ac-3, not supported yet
- i.Tracks = i.Tracks[:len(i.Tracks)-1]
- state = waitingTrak
- return nil, nil
-
- case "ec-3": // ec-3, not supported yet
- i.Tracks = i.Tracks[:len(i.Tracks)-1]
- state = waitingTrak
- return nil, nil
-
- case "c608", "c708": // closed captions, not supported yet
- i.Tracks = i.Tracks[:len(i.Tracks)-1]
- state = waitingTrak
- return nil, nil
-
- case "chrm", "nmhd":
- return nil, nil
- }
-
- return h.Expand()
- })
- if err != nil {
- return err
- }
-
- if state != waitingTrak {
- return fmt.Errorf("parse error")
- }
-
- if len(i.Tracks) == 0 {
- return fmt.Errorf("no tracks found")
- }
-
- return nil
-}
-
-// Marshal encodes a FMP4 initialization file.
-func (i *Init) Marshal() ([]byte, error) {
- /*
- - ftyp
- - moov
- - mvhd
- - trak
- - trak
- - ...
- - mvex
- - trex
- - trex
- - ...
- */
-
- w := newMP4Writer()
-
- _, err := w.WriteBox(&gomp4.Ftyp{ //
- MajorBrand: [4]byte{'m', 'p', '4', '2'},
- MinorVersion: 1,
- CompatibleBrands: []gomp4.CompatibleBrandElem{
- {CompatibleBrand: [4]byte{'m', 'p', '4', '1'}},
- {CompatibleBrand: [4]byte{'m', 'p', '4', '2'}},
- {CompatibleBrand: [4]byte{'i', 's', 'o', 'm'}},
- {CompatibleBrand: [4]byte{'h', 'l', 's', 'f'}},
- },
- })
- if err != nil {
- return nil, err
- }
-
- _, err = w.writeBoxStart(&gomp4.Moov{}) //
- if err != nil {
- return nil, err
- }
-
- _, err = w.WriteBox(&gomp4.Mvhd{ //
- Timescale: 1000,
- Rate: 65536,
- Volume: 256,
- Matrix: [9]int32{0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000},
- NextTrackID: 4294967295,
- })
- if err != nil {
- return nil, err
- }
-
- for _, track := range i.Tracks {
- err := track.marshal(w)
- if err != nil {
- return nil, err
- }
- }
-
- _, err = w.writeBoxStart(&gomp4.Mvex{}) //
- if err != nil {
- return nil, err
- }
-
- for _, track := range i.Tracks {
- _, err = w.WriteBox(&gomp4.Trex{ //
- TrackID: uint32(track.ID),
- DefaultSampleDescriptionIndex: 1,
- })
- if err != nil {
- return nil, err
- }
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return nil, err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return nil, err
- }
-
- return w.bytes(), nil
-}
diff --git a/internal/hls/fmp4/init_test.go b/internal/hls/fmp4/init_test.go
deleted file mode 100644
index 697d93cf..00000000
--- a/internal/hls/fmp4/init_test.go
+++ /dev/null
@@ -1,1453 +0,0 @@
-package fmp4
-
-import (
- "testing"
-
- "github.com/aler9/gortsplib/v2/pkg/codecs/mpeg4audio"
- "github.com/aler9/gortsplib/v2/pkg/format"
- "github.com/stretchr/testify/require"
-)
-
-var testSPS = []byte{
- 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
- 0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04,
- 0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9,
- 0x20,
-}
-
-var testVideoTrack = &format.H264{
- PayloadTyp: 96,
- SPS: testSPS,
- PPS: []byte{0x08},
- PacketizationMode: 1,
-}
-
-var testAudioTrack = &format.MPEG4Audio{
- PayloadTyp: 96,
- Config: &mpeg4audio.Config{
- Type: 2,
- SampleRate: 44100,
- ChannelCount: 2,
- },
- SizeLength: 13,
- IndexLength: 3,
- IndexDeltaLength: 3,
-}
-
-var casesInit = []struct {
- name string
- enc []byte
- dec Init
-}{
- {
- "h264",
- []byte{
- 0x00, 0x00, 0x00, 0x20,
- 'f', 't', 'y', 'p',
- 0x6d, 0x70, 0x34, 0x32, 0x00, 0x00, 0x00, 0x01,
- 0x6d, 0x70, 0x34, 0x31, 0x6d, 0x70, 0x34, 0x32,
- 0x69, 0x73, 0x6f, 0x6d, 0x68, 0x6c, 0x73, 0x66,
- 0x00, 0x00, 0x02, 0x88,
- 'm', 'o', 'o', 'v',
- 0x00, 0x00, 0x00, 0x6c,
- 'm', 'v', 'h', 'd',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0xec,
- 't', 'r', 'a', 'k',
- 0x00, 0x00, 0x00, 0x5c,
- 't', 'k', 'h', 'd',
- 0x00, 0x00, 0x00, 0x03,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x07, 0x80, 0x00, 0x00, 0x04, 0x38, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x88,
- 'm', 'd', 'i', 'a',
- 0x00, 0x00, 0x00, 0x20,
- 'm', 'd', 'h', 'd',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x5f, 0x90,
- 0x00, 0x00, 0x00, 0x00, 0x55, 0xc4, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x2d,
- 'h', 'd', 'l', 'r',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x76, 0x69, 0x64, 0x65, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e,
- 0x64, 0x6c, 0x65, 0x72, 0x00, 0x00, 0x00, 0x01,
- 0x33,
- 'm', 'i', 'n', 'f',
- 0x00, 0x00, 0x00,
- 0x14,
- 'v', 'm', 'h', 'd',
- 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x24,
- 'd', 'i', 'n', 'f',
- 0x00, 0x00, 0x00, 0x1c, 0x64, 0x72, 0x65,
- 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x72, 0x6c,
- 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0xf3, 0x73, 0x74, 0x62, 0x6c, 0x00, 0x00, 0x00,
- 0xa7, 0x73, 0x74, 0x73, 0x64, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x97, 0x61, 0x76, 0x63, 0x31, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x04,
- 0x38, 0x00, 0x48, 0x00, 0x00, 0x00, 0x48, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x18, 0xff, 0xff, 0x00, 0x00, 0x00, 0x2d, 0x61,
- 0x76, 0x63, 0x43, 0x01, 0x42, 0xc0, 0x28, 0x03,
- 0x01, 0x00, 0x19, 0x67, 0x42, 0xc0, 0x28, 0xd9,
- 0x00, 0x78, 0x02, 0x27, 0xe5, 0x84, 0x00, 0x00,
- 0x03, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0xf0,
- 0x3c, 0x60, 0xc9, 0x20, 0x01, 0x00, 0x01, 0x08,
- 0x00, 0x00, 0x00, 0x14, 0x62, 0x74, 0x72, 0x74,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x42, 0x40,
- 0x00, 0x0f, 0x42, 0x40, 0x00, 0x00, 0x00, 0x10,
- 0x73, 0x74, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
- 0x73, 0x74, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
- 0x73, 0x74, 0x73, 0x7a, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x63, 0x6f,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x28, 0x6d, 0x76, 0x65, 0x78,
- 0x00, 0x00, 0x00, 0x20, 0x74, 0x72, 0x65, 0x78,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- },
- Init{
- Tracks: []*InitTrack{
- {
- ID: 1,
- TimeScale: 90000,
- Format: testVideoTrack,
- },
- },
- },
- },
- {
- "h265",
- []byte{
- 0x00, 0x00, 0x00, 0x20,
- 'f', 't', 'y', 'p',
- 0x6d, 0x70, 0x34, 0x32, 0x00, 0x00, 0x00, 0x01,
- 0x6d, 0x70, 0x34, 0x31, 0x6d, 0x70, 0x34, 0x32,
- 0x69, 0x73, 0x6f, 0x6d, 0x68, 0x6c, 0x73, 0x66,
- 0x00, 0x00, 0x02, 0xb8,
- 'm', 'o', 'o', 'v',
- 0x00, 0x00, 0x00, 0x6c,
- 'm', 'v', 'h', 'd',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x02, 0x1c,
- 0x74, 0x72, 0x61, 0x6b, 0x00, 0x00, 0x00, 0x5c,
- 0x74, 0x6b, 0x68, 0x64, 0x00, 0x00, 0x00, 0x03,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x07, 0x80, 0x00, 0x00, 0x04, 0x38, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0xb8, 0x6d, 0x64, 0x69, 0x61,
- 0x00, 0x00, 0x00, 0x20, 0x6d, 0x64, 0x68, 0x64,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x5f, 0x90,
- 0x00, 0x00, 0x00, 0x00, 0x55, 0xc4, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x2d, 0x68, 0x64, 0x6c, 0x72,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x76, 0x69, 0x64, 0x65, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e,
- 0x64, 0x6c, 0x65, 0x72, 0x00, 0x00, 0x00, 0x01,
- 0x63, 0x6d, 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00,
- 0x14, 0x76, 0x6d, 0x68, 0x64, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x24, 0x64, 0x69, 0x6e,
- 0x66, 0x00, 0x00, 0x00, 0x1c, 0x64, 0x72, 0x65,
- 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x72, 0x6c,
- 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
- 0x23, 0x73, 0x74, 0x62, 0x6c, 0x00, 0x00, 0x00,
- 0xd7, 0x73, 0x74, 0x73, 0x64, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0xc7, 0x68, 0x65, 0x76, 0x31, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x04,
- 0x38, 0x00, 0x48, 0x00, 0x00, 0x00, 0x48, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x18, 0xff, 0xff, 0x00, 0x00, 0x00, 0x5d, 0x68,
- 0x76, 0x63, 0x43, 0x01, 0x01, 0x60, 0x00, 0x00,
- 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x78,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x13, 0x03, 0x20, 0x00, 0x01, 0x00, 0x04, 0x01,
- 0x02, 0x03, 0x04, 0x21, 0x00, 0x01, 0x00, 0x2a,
- 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03,
- 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03,
- 0x00, 0x78, 0xa0, 0x03, 0xc0, 0x80, 0x10, 0xe5,
- 0x96, 0x66, 0x69, 0x24, 0xca, 0xe0, 0x10, 0x00,
- 0x00, 0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x01,
- 0xe0, 0x80, 0x22, 0x00, 0x01, 0x00, 0x01, 0x08,
- 0x00, 0x00, 0x00, 0x14, 0x62, 0x74, 0x72, 0x74,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x42, 0x40,
- 0x00, 0x0f, 0x42, 0x40, 0x00, 0x00, 0x00, 0x10,
- 0x73, 0x74, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
- 0x73, 0x74, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
- 0x73, 0x74, 0x73, 0x7a, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x63, 0x6f,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x28, 0x6d, 0x76, 0x65, 0x78,
- 0x00, 0x00, 0x00, 0x20, 0x74, 0x72, 0x65, 0x78,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- },
- Init{
- Tracks: []*InitTrack{
- {
- ID: 1,
- TimeScale: 90000,
- Format: &format.H265{
- PayloadTyp: 96,
- VPS: []byte{0x01, 0x02, 0x03, 0x04},
- SPS: []byte{
- 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03,
- 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03,
- 0x00, 0x78, 0xa0, 0x03, 0xc0, 0x80, 0x10, 0xe5,
- 0x96, 0x66, 0x69, 0x24, 0xca, 0xe0, 0x10, 0x00,
- 0x00, 0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x01,
- 0xe0, 0x80,
- },
- PPS: []byte{0x08},
- },
- },
- },
- },
- },
- {
- "mpeg4audio",
- []byte{
- 0x00, 0x00, 0x00, 0x20,
- 'f', 't', 'y', 'p',
- 0x6d, 0x70, 0x34, 0x32, 0x00, 0x00, 0x00, 0x01,
- 0x6d, 0x70, 0x34, 0x31, 0x6d, 0x70, 0x34, 0x32,
- 0x69, 0x73, 0x6f, 0x6d, 0x68, 0x6c, 0x73, 0x66,
- 0x00, 0x00, 0x02, 0x58,
- 'm', 'o', 'o', 'v',
- 0x00, 0x00, 0x00, 0x6c,
- 'm', 'v', 'h', 'd',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0xbc,
- 't', 'r', 'a', 'k',
- 0x00, 0x00, 0x00, 0x5c,
- 't', 'k', 'h', 'd',
- 0x00, 0x00, 0x00, 0x03,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x58,
- 'm', 'd', 'i', 'a',
- 0x00, 0x00, 0x00, 0x20,
- 'm', 'd', 'h', 'd',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xac, 0x44,
- 0x00, 0x00, 0x00, 0x00, 0x55, 0xc4, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x2d,
- 'h', 'd', 'l', 'r',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x73, 0x6f, 0x75, 0x6e, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e,
- 0x64, 0x6c, 0x65, 0x72, 0x00, 0x00, 0x00, 0x01,
- 0x03,
- 'm', 'i', 'n', 'f',
- 0x00, 0x00, 0x00, 0x10,
- 's', 'm', 'h', 'd',
- 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x24,
- 'd', 'i', 'n', 'f',
- 0x00, 0x00, 0x00,
- 0x1c, 0x64, 0x72, 0x65, 0x66, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x0c, 0x75, 0x72, 0x6c, 0x20, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0xc7, 0x73, 0x74, 0x62,
- 0x6c, 0x00, 0x00, 0x00, 0x7b, 0x73, 0x74, 0x73,
- 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x6b,
- 'm', 'p', '4', 'a',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x02, 0x00, 0x10, 0x00, 0x00, 0x00,
- 0x00, 0xac, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x33,
- 'e', 's', 'd', 's',
- 0x00, 0x00, 0x00,
- 0x00, 0x03, 0x80, 0x80, 0x80, 0x22, 0x00, 0x01,
- 0x00, 0x04, 0x80, 0x80, 0x80, 0x14, 0x40, 0x15,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0xf7, 0x39, 0x00,
- 0x01, 0xf7, 0x39, 0x05, 0x80, 0x80, 0x80, 0x02,
- 0x12, 0x10, 0x06, 0x80, 0x80, 0x80, 0x01, 0x02,
- 0x00, 0x00, 0x00, 0x14,
- 'b', 't', 'r', 't',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf7, 0x39,
- 0x00, 0x01, 0xf7, 0x39, 0x00, 0x00, 0x00, 0x10,
- 0x73, 0x74, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
- 0x73, 0x74, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
- 0x73, 0x74, 0x73, 0x7a, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x63, 0x6f,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x28,
- 'm', 'v', 'e', 'x',
- 0x00, 0x00, 0x00, 0x20,
- 't', 'r', 'e', 'x',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- },
- Init{
- Tracks: []*InitTrack{
- {
- ID: 1,
- TimeScale: uint32(testAudioTrack.ClockRate()),
- Format: testAudioTrack,
- },
- },
- },
- },
- {
- "opus",
- []byte{
- 0x00, 0x00, 0x00, 0x20,
- 'f', 't', 'y', 'p',
- 0x6d, 0x70, 0x34, 0x32, 0x00, 0x00, 0x00, 0x01,
- 0x6d, 0x70, 0x34, 0x31, 0x6d, 0x70, 0x34, 0x32,
- 0x69, 0x73, 0x6f, 0x6d, 0x68, 0x6c, 0x73, 0x66,
- 0x00, 0x00, 0x02, 0x38,
- 'm', 'o', 'o', 'v',
- 0x00, 0x00, 0x00, 0x6c,
- 'm', 'v', 'h', 'd',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0x9c,
- 0x74, 0x72, 0x61, 0x6b, 0x00, 0x00, 0x00, 0x5c,
- 0x74, 0x6b, 0x68, 0x64, 0x00, 0x00, 0x00, 0x03,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x38, 0x6d, 0x64, 0x69, 0x61,
- 0x00, 0x00, 0x00, 0x20, 0x6d, 0x64, 0x68, 0x64,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x55, 0xc4, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x2d, 0x68, 0x64, 0x6c, 0x72,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x73, 0x6f, 0x75, 0x6e, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e,
- 0x64, 0x6c, 0x65, 0x72, 0x00, 0x00, 0x00, 0x00,
- 0xe3, 0x6d, 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00,
- 0x10, 0x73, 0x6d, 0x68, 0x64, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x24, 0x64, 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00,
- 0x1c, 0x64, 0x72, 0x65, 0x66, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x0c, 0x75, 0x72, 0x6c, 0x20, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0xa7, 0x73, 0x74, 0x62,
- 0x6c, 0x00, 0x00, 0x00, 0x5b, 0x73, 0x74, 0x73,
- 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x4b, 0x4f, 0x70, 0x75,
- 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x02, 0x00, 0x10, 0x00, 0x00, 0x00,
- 0x00, 0xbb, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x13, 0x64, 0x4f, 0x70, 0x73, 0x00, 0x02, 0x01,
- 0x38, 0x00, 0x00, 0xbb, 0x80, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x14, 0x62, 0x74, 0x72, 0x74,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf7, 0x39,
- 0x00, 0x01, 0xf7, 0x39, 0x00, 0x00, 0x00, 0x10,
- 0x73, 0x74, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
- 0x73, 0x74, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
- 0x73, 0x74, 0x73, 0x7a, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x63, 0x6f,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x28, 0x6d, 0x76, 0x65, 0x78,
- 0x00, 0x00, 0x00, 0x20, 0x74, 0x72, 0x65, 0x78,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- },
- Init{
- Tracks: []*InitTrack{
- {
- ID: 1,
- TimeScale: 48000,
- Format: &format.Opus{
- PayloadTyp: 96,
- SampleRate: 48000,
- ChannelCount: 2,
- },
- },
- },
- },
- },
- {
- "h264 + mpeg4audio",
- []byte{
- 0x00, 0x00, 0x00, 0x20,
- 'f', 't', 'y', 'p',
- 0x6d, 0x70, 0x34, 0x32, 0x00, 0x00, 0x00, 0x01,
- 0x6d, 0x70, 0x34, 0x31, 0x6d, 0x70, 0x34, 0x32,
- 0x69, 0x73, 0x6f, 0x6d, 0x68, 0x6c, 0x73, 0x66,
- 0x00, 0x00, 0x04, 0x64,
- 'm', 'o', 'o', 'v',
- 0x00, 0x00, 0x00, 0x6c,
- 'm', 'v', 'h', 'd',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0xec,
- 't', 'r', 'a', 'k',
- 0x00, 0x00, 0x00, 0x5c,
- 't', 'k', 'h', 'd',
- 0x00, 0x00, 0x00, 0x03,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x07, 0x80, 0x00, 0x00, 0x04, 0x38, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x88,
- 'm', 'd', 'i', 'a',
- 0x00, 0x00, 0x00, 0x20,
- 'm', 'd', 'h', 'd',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x5f, 0x90,
- 0x00, 0x00, 0x00, 0x00, 0x55, 0xc4, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x2d,
- 'h', 'd', 'l', 'r',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x76, 0x69, 0x64, 0x65, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e,
- 0x64, 0x6c, 0x65, 0x72, 0x00, 0x00, 0x00, 0x01,
- 0x33,
- 'm', 'i', 'n', 'f',
- 0x00, 0x00, 0x00, 0x14,
- 'v', 'm', 'h', 'd',
- 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x24, 0x64, 0x69, 0x6e,
- 0x66, 0x00, 0x00, 0x00, 0x1c, 0x64, 0x72, 0x65,
- 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x72, 0x6c,
- 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0xf3, 0x73, 0x74, 0x62, 0x6c, 0x00, 0x00, 0x00,
- 0xa7, 0x73, 0x74, 0x73, 0x64, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x97, 0x61, 0x76, 0x63, 0x31, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x04,
- 0x38, 0x00, 0x48, 0x00, 0x00, 0x00, 0x48, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x18, 0xff, 0xff, 0x00, 0x00, 0x00, 0x2d, 0x61,
- 0x76, 0x63, 0x43, 0x01, 0x42, 0xc0, 0x28, 0x03,
- 0x01, 0x00, 0x19, 0x67, 0x42, 0xc0, 0x28, 0xd9,
- 0x00, 0x78, 0x02, 0x27, 0xe5, 0x84, 0x00, 0x00,
- 0x03, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0xf0,
- 0x3c, 0x60, 0xc9, 0x20, 0x01, 0x00, 0x01, 0x08,
- 0x00, 0x00, 0x00, 0x14, 0x62, 0x74, 0x72, 0x74,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x42, 0x40,
- 0x00, 0x0f, 0x42, 0x40, 0x00, 0x00, 0x00, 0x10,
- 0x73, 0x74, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
- 0x73, 0x74, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
- 0x73, 0x74, 0x73, 0x7a, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x63, 0x6f,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0xbc,
- 't', 'r', 'a', 'k',
- 0x00, 0x00, 0x00, 0x5c,
- 't', 'k', 'h', 'd',
- 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x58,
- 'm', 'd', 'i', 'a',
- 0x00, 0x00, 0x00, 0x20,
- 'm', 'd', 'h', 'd',
- 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0xac, 0x44, 0x00, 0x00, 0x00, 0x00,
- 0x55, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d,
- 'h', 'd', 'l', 'r',
- 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x73, 0x6f, 0x75, 0x6e,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x53, 0x6f, 0x75, 0x6e,
- 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72,
- 0x00, 0x00, 0x00, 0x01, 0x03, 0x6d, 0x69, 0x6e,
- 0x66, 0x00, 0x00, 0x00, 0x10,
- 's', 'm', 'h', 'd',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x24, 0x64, 0x69, 0x6e,
- 0x66, 0x00, 0x00, 0x00, 0x1c, 0x64, 0x72, 0x65,
- 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x72, 0x6c,
- 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0xc7, 0x73, 0x74, 0x62, 0x6c, 0x00, 0x00, 0x00,
- 0x7b, 0x73, 0x74, 0x73, 0x64, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x6b, 0x6d, 0x70, 0x34, 0x61, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x00, 0xac, 0x44, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x33, 0x65, 0x73, 0x64,
- 0x73, 0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x80,
- 0x80, 0x22, 0x00, 0x02, 0x00, 0x04, 0x80, 0x80,
- 0x80, 0x14, 0x40, 0x15, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0xf7, 0x39, 0x00, 0x01, 0xf7, 0x39, 0x05,
- 0x80, 0x80, 0x80, 0x02, 0x12, 0x10, 0x06, 0x80,
- 0x80, 0x80, 0x01, 0x02, 0x00, 0x00, 0x00, 0x14,
- 0x62, 0x74, 0x72, 0x74, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x01, 0xf7, 0x39, 0x00, 0x01, 0xf7, 0x39,
- 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x74, 0x73,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x73, 0x63,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x14, 0x73, 0x74, 0x73, 0x7a,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
- 0x73, 0x74, 0x63, 0x6f, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48,
- 0x6d, 0x76, 0x65, 0x78, 0x00, 0x00, 0x00, 0x20,
- 0x74, 0x72, 0x65, 0x78, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
- 0x74, 0x72, 0x65, 0x78, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00,
- },
- Init{
- Tracks: []*InitTrack{
- {
- ID: 1,
- TimeScale: 90000,
- Format: testVideoTrack,
- },
- {
- ID: 2,
- TimeScale: uint32(testAudioTrack.ClockRate()),
- Format: testAudioTrack,
- },
- },
- },
- },
-}
-
-func TestInitMarshal(t *testing.T) {
- for _, ca := range casesInit {
- t.Run(ca.name, func(t *testing.T) {
- byts, err := ca.dec.Marshal()
- require.NoError(t, err)
- require.Equal(t, ca.enc, byts)
- })
- }
-}
-
-func TestInitUnmarshal(t *testing.T) {
- for _, ca := range casesInit {
- t.Run(ca.name, func(t *testing.T) {
- var init Init
- err := init.Unmarshal(ca.enc)
- require.NoError(t, err)
- require.Equal(t, ca.dec, init)
- })
- }
-}
-
-func TestInitUnmarshalExternal(t *testing.T) {
- for _, ca := range []struct {
- name string
- byts []byte
- init Init
- }{
- {
- "h264",
- []byte{
- 0x00, 0x00, 0x00, 0x1c,
- 'f', 't', 'y', 'p',
- 0x64, 0x61, 0x73, 0x68, 0x00, 0x00, 0x00, 0x01,
- 0x69, 0x73, 0x6f, 0x6d, 0x61, 0x76, 0x63, 0x31,
- 0x64, 0x61, 0x73, 0x68, 0x00, 0x00, 0x02, 0x92,
- 'm', 'o', 'o', 'v',
- 0x00, 0x00, 0x00, 0x6c,
- 'm', 'v', 'h', 'd',
- 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x98, 0x96, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
- 0x00, 0x00, 0x01, 0xf6,
- 't', 'r', 'a', 'k',
- 0x00, 0x00, 0x00, 0x5c,
- 't', 'k', 'h', 'd',
- 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x40, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00,
- 0x02, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x01, 0x92,
- 'm', 'd', 'i', 'a',
- 0x00, 0x00, 0x00, 0x20,
- 'm', 'd', 'h', 'd',
- 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x98, 0x96, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x55, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38,
- 'h', 'd', 'l', 'r',
- 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x76, 0x69, 0x64, 0x65,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x42, 0x72, 0x6f, 0x61,
- 0x64, 0x70, 0x65, 0x61, 0x6b, 0x20, 0x56, 0x69,
- 0x64, 0x65, 0x6f, 0x20, 0x48, 0x61, 0x6e, 0x64,
- 0x6c, 0x65, 0x72, 0x00, 0x00, 0x00, 0x01, 0x32,
- 'm', 'i', 'n', 'f',
- 0x00, 0x00, 0x00, 0x14,
- 'v', 'm', 'h', 'd',
- 0x00, 0x00, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x24,
- 'd', 'i', 'n', 'f',
- 0x00, 0x00, 0x00, 0x1c, 0x64, 0x72, 0x65, 0x66,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x0c, 0x75, 0x72, 0x6c, 0x20,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf2,
- 0x73, 0x74, 0x62, 0x6c, 0x00, 0x00, 0x00, 0xa6,
- 0x73, 0x74, 0x73, 0x64, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x96,
- 0x61, 0x76, 0x63, 0x31, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x02, 0x1c,
- 0x00, 0x48, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x68,
- 0x32, 0x36, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18,
- 0xff, 0xff, 0x00, 0x00, 0x00, 0x30, 0x61, 0x76,
- 0x63, 0x43, 0x01, 0x42, 0xc0, 0x1f, 0xff, 0xe1,
- 0x00, 0x19, 0x67, 0x42, 0xc0, 0x1f, 0xd9, 0x00,
- 0xf0, 0x11, 0x7e, 0xf0, 0x11, 0x00, 0x00, 0x03,
- 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x30, 0x8f,
- 0x18, 0x32, 0x48, 0x01, 0x00, 0x04, 0x68, 0xcb,
- 0x8c, 0xb2, 0x00, 0x00, 0x00, 0x10, 0x70, 0x61,
- 0x73, 0x70, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x73, 0x74,
- 0x74, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x73, 0x74,
- 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x73, 0x74,
- 0x73, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x10, 0x73, 0x74, 0x63, 0x6f, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x28, 0x6d, 0x76, 0x65, 0x78, 0x00, 0x00,
- 0x00, 0x20, 0x74, 0x72, 0x65, 0x78, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- },
- Init{
- Tracks: []*InitTrack{
- {
- ID: 256,
- TimeScale: 10000000,
- Format: &format.H264{
- PayloadTyp: 96,
- SPS: []byte{
- 0x67, 0x42, 0xc0, 0x1f, 0xd9, 0x00, 0xf0, 0x11,
- 0x7e, 0xf0, 0x11, 0x00, 0x00, 0x03, 0x00, 0x01,
- 0x00, 0x00, 0x03, 0x00, 0x30, 0x8f, 0x18, 0x32,
- 0x48,
- },
- PPS: []byte{
- 0x68, 0xcb, 0x8c, 0xb2,
- },
- PacketizationMode: 1,
- },
- },
- },
- },
- },
- {
- "h265 apple",
- []byte{
- 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70,
- 0x6d, 0x70, 0x34, 0x32, 0x00, 0x00, 0x00, 0x01,
- 0x6d, 0x70, 0x34, 0x31, 0x6d, 0x70, 0x34, 0x32,
- 0x69, 0x73, 0x6f, 0x6d, 0x68, 0x6c, 0x73, 0x66,
- 0x00, 0x00, 0x04, 0x96, 0x6d, 0x6f, 0x6f, 0x76,
- 0x00, 0x00, 0x00, 0x6c, 0x6d, 0x76, 0x68, 0x64,
- 0x00, 0x00, 0x00, 0x00, 0xd5, 0x5b, 0xc6, 0x84,
- 0xd5, 0x5b, 0xc6, 0x84, 0x00, 0x00, 0x02, 0x58,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x02, 0x70,
- 0x74, 0x72, 0x61, 0x6b, 0x00, 0x00, 0x00, 0x5c,
- 0x74, 0x6b, 0x68, 0x64, 0x00, 0x00, 0x00, 0x01,
- 0xd5, 0x5b, 0xc6, 0x84, 0xd5, 0x5b, 0xc6, 0x84,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x03, 0xc0, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00,
- 0x00, 0x00, 0x02, 0x0c, 0x6d, 0x64, 0x69, 0x61,
- 0x00, 0x00, 0x00, 0x20, 0x6d, 0x64, 0x68, 0x64,
- 0x00, 0x00, 0x00, 0x00, 0xd5, 0x5b, 0xc6, 0x84,
- 0xd5, 0x5b, 0xc6, 0x84, 0x00, 0x00, 0x17, 0x70,
- 0x00, 0x00, 0x00, 0x00, 0x15, 0xc7, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x31, 0x68, 0x64, 0x6c, 0x72,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x76, 0x69, 0x64, 0x65, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x43, 0x6f, 0x72, 0x65, 0x20, 0x4d, 0x65, 0x64,
- 0x69, 0x61, 0x20, 0x56, 0x69, 0x64, 0x65, 0x6f,
- 0x00, 0x00, 0x00, 0x01, 0xb3, 0x6d, 0x69, 0x6e,
- 0x66, 0x00, 0x00, 0x00, 0x14, 0x76, 0x6d, 0x68,
- 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x24, 0x64, 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00,
- 0x1c, 0x64, 0x72, 0x65, 0x66, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x0c, 0x75, 0x72, 0x6c, 0x20, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x01, 0x73, 0x73, 0x74, 0x62,
- 0x6c, 0x00, 0x00, 0x01, 0x27, 0x73, 0x74, 0x73,
- 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x01, 0x17, 0x68, 0x76, 0x63,
- 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x03, 0xc0, 0x02, 0x1c, 0x00, 0x48, 0x00,
- 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x18, 0xff, 0xff, 0x00,
- 0x00, 0x00, 0x8a, 0x68, 0x76, 0x63, 0x43, 0x01,
- 0x02, 0x00, 0x00, 0x00, 0x04, 0xb0, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x7b, 0xf0, 0x00, 0xfc, 0xfd,
- 0xfa, 0xfa, 0x00, 0x00, 0x0f, 0x03, 0xa0, 0x00,
- 0x01, 0x00, 0x18, 0x40, 0x01, 0x0c, 0x01, 0xff,
- 0xff, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0xb0,
- 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x7b,
- 0x18, 0xb0, 0x24, 0xa1, 0x00, 0x01, 0x00, 0x3c,
- 0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03,
- 0x00, 0xb0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03,
- 0x00, 0x7b, 0xa0, 0x07, 0x82, 0x00, 0x88, 0x7d,
- 0xb6, 0x71, 0x8b, 0x92, 0x44, 0x80, 0x53, 0x88,
- 0x88, 0x92, 0xcf, 0x24, 0xa6, 0x92, 0x72, 0xc9,
- 0x12, 0x49, 0x22, 0xdc, 0x91, 0xaa, 0x48, 0xfc,
- 0xa2, 0x23, 0xff, 0x00, 0x01, 0x00, 0x01, 0x6a,
- 0x02, 0x02, 0x02, 0x01, 0xa2, 0x00, 0x01, 0x00,
- 0x08, 0x44, 0x01, 0xc0, 0x25, 0x2f, 0x05, 0x32,
- 0x40, 0x00, 0x00, 0x00, 0x13, 0x63, 0x6f, 0x6c,
- 0x72, 0x6e, 0x63, 0x6c, 0x78, 0x00, 0x01, 0x00,
- 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0a,
- 0x66, 0x69, 0x65, 0x6c, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x0a, 0x63, 0x68, 0x72, 0x6d, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x10, 0x70, 0x61, 0x73, 0x70,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x74, 0x73,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x73, 0x63,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x14, 0x73, 0x74, 0x73, 0x7a,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
- 0x73, 0x74, 0x63, 0x6f, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x6a,
- 0x74, 0x72, 0x61, 0x6b, 0x00, 0x00, 0x00, 0x5c,
- 0x74, 0x6b, 0x68, 0x64, 0x00, 0x00, 0x00, 0x01,
- 0xd5, 0x5b, 0xc6, 0x84, 0xd5, 0x5b, 0xc6, 0x84,
- 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x06, 0x6d, 0x64, 0x69, 0x61,
- 0x00, 0x00, 0x00, 0x20, 0x6d, 0x64, 0x68, 0x64,
- 0x00, 0x00, 0x00, 0x00, 0xd5, 0x5b, 0xc6, 0x84,
- 0xd5, 0x5b, 0xc6, 0x84, 0x00, 0x00, 0x75, 0x30,
- 0x00, 0x00, 0x00, 0x00, 0x15, 0xc7, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x3a, 0x68, 0x64, 0x6c, 0x72,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x63, 0x6c, 0x63, 0x70, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x43, 0x6f, 0x72, 0x65, 0x20, 0x4d, 0x65, 0x64,
- 0x69, 0x61, 0x20, 0x43, 0x6c, 0x6f, 0x73, 0x65,
- 0x64, 0x20, 0x43, 0x61, 0x70, 0x74, 0x69, 0x6f,
- 0x6e, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x6d, 0x69,
- 0x6e, 0x66, 0x00, 0x00, 0x00, 0x0c, 0x6e, 0x6d,
- 0x68, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x24, 0x64, 0x69, 0x6e, 0x66, 0x00, 0x00,
- 0x00, 0x1c, 0x64, 0x72, 0x65, 0x66, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x0c, 0x75, 0x72, 0x6c, 0x20, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x6c, 0x73, 0x74,
- 0x62, 0x6c, 0x00, 0x00, 0x00, 0x20, 0x73, 0x74,
- 0x73, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x63, 0x36,
- 0x30, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x73, 0x74,
- 0x74, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x73, 0x74,
- 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x73, 0x74,
- 0x73, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x10, 0x73, 0x74, 0x63, 0x6f, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x48, 0x6d, 0x76, 0x65, 0x78, 0x00, 0x00,
- 0x00, 0x20, 0x74, 0x72, 0x65, 0x78, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x20, 0x74, 0x72, 0x65, 0x78, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- },
- Init{
- Tracks: []*InitTrack{{
- ID: 1,
- TimeScale: 6000,
- Format: &format.H265{
- PayloadTyp: 96,
- VPS: []byte{
- 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x02, 0x20,
- 0x00, 0x00, 0x03, 0x00, 0xb0, 0x00, 0x00, 0x03,
- 0x00, 0x00, 0x03, 0x00, 0x7b, 0x18, 0xb0, 0x24,
- },
- SPS: []byte{
- 0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03,
- 0x00, 0xb0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03,
- 0x00, 0x7b, 0xa0, 0x07, 0x82, 0x00, 0x88, 0x7d,
- 0xb6, 0x71, 0x8b, 0x92, 0x44, 0x80, 0x53, 0x88,
- 0x88, 0x92, 0xcf, 0x24, 0xa6, 0x92, 0x72, 0xc9,
- 0x12, 0x49, 0x22, 0xdc, 0x91, 0xaa, 0x48, 0xfc,
- 0xa2, 0x23, 0xff, 0x00, 0x01, 0x00, 0x01, 0x6a,
- 0x02, 0x02, 0x02, 0x01,
- },
- PPS: []byte{
- 0x44, 0x01, 0xc0, 0x25, 0x2f, 0x05, 0x32, 0x40,
- },
- },
- }},
- },
- },
- {
- "mpeg4audio",
- []byte{
- 0x00, 0x00, 0x00, 0x18,
- 'f', 't', 'y', 'p',
- 0x69, 0x73, 0x6f, 0x35, 0x00, 0x00, 0x00, 0x01,
- 0x69, 0x73, 0x6f, 0x35, 0x64, 0x61, 0x73, 0x68,
- 0x00, 0x00, 0x02, 0x43,
- 'm', 'o', 'o', 'v',
- 0x00, 0x00, 0x00, 0x6c,
- 'm', 'v', 'h', 'd',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x96, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0xa7,
- 't', 'r', 'a', 'k',
- 0x00, 0x00, 0x00, 0x5c,
- 't', 'k', 'h', 'd',
- 0x00, 0x00, 0x00, 0x07,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x43,
- 'm', 'd', 'i', 'a',
- 0x00, 0x00, 0x00, 0x20,
- 'm', 'd', 'h', 'd',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x96, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x55, 0xc4, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x38,
- 'h', 'd', 'l', 'r',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x73, 0x6f, 0x75, 0x6e, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x42, 0x72, 0x6f, 0x61, 0x64, 0x70, 0x65, 0x61,
- 0x6b, 0x20, 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x20,
- 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00,
- 0x00, 0x00, 0x00, 0xe3,
- 'm', 'i', 'n', 'f',
- 0x00, 0x00, 0x00, 0x10,
- 's', 'm', 'h', 'd',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x24,
- 'd', 'i', 'n', 'f',
- 0x00, 0x00, 0x00, 0x1c, 0x64, 0x72, 0x65, 0x66,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x0c, 0x75, 0x72, 0x6c, 0x20,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa7,
- 0x73, 0x74, 0x62, 0x6c, 0x00, 0x00, 0x00, 0x5b,
- 0x73, 0x74, 0x73, 0x64, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x4b,
- 0x6d, 0x70, 0x34, 0x61, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x10,
- 0x00, 0x00, 0x00, 0x00, 0xbb, 0x80, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x27, 0x65, 0x73, 0x64, 0x73,
- 0x00, 0x00, 0x00, 0x00, 0x03, 0x19, 0x00, 0x00,
- 0x00, 0x04, 0x11, 0x40, 0x15, 0x00, 0x30, 0x00,
- 0x00, 0x11, 0x94, 0x00, 0x00, 0x11, 0x94, 0x00,
- 0x05, 0x02, 0x11, 0x90, 0x06, 0x01, 0x02, 0x00,
- 0x00, 0x00, 0x10, 0x73, 0x74, 0x74, 0x73, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x10, 0x73, 0x74, 0x73, 0x63, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x14, 0x73, 0x74, 0x73, 0x7a, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x73,
- 0x74, 0x63, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x6d,
- 0x76, 0x65, 0x78, 0x00, 0x00, 0x00, 0x20, 0x74,
- 0x72, 0x65, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00,
- },
- Init{
- Tracks: []*InitTrack{
- {
- ID: 257,
- TimeScale: 10000000,
- Format: &format.MPEG4Audio{
- PayloadTyp: 96,
- Config: &mpeg4audio.Config{
- Type: mpeg4audio.ObjectTypeAACLC,
- SampleRate: 48000,
- ChannelCount: 2,
- },
- SizeLength: 13,
- IndexLength: 3,
- IndexDeltaLength: 3,
- },
- },
- },
- },
- },
- {
- "ignored closed captions",
- []byte{
- 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70,
- 0x6d, 0x70, 0x34, 0x32, 0x00, 0x00, 0x00, 0x01,
- 0x6d, 0x70, 0x34, 0x31, 0x6d, 0x70, 0x34, 0x32,
- 0x69, 0x73, 0x6f, 0x6d, 0x68, 0x6c, 0x73, 0x66,
- 0x00, 0x00, 0x04, 0x3f, 0x6d, 0x6f, 0x6f, 0x76,
- 0x00, 0x00, 0x00, 0x6c, 0x6d, 0x76, 0x68, 0x64,
- 0x00, 0x00, 0x00, 0x00, 0xd5, 0x5b, 0xc6, 0x62,
- 0xd5, 0x5b, 0xc6, 0x62, 0x00, 0x00, 0x02, 0x58,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x02, 0x19,
- 0x74, 0x72, 0x61, 0x6b, 0x00, 0x00, 0x00, 0x5c,
- 0x74, 0x6b, 0x68, 0x64, 0x00, 0x00, 0x00, 0x01,
- 0xd5, 0x5b, 0xc6, 0x62, 0xd5, 0x5b, 0xc6, 0x62,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x07, 0x80, 0x00, 0x00, 0x04, 0x38, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0xb5, 0x6d, 0x64, 0x69, 0x61,
- 0x00, 0x00, 0x00, 0x20, 0x6d, 0x64, 0x68, 0x64,
- 0x00, 0x00, 0x00, 0x00, 0xd5, 0x5b, 0xc6, 0x62,
- 0xd5, 0x5b, 0xc6, 0x62, 0x00, 0x00, 0x17, 0x70,
- 0x00, 0x00, 0x00, 0x00, 0x15, 0xc7, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x31, 0x68, 0x64, 0x6c, 0x72,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x76, 0x69, 0x64, 0x65, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x43, 0x6f, 0x72, 0x65, 0x20, 0x4d, 0x65, 0x64,
- 0x69, 0x61, 0x20, 0x56, 0x69, 0x64, 0x65, 0x6f,
- 0x00, 0x00, 0x00, 0x01, 0x5c, 0x6d, 0x69, 0x6e,
- 0x66, 0x00, 0x00, 0x00, 0x14, 0x76, 0x6d, 0x68,
- 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x24, 0x64, 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00,
- 0x1c, 0x64, 0x72, 0x65, 0x66, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x0c, 0x75, 0x72, 0x6c, 0x20, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x01, 0x1c, 0x73, 0x74, 0x62,
- 0x6c, 0x00, 0x00, 0x00, 0xd0, 0x73, 0x74, 0x73,
- 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0xc0, 0x61, 0x76, 0x63,
- 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x07, 0x80, 0x04, 0x38, 0x00, 0x48, 0x00,
- 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x18, 0xff, 0xff, 0x00,
- 0x00, 0x00, 0x33, 0x61, 0x76, 0x63, 0x43, 0x01,
- 0x64, 0x00, 0x2a, 0xff, 0xe1, 0x00, 0x1b, 0x27,
- 0x64, 0x00, 0x2a, 0xac, 0x52, 0x14, 0x07, 0x80,
- 0x22, 0x7e, 0x5f, 0xfc, 0x00, 0x04, 0x00, 0x05,
- 0xa8, 0x08, 0x08, 0x0d, 0xb6, 0x15, 0xaf, 0x7b,
- 0xe0, 0x20, 0x01, 0x00, 0x05, 0x28, 0xf9, 0x09,
- 0x09, 0xcb, 0x00, 0x00, 0x00, 0x13, 0x63, 0x6f,
- 0x6c, 0x72, 0x6e, 0x63, 0x6c, 0x78, 0x00, 0x01,
- 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x0a, 0x63, 0x68, 0x72, 0x6d, 0x01,
- 0x01, 0x00, 0x00, 0x00, 0x10, 0x70, 0x61, 0x73,
- 0x70, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x74,
- 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x73,
- 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x14, 0x73, 0x74, 0x73,
- 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x10, 0x73, 0x74, 0x63, 0x6f, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
- 0x6a, 0x74, 0x72, 0x61, 0x6b, 0x00, 0x00, 0x00,
- 0x5c, 0x74, 0x6b, 0x68, 0x64, 0x00, 0x00, 0x00,
- 0x01, 0xd5, 0x5b, 0xc6, 0x62, 0xd5, 0x5b, 0xc6,
- 0x62, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00,
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0x06, 0x6d, 0x64, 0x69,
- 0x61, 0x00, 0x00, 0x00, 0x20, 0x6d, 0x64, 0x68,
- 0x64, 0x00, 0x00, 0x00, 0x00, 0xd5, 0x5b, 0xc6,
- 0x62, 0xd5, 0x5b, 0xc6, 0x62, 0x00, 0x00, 0x75,
- 0x30, 0x00, 0x00, 0x00, 0x00, 0x15, 0xc7, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x3a, 0x68, 0x64, 0x6c,
- 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x63, 0x6c, 0x63, 0x70, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x4d, 0x65,
- 0x64, 0x69, 0x61, 0x20, 0x43, 0x6c, 0x6f, 0x73,
- 0x65, 0x64, 0x20, 0x43, 0x61, 0x70, 0x74, 0x69,
- 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x6d,
- 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00, 0x0c, 0x6e,
- 0x6d, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x24, 0x64, 0x69, 0x6e, 0x66, 0x00,
- 0x00, 0x00, 0x1c, 0x64, 0x72, 0x65, 0x66, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x0c, 0x75, 0x72, 0x6c, 0x20, 0x00,
- 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x6c, 0x73,
- 0x74, 0x62, 0x6c, 0x00, 0x00, 0x00, 0x20, 0x73,
- 0x74, 0x73, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x63,
- 0x36, 0x30, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x73,
- 0x74, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x73,
- 0x74, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x73,
- 0x74, 0x73, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x10, 0x73, 0x74, 0x63, 0x6f, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x48, 0x6d, 0x76, 0x65, 0x78, 0x00,
- 0x00, 0x00, 0x20, 0x74, 0x72, 0x65, 0x78, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x20, 0x74, 0x72, 0x65, 0x78, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
- 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- },
- Init{
- Tracks: []*InitTrack{{
- ID: 1,
- TimeScale: 6000,
- Format: &format.H264{
- PayloadTyp: 96,
- PacketizationMode: 1,
- SPS: []byte{
- 0x27, 0x64, 0x00, 0x2a, 0xac, 0x52, 0x14, 0x07,
- 0x80, 0x22, 0x7e, 0x5f, 0xfc, 0x00, 0x04, 0x00,
- 0x05, 0xa8, 0x08, 0x08, 0x0d, 0xb6, 0x15, 0xaf,
- 0x7b, 0xe0, 0x20,
- },
- PPS: []byte{
- 0x28, 0xf9, 0x09, 0x09, 0xcb,
- },
- },
- }},
- },
- },
- } {
- t.Run(ca.name, func(t *testing.T) {
- var init Init
- err := init.Unmarshal(ca.byts)
- require.NoError(t, err)
- require.Equal(t, ca.init, init)
- })
- }
-}
-
-func TestInitUnmarshalErrors(t *testing.T) {
- for _, ca := range []struct {
- name string
- byts []byte
- err string
- }{
- {
- "ac3",
- []byte{
- 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70,
- 0x6d, 0x70, 0x34, 0x32, 0x00, 0x00, 0x00, 0x01,
- 0x6d, 0x70, 0x34, 0x31, 0x6d, 0x70, 0x34, 0x32,
- 0x69, 0x73, 0x6f, 0x6d, 0x68, 0x6c, 0x73, 0x66,
- 0x00, 0x00, 0x02, 0x20, 0x6d, 0x6f, 0x6f, 0x76,
- 0x00, 0x00, 0x00, 0x6c, 0x6d, 0x76, 0x68, 0x64,
- 0x00, 0x00, 0x00, 0x00, 0xd5, 0x5b, 0xc6, 0x5d,
- 0xd5, 0x5b, 0xc6, 0x5d, 0x00, 0x00, 0x02, 0x58,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x84,
- 0x74, 0x72, 0x61, 0x6b, 0x00, 0x00, 0x00, 0x5c,
- 0x74, 0x6b, 0x68, 0x64, 0x00, 0x00, 0x00, 0x01,
- 0xd5, 0x5b, 0xc6, 0x5d, 0xd5, 0x5b, 0xc6, 0x5d,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x20, 0x6d, 0x64, 0x69, 0x61,
- 0x00, 0x00, 0x00, 0x20, 0x6d, 0x64, 0x68, 0x64,
- 0x00, 0x00, 0x00, 0x00, 0xd5, 0x5b, 0xc6, 0x5d,
- 0xd5, 0x5b, 0xc6, 0x5d, 0x00, 0x00, 0xbb, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x55, 0xc4, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x31, 0x68, 0x64, 0x6c, 0x72,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x73, 0x6f, 0x75, 0x6e, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x43, 0x6f, 0x72, 0x65, 0x20, 0x4d, 0x65, 0x64,
- 0x69, 0x61, 0x20, 0x41, 0x75, 0x64, 0x69, 0x6f,
- 0x00, 0x00, 0x00, 0x00, 0xc7, 0x6d, 0x69, 0x6e,
- 0x66, 0x00, 0x00, 0x00, 0x10, 0x73, 0x6d, 0x68,
- 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x24, 0x64, 0x69, 0x6e,
- 0x66, 0x00, 0x00, 0x00, 0x1c, 0x64, 0x72, 0x65,
- 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x72, 0x6c,
- 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x8b, 0x73, 0x74, 0x62, 0x6c, 0x00, 0x00, 0x00,
- 0x3f, 0x73, 0x74, 0x73, 0x64, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x2f, 0x61, 0x63, 0x2d, 0x33, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x80, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x0b, 0x64, 0x61, 0x63,
- 0x33, 0x0c, 0x3d, 0x40, 0x00, 0x00, 0x00, 0x10,
- 0x73, 0x74, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
- 0x73, 0x74, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
- 0x73, 0x74, 0x73, 0x7a, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x63, 0x6f,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x28, 0x6d, 0x76, 0x65, 0x78,
- 0x00, 0x00, 0x00, 0x20, 0x74, 0x72, 0x65, 0x78,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- },
- "no tracks found",
- },
- {
- "ac-3",
- []byte{
- 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70,
- 0x6d, 0x70, 0x34, 0x32, 0x00, 0x00, 0x00, 0x01,
- 0x6d, 0x70, 0x34, 0x31, 0x6d, 0x70, 0x34, 0x32,
- 0x69, 0x73, 0x6f, 0x6d, 0x68, 0x6c, 0x73, 0x66,
- 0x00, 0x00, 0x02, 0x22, 0x6d, 0x6f, 0x6f, 0x76,
- 0x00, 0x00, 0x00, 0x6c, 0x6d, 0x76, 0x68, 0x64,
- 0x00, 0x00, 0x00, 0x00, 0xd5, 0x5b, 0xc6, 0x5d,
- 0xd5, 0x5b, 0xc6, 0x5d, 0x00, 0x00, 0x02, 0x58,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x86,
- 0x74, 0x72, 0x61, 0x6b, 0x00, 0x00, 0x00, 0x5c,
- 0x74, 0x6b, 0x68, 0x64, 0x00, 0x00, 0x00, 0x01,
- 0xd5, 0x5b, 0xc6, 0x5d, 0xd5, 0x5b, 0xc6, 0x5d,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x22, 0x6d, 0x64, 0x69, 0x61,
- 0x00, 0x00, 0x00, 0x20, 0x6d, 0x64, 0x68, 0x64,
- 0x00, 0x00, 0x00, 0x00, 0xd5, 0x5b, 0xc6, 0x5d,
- 0xd5, 0x5b, 0xc6, 0x5d, 0x00, 0x00, 0xbb, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x55, 0xc4, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x31, 0x68, 0x64, 0x6c, 0x72,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x73, 0x6f, 0x75, 0x6e, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x43, 0x6f, 0x72, 0x65, 0x20, 0x4d, 0x65, 0x64,
- 0x69, 0x61, 0x20, 0x41, 0x75, 0x64, 0x69, 0x6f,
- 0x00, 0x00, 0x00, 0x00, 0xc9, 0x6d, 0x69, 0x6e,
- 0x66, 0x00, 0x00, 0x00, 0x10, 0x73, 0x6d, 0x68,
- 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x24, 0x64, 0x69, 0x6e,
- 0x66, 0x00, 0x00, 0x00, 0x1c, 0x64, 0x72, 0x65,
- 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x72, 0x6c,
- 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x8d, 0x73, 0x74, 0x62, 0x6c, 0x00, 0x00, 0x00,
- 0x41, 0x73, 0x74, 0x73, 0x64, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x31, 0x65, 0x63, 0x2d, 0x33, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x80, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x0d, 0x64, 0x65, 0x63,
- 0x33, 0x00, 0xc0, 0x20, 0x0f, 0x00, 0x00, 0x00,
- 0x00, 0x10, 0x73, 0x74, 0x74, 0x73, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x10, 0x73, 0x74, 0x73, 0x63, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x14, 0x73, 0x74, 0x73, 0x7a, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x73, 0x74,
- 0x63, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x6d, 0x76,
- 0x65, 0x78, 0x00, 0x00, 0x00, 0x20, 0x74, 0x72,
- 0x65, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00,
- },
- "no tracks found",
- },
- } {
- t.Run(ca.name, func(t *testing.T) {
- var init Init
- err := init.Unmarshal(ca.byts)
- require.EqualError(t, err, ca.err)
- })
- }
-}
diff --git a/internal/hls/fmp4/init_track.go b/internal/hls/fmp4/init_track.go
deleted file mode 100644
index df7a6e0c..00000000
--- a/internal/hls/fmp4/init_track.go
+++ /dev/null
@@ -1,526 +0,0 @@
-package fmp4
-
-import (
- gomp4 "github.com/abema/go-mp4"
- "github.com/aler9/gortsplib/v2/pkg/codecs/h264"
- "github.com/aler9/gortsplib/v2/pkg/codecs/h265"
- "github.com/aler9/gortsplib/v2/pkg/format"
-)
-
-// InitTrack is a track of Init.
-type InitTrack struct {
- ID int
- TimeScale uint32
- Format format.Format
-}
-
-func (track *InitTrack) marshal(w *mp4Writer) error {
- /*
- trak
- - tkhd
- - mdia
- - mdhd
- - hdlr
- - minf
- - vmhd (video)
- - smhd (audio)
- - dinf
- - dref
- - url
- - stbl
- - stsd
- - avc1 (h264)
- - avcC
- - btrt
- - hev1 (h265)
- - hvcC
- - mp4a (mpeg4audio)
- - esds
- - btrt
- - Opus (opus)
- - dOps
- - btrt
- - stts
- - stsc
- - stsz
- - stco
- */
-
- _, err := w.writeBoxStart(&gomp4.Trak{}) //
- if err != nil {
- return err
- }
-
- var h264SPS []byte
- var h264PPS []byte
- var h264SPSP h264.SPS
-
- var h265VPS []byte
- var h265SPS []byte
- var h265PPS []byte
- var h265SPSP h265.SPS
-
- var width int
- var height int
-
- switch ttrack := track.Format.(type) {
- case *format.H264:
- h264SPS = ttrack.SafeSPS()
- h264PPS = ttrack.SafePPS()
-
- err = h264SPSP.Unmarshal(h264SPS)
- if err != nil {
- return err
- }
-
- width = h264SPSP.Width()
- height = h264SPSP.Height()
-
- case *format.H265:
- h265VPS = ttrack.SafeVPS()
- h265SPS = ttrack.SafeSPS()
- h265PPS = ttrack.SafePPS()
-
- err = h265SPSP.Unmarshal(h265SPS)
- if err != nil {
- return err
- }
-
- width = h265SPSP.Width()
- height = h265SPSP.Height()
- }
-
- switch track.Format.(type) {
- case *format.H264, *format.H265:
- _, err = w.WriteBox(&gomp4.Tkhd{ //
- FullBox: gomp4.FullBox{
- Flags: [3]byte{0, 0, 3},
- },
- TrackID: uint32(track.ID),
- Width: uint32(width * 65536),
- Height: uint32(height * 65536),
- Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000},
- })
- if err != nil {
- return err
- }
-
- case *format.MPEG4Audio, *format.Opus:
- _, err = w.WriteBox(&gomp4.Tkhd{ //
- FullBox: gomp4.FullBox{
- Flags: [3]byte{0, 0, 3},
- },
- TrackID: uint32(track.ID),
- AlternateGroup: 1,
- Volume: 256,
- Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000},
- })
- if err != nil {
- return err
- }
- }
-
- _, err = w.writeBoxStart(&gomp4.Mdia{}) //
- if err != nil {
- return err
- }
-
- _, err = w.WriteBox(&gomp4.Mdhd{ //
- Timescale: track.TimeScale,
- Language: [3]byte{'u', 'n', 'd'},
- })
- if err != nil {
- return err
- }
-
- switch track.Format.(type) {
- case *format.H264, *format.H265:
- _, err = w.WriteBox(&gomp4.Hdlr{ //
- HandlerType: [4]byte{'v', 'i', 'd', 'e'},
- Name: "VideoHandler",
- })
- if err != nil {
- return err
- }
-
- case *format.MPEG4Audio, *format.Opus:
- _, err = w.WriteBox(&gomp4.Hdlr{ //
- HandlerType: [4]byte{'s', 'o', 'u', 'n'},
- Name: "SoundHandler",
- })
- if err != nil {
- return err
- }
- }
-
- _, err = w.writeBoxStart(&gomp4.Minf{}) //
- if err != nil {
- return err
- }
-
- switch track.Format.(type) {
- case *format.H264, *format.H265:
- _, err = w.WriteBox(&gomp4.Vmhd{ //
- FullBox: gomp4.FullBox{
- Flags: [3]byte{0, 0, 1},
- },
- })
- if err != nil {
- return err
- }
-
- case *format.MPEG4Audio, *format.Opus:
- _, err = w.WriteBox(&gomp4.Smhd{ //
- })
- if err != nil {
- return err
- }
- }
-
- _, err = w.writeBoxStart(&gomp4.Dinf{}) //
- if err != nil {
- return err
- }
-
- _, err = w.writeBoxStart(&gomp4.Dref{ //
- EntryCount: 1,
- })
- if err != nil {
- return err
- }
-
- _, err = w.WriteBox(&gomp4.Url{ //
- FullBox: gomp4.FullBox{
- Flags: [3]byte{0, 0, 1},
- },
- })
- if err != nil {
- return err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return err
- }
-
- _, err = w.writeBoxStart(&gomp4.Stbl{}) //
- if err != nil {
- return err
- }
-
- _, err = w.writeBoxStart(&gomp4.Stsd{ //
- EntryCount: 1,
- })
- if err != nil {
- return err
- }
-
- switch ttrack := track.Format.(type) {
- case *format.H264:
- _, err = w.writeBoxStart(&gomp4.VisualSampleEntry{ //
- SampleEntry: gomp4.SampleEntry{
- AnyTypeBox: gomp4.AnyTypeBox{
- Type: gomp4.BoxTypeAvc1(),
- },
- DataReferenceIndex: 1,
- },
- Width: uint16(width),
- Height: uint16(height),
- Horizresolution: 4718592,
- Vertresolution: 4718592,
- FrameCount: 1,
- Depth: 24,
- PreDefined3: -1,
- })
- if err != nil {
- return err
- }
-
- _, err = w.WriteBox(&gomp4.AVCDecoderConfiguration{ //
- AnyTypeBox: gomp4.AnyTypeBox{
- Type: gomp4.BoxTypeAvcC(),
- },
- ConfigurationVersion: 1,
- Profile: h264SPSP.ProfileIdc,
- ProfileCompatibility: h264SPS[2],
- Level: h264SPSP.LevelIdc,
- LengthSizeMinusOne: 3,
- NumOfSequenceParameterSets: 1,
- SequenceParameterSets: []gomp4.AVCParameterSet{
- {
- Length: uint16(len(h264SPS)),
- NALUnit: h264SPS,
- },
- },
- NumOfPictureParameterSets: 1,
- PictureParameterSets: []gomp4.AVCParameterSet{
- {
- Length: uint16(len(h264PPS)),
- NALUnit: h264PPS,
- },
- },
- })
- if err != nil {
- return err
- }
-
- _, err = w.WriteBox(&gomp4.Btrt{ //
- MaxBitrate: 1000000,
- AvgBitrate: 1000000,
- })
- if err != nil {
- return err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return err
- }
-
- case *format.H265:
- _, err = w.writeBoxStart(&gomp4.VisualSampleEntry{ //
- SampleEntry: gomp4.SampleEntry{
- AnyTypeBox: gomp4.AnyTypeBox{
- Type: gomp4.BoxTypeHev1(),
- },
- DataReferenceIndex: 1,
- },
- Width: uint16(width),
- Height: uint16(height),
- Horizresolution: 4718592,
- Vertresolution: 4718592,
- FrameCount: 1,
- Depth: 24,
- PreDefined3: -1,
- })
- if err != nil {
- return err
- }
-
- _, err = w.WriteBox(&gomp4.HvcC{ //
- ConfigurationVersion: 1,
- GeneralProfileIdc: h265SPSP.ProfileTierLevel.GeneralProfileIdc,
- GeneralProfileCompatibility: h265SPSP.ProfileTierLevel.GeneralProfileCompatibilityFlag,
- GeneralConstraintIndicator: [6]uint8{
- h265SPS[7], h265SPS[8], h265SPS[9],
- h265SPS[10], h265SPS[11], h265SPS[12],
- },
- GeneralLevelIdc: h265SPSP.ProfileTierLevel.GeneralLevelIdc,
- // MinSpatialSegmentationIdc
- // ParallelismType
- ChromaFormatIdc: uint8(h265SPSP.ChromaFormatIdc),
- BitDepthLumaMinus8: uint8(h265SPSP.BitDepthLumaMinus8),
- BitDepthChromaMinus8: uint8(h265SPSP.BitDepthChromaMinus8),
- // AvgFrameRate
- // ConstantFrameRate
- NumTemporalLayers: 1,
- // TemporalIdNested
- LengthSizeMinusOne: 3,
- NumOfNaluArrays: 3,
- NaluArrays: []gomp4.HEVCNaluArray{
- {
- NaluType: byte(h265.NALUType_VPS_NUT),
- NumNalus: 1,
- Nalus: []gomp4.HEVCNalu{{
- Length: uint16(len(h265VPS)),
- NALUnit: h265VPS,
- }},
- },
- {
- NaluType: byte(h265.NALUType_SPS_NUT),
- NumNalus: 1,
- Nalus: []gomp4.HEVCNalu{{
- Length: uint16(len(h265SPS)),
- NALUnit: h265SPS,
- }},
- },
- {
- NaluType: byte(h265.NALUType_PPS_NUT),
- NumNalus: 1,
- Nalus: []gomp4.HEVCNalu{{
- Length: uint16(len(h265PPS)),
- NALUnit: h265PPS,
- }},
- },
- },
- })
- if err != nil {
- return err
- }
-
- _, err = w.WriteBox(&gomp4.Btrt{ //
- MaxBitrate: 1000000,
- AvgBitrate: 1000000,
- })
- if err != nil {
- return err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return err
- }
-
- case *format.MPEG4Audio:
- _, err = w.writeBoxStart(&gomp4.AudioSampleEntry{ //
- SampleEntry: gomp4.SampleEntry{
- AnyTypeBox: gomp4.AnyTypeBox{
- Type: gomp4.BoxTypeMp4a(),
- },
- DataReferenceIndex: 1,
- },
- ChannelCount: uint16(ttrack.Config.ChannelCount),
- SampleSize: 16,
- SampleRate: uint32(ttrack.ClockRate() * 65536),
- })
- if err != nil {
- return err
- }
-
- enc, _ := ttrack.Config.Marshal()
-
- _, err = w.WriteBox(&gomp4.Esds{ //
- Descriptors: []gomp4.Descriptor{
- {
- Tag: gomp4.ESDescrTag,
- Size: 32 + uint32(len(enc)),
- ESDescriptor: &gomp4.ESDescriptor{
- ESID: uint16(track.ID),
- },
- },
- {
- Tag: gomp4.DecoderConfigDescrTag,
- Size: 18 + uint32(len(enc)),
- DecoderConfigDescriptor: &gomp4.DecoderConfigDescriptor{
- ObjectTypeIndication: 0x40,
- StreamType: 0x05,
- UpStream: false,
- Reserved: true,
- MaxBitrate: 128825,
- AvgBitrate: 128825,
- },
- },
- {
- Tag: gomp4.DecSpecificInfoTag,
- Size: uint32(len(enc)),
- Data: enc,
- },
- {
- Tag: gomp4.SLConfigDescrTag,
- Size: 1,
- Data: []byte{0x02},
- },
- },
- })
- if err != nil {
- return err
- }
-
- _, err = w.WriteBox(&gomp4.Btrt{ //
- MaxBitrate: 128825,
- AvgBitrate: 128825,
- })
- if err != nil {
- return err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return err
- }
-
- case *format.Opus:
- _, err = w.writeBoxStart(&gomp4.AudioSampleEntry{ //
- SampleEntry: gomp4.SampleEntry{
- AnyTypeBox: gomp4.AnyTypeBox{
- Type: BoxTypeOpus(),
- },
- DataReferenceIndex: 1,
- },
- ChannelCount: uint16(ttrack.ChannelCount),
- SampleSize: 16,
- SampleRate: uint32(ttrack.ClockRate() * 65536),
- })
- if err != nil {
- return err
- }
-
- _, err = w.WriteBox(&DOps{ //
- OutputChannelCount: uint8(ttrack.ChannelCount),
- PreSkip: 312,
- InputSampleRate: uint32(ttrack.ClockRate()),
- })
- if err != nil {
- return err
- }
-
- _, err = w.WriteBox(&gomp4.Btrt{ //
- MaxBitrate: 128825,
- AvgBitrate: 128825,
- })
- if err != nil {
- return err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return err
- }
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return err
- }
-
- _, err = w.WriteBox(&gomp4.Stts{ //
- })
- if err != nil {
- return err
- }
-
- _, err = w.WriteBox(&gomp4.Stsc{ //
- })
- if err != nil {
- return err
- }
-
- _, err = w.WriteBox(&gomp4.Stsz{ //
- })
- if err != nil {
- return err
- }
-
- _, err = w.WriteBox(&gomp4.Stco{ //
- })
- if err != nil {
- return err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return err
- }
-
- return nil
-}
diff --git a/internal/hls/fmp4/mp4_writer.go b/internal/hls/fmp4/mp4_writer.go
deleted file mode 100644
index 39312578..00000000
--- a/internal/hls/fmp4/mp4_writer.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package fmp4
-
-import (
- "io"
-
- gomp4 "github.com/abema/go-mp4"
- "github.com/orcaman/writerseeker"
-)
-
-type mp4Writer struct {
- buf *writerseeker.WriterSeeker
- w *gomp4.Writer
-}
-
-func newMP4Writer() *mp4Writer {
- w := &mp4Writer{
- buf: &writerseeker.WriterSeeker{},
- }
-
- w.w = gomp4.NewWriter(w.buf)
-
- return w
-}
-
-func (w *mp4Writer) writeBoxStart(box gomp4.IImmutableBox) (int, error) {
- bi := &gomp4.BoxInfo{
- Type: box.GetType(),
- }
- var err error
- bi, err = w.w.StartBox(bi)
- if err != nil {
- return 0, err
- }
-
- _, err = gomp4.Marshal(w.w, box, gomp4.Context{})
- if err != nil {
- return 0, err
- }
-
- return int(bi.Offset), nil
-}
-
-func (w *mp4Writer) writeBoxEnd() error {
- _, err := w.w.EndBox()
- return err
-}
-
-func (w *mp4Writer) WriteBox(box gomp4.IImmutableBox) (int, error) {
- off, err := w.writeBoxStart(box)
- if err != nil {
- return 0, err
- }
-
- err = w.writeBoxEnd()
- if err != nil {
- return 0, err
- }
-
- return off, nil
-}
-
-func (w *mp4Writer) rewriteBox(off int, box gomp4.IImmutableBox) error {
- prevOff, err := w.w.Seek(0, io.SeekCurrent)
- if err != nil {
- return err
- }
-
- _, err = w.w.Seek(int64(off), io.SeekStart)
- if err != nil {
- return err
- }
-
- _, err = w.writeBoxStart(box)
- if err != nil {
- return err
- }
-
- err = w.writeBoxEnd()
- if err != nil {
- return err
- }
-
- _, err = w.w.Seek(prevOff, io.SeekStart)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (w *mp4Writer) bytes() []byte {
- return w.buf.Bytes()
-}
diff --git a/internal/hls/fmp4/part.go b/internal/hls/fmp4/part.go
deleted file mode 100644
index 9deaf569..00000000
--- a/internal/hls/fmp4/part.go
+++ /dev/null
@@ -1,263 +0,0 @@
-package fmp4
-
-import (
- "bytes"
- "fmt"
-
- gomp4 "github.com/abema/go-mp4"
-)
-
-const (
- trunFlagDataOffsetPreset = 0x01
- trunFlagSampleDurationPresent = 0x100
- trunFlagSampleSizePresent = 0x200
- trunFlagSampleFlagsPresent = 0x400
- trunFlagSampleCompositionTimeOffsetPresentOrV1 = 0x800
-
- sampleFlagIsNonSyncSample = 1 << 16
-)
-
-// Part is a FMP4 part file.
-type Part struct {
- Tracks []*PartTrack
-}
-
-// Parts is a sequence of FMP4 parts.
-type Parts []*Part
-
-// Unmarshal decodes one or more FMP4 parts.
-func (ps *Parts) Unmarshal(byts []byte) error {
- type readState int
-
- const (
- waitingMoof readState = iota
- waitingTraf
- waitingTfdtTfhdTrun
- )
-
- state := waitingMoof
- var curPart *Part
- var moofOffset uint64
- var curTrack *PartTrack
- var tfdt *gomp4.Tfdt
- var tfhd *gomp4.Tfhd
-
- _, err := gomp4.ReadBoxStructure(bytes.NewReader(byts), func(h *gomp4.ReadHandle) (interface{}, error) {
- switch h.BoxInfo.Type.String() {
- case "moof":
- if state != waitingMoof {
- return nil, fmt.Errorf("unexpected moof")
- }
-
- curPart = &Part{}
- *ps = append(*ps, curPart)
- moofOffset = h.BoxInfo.Offset
- state = waitingTraf
-
- case "traf":
- if state != waitingTraf && state != waitingTfdtTfhdTrun {
- return nil, fmt.Errorf("unexpected traf")
- }
-
- if curTrack != nil {
- if tfdt == nil || tfhd == nil || curTrack.Samples == nil {
- return nil, fmt.Errorf("parse error")
- }
- }
-
- curTrack = &PartTrack{}
- curPart.Tracks = append(curPart.Tracks, curTrack)
- tfdt = nil
- tfhd = nil
- state = waitingTfdtTfhdTrun
-
- case "tfhd":
- if state != waitingTfdtTfhdTrun || tfhd != nil {
- return nil, fmt.Errorf("unexpected tfhd")
- }
-
- box, _, err := h.ReadPayload()
- if err != nil {
- return nil, err
- }
-
- tfhd = box.(*gomp4.Tfhd)
- curTrack.ID = int(tfhd.TrackID)
-
- case "tfdt":
- if state != waitingTfdtTfhdTrun || tfdt != nil {
- return nil, fmt.Errorf("unexpected tfdt")
- }
-
- box, _, err := h.ReadPayload()
- if err != nil {
- return nil, err
- }
-
- tfdt = box.(*gomp4.Tfdt)
-
- if tfdt.FullBox.Version != 1 {
- return nil, fmt.Errorf("unsupported tfdt version")
- }
-
- curTrack.BaseTime = tfdt.BaseMediaDecodeTimeV1
-
- case "trun":
- if state != waitingTfdtTfhdTrun || tfhd == nil {
- return nil, fmt.Errorf("unexpected trun")
- }
-
- box, _, err := h.ReadPayload()
- if err != nil {
- return nil, err
- }
- trun := box.(*gomp4.Trun)
-
- trunFlags := uint16(trun.Flags[1])<<8 | uint16(trun.Flags[2])
- if (trunFlags & trunFlagDataOffsetPreset) == 0 {
- return nil, fmt.Errorf("unsupported flags")
- }
-
- existing := len(curTrack.Samples)
- tmp := make([]*PartSample, existing+len(trun.Entries))
- copy(tmp, curTrack.Samples)
- curTrack.Samples = tmp
-
- ptr := byts[uint64(trun.DataOffset)+moofOffset:]
-
- for i, e := range trun.Entries {
- s := &PartSample{}
-
- if (trunFlags & trunFlagSampleDurationPresent) != 0 {
- s.Duration = e.SampleDuration
- } else {
- s.Duration = tfhd.DefaultSampleDuration
- }
-
- s.PTSOffset = e.SampleCompositionTimeOffsetV1
-
- var sampleFlags uint32
- if (trunFlags & trunFlagSampleFlagsPresent) != 0 {
- sampleFlags = e.SampleFlags
- } else {
- sampleFlags = tfhd.DefaultSampleFlags
- }
- s.IsNonSyncSample = ((sampleFlags & sampleFlagIsNonSyncSample) != 0)
-
- var size uint32
- if (trunFlags & trunFlagSampleSizePresent) != 0 {
- size = e.SampleSize
- } else {
- size = tfhd.DefaultSampleSize
- }
-
- s.Payload = ptr[:size]
- ptr = ptr[size:]
-
- curTrack.Samples[existing+i] = s
- }
-
- case "mdat":
- if state != waitingTraf && state != waitingTfdtTfhdTrun {
- return nil, fmt.Errorf("unexpected mdat")
- }
-
- if curTrack != nil {
- if tfdt == nil || tfhd == nil || curTrack.Samples == nil {
- return nil, fmt.Errorf("parse error")
- }
- }
-
- state = waitingMoof
- return nil, nil
- }
-
- return h.Expand()
- })
- if err != nil {
- return err
- }
-
- if state != waitingMoof {
- return fmt.Errorf("decode error")
- }
-
- return nil
-}
-
-// Marshal encodes a FMP4 part file.
-func (p *Part) Marshal() ([]byte, error) {
- /*
- moof
- - mfhd
- - traf (video)
- - traf (audio)
- mdat
- */
-
- w := newMP4Writer()
-
- moofOffset, err := w.writeBoxStart(&gomp4.Moof{}) //
- if err != nil {
- return nil, err
- }
-
- _, err = w.WriteBox(&gomp4.Mfhd{ //
- SequenceNumber: 0,
- })
- if err != nil {
- return nil, err
- }
-
- trackLen := len(p.Tracks)
- truns := make([]*gomp4.Trun, trackLen)
- trunOffsets := make([]int, trackLen)
- dataOffsets := make([]int, trackLen)
- dataSize := 0
-
- for i, track := range p.Tracks {
- trun, trunOffset, err := track.marshal(w)
- if err != nil {
- return nil, err
- }
-
- dataOffsets[i] = dataSize
-
- for _, sample := range track.Samples {
- dataSize += len(sample.Payload)
- }
-
- truns[i] = trun
- trunOffsets[i] = trunOffset
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return nil, err
- }
-
- mdat := &gomp4.Mdat{} //
- mdat.Data = make([]byte, dataSize)
- pos := 0
-
- for _, track := range p.Tracks {
- for _, sample := range track.Samples {
- pos += copy(mdat.Data[pos:], sample.Payload)
- }
- }
-
- mdatOffset, err := w.WriteBox(mdat)
- if err != nil {
- return nil, err
- }
-
- for i := range p.Tracks {
- truns[i].DataOffset = int32(dataOffsets[i] + mdatOffset - moofOffset + 8)
- err = w.rewriteBox(trunOffsets[i], truns[i])
- if err != nil {
- return nil, err
- }
- }
-
- return w.bytes(), nil
-}
diff --git a/internal/hls/fmp4/part_test.go b/internal/hls/fmp4/part_test.go
deleted file mode 100644
index ea7b5c46..00000000
--- a/internal/hls/fmp4/part_test.go
+++ /dev/null
@@ -1,249 +0,0 @@
-package fmp4
-
-import (
- "bytes"
- "testing"
-
- gomp4 "github.com/abema/go-mp4"
- "github.com/stretchr/testify/require"
-)
-
-func testMP4(t *testing.T, byts []byte, boxes []gomp4.BoxPath) {
- i := 0
- _, err := gomp4.ReadBoxStructure(bytes.NewReader(byts), func(h *gomp4.ReadHandle) (interface{}, error) {
- require.Equal(t, boxes[i], h.Path)
- i++
- return h.Expand()
- })
- require.NoError(t, err)
-}
-
-func TestPartMarshal(t *testing.T) {
- testVideoSamples := []*PartSample{
- {
- Duration: 2 * 90000,
- Payload: []byte{
- 0x00, 0x00, 0x00, 0x04,
- 0x01, 0x02, 0x03, 0x04, // SPS
- 0x00, 0x00, 0x00, 0x01,
- 0x08, // PPS
- 0x00, 0x00, 0x00, 0x01,
- 0x05, // IDR
- },
- },
- {
- Duration: 2 * 90000,
- Payload: []byte{
- 0x00, 0x00, 0x00, 0x01,
- 0x01, // non-IDR
- },
- IsNonSyncSample: true,
- },
- {
- Duration: 1 * 90000,
- Payload: []byte{
- 0x00, 0x00, 0x00, 0x01,
- 0x01, // non-IDR
- },
- IsNonSyncSample: true,
- },
- }
-
- testAudioSamples := []*PartSample{
- {
- Duration: 500 * 48000 / 1000,
- Payload: []byte{
- 0x01, 0x02, 0x03, 0x04,
- },
- },
- {
- Duration: 1 * 48000,
- Payload: []byte{
- 0x01, 0x02, 0x03, 0x04,
- },
- },
- }
-
- t.Run("video + audio", func(t *testing.T) {
- part := Part{
- Tracks: []*PartTrack{
- {
- ID: 1,
- Samples: testVideoSamples,
- IsVideo: true,
- },
- {
- ID: 2,
- BaseTime: 3 * 48000,
- Samples: testAudioSamples,
- },
- },
- }
-
- byts, err := part.Marshal()
- require.NoError(t, err)
-
- boxes := []gomp4.BoxPath{
- {gomp4.BoxTypeMoof()},
- {gomp4.BoxTypeMoof(), gomp4.BoxTypeMfhd()},
- {gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf()},
- {gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTfhd()},
- {gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTfdt()},
- {gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTrun()},
- {gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf()},
- {gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTfhd()},
- {gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTfdt()},
- {gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTrun()},
- {gomp4.BoxTypeMdat()},
- }
- testMP4(t, byts, boxes)
- })
-
- t.Run("video only", func(t *testing.T) {
- part := Part{
- Tracks: []*PartTrack{
- {
- ID: 1,
- Samples: testVideoSamples,
- IsVideo: true,
- },
- },
- }
-
- byts, err := part.Marshal()
- require.NoError(t, err)
-
- boxes := []gomp4.BoxPath{
- {gomp4.BoxTypeMoof()},
- {gomp4.BoxTypeMoof(), gomp4.BoxTypeMfhd()},
- {gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf()},
- {gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTfhd()},
- {gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTfdt()},
- {gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTrun()},
- {gomp4.BoxTypeMdat()},
- }
- testMP4(t, byts, boxes)
- })
-
- t.Run("audio only", func(t *testing.T) {
- part := Part{
- Tracks: []*PartTrack{
- {
- ID: 1,
- Samples: testAudioSamples,
- },
- },
- }
-
- byts, err := part.Marshal()
- require.NoError(t, err)
-
- boxes := []gomp4.BoxPath{
- {gomp4.BoxTypeMoof()},
- {gomp4.BoxTypeMoof(), gomp4.BoxTypeMfhd()},
- {gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf()},
- {gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTfhd()},
- {gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTfdt()},
- {gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTrun()},
- {gomp4.BoxTypeMdat()},
- }
- testMP4(t, byts, boxes)
- })
-}
-
-func TestPartUnmarshal(t *testing.T) {
- byts := []byte{
- 0x00, 0x00, 0x00, 0xd8, 0x6d, 0x6f, 0x6f, 0x66,
- 0x00, 0x00, 0x00, 0x10, 0x6d, 0x66, 0x68, 0x64,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x70, 0x74, 0x72, 0x61, 0x66,
- 0x00, 0x00, 0x00, 0x10, 0x74, 0x66, 0x68, 0x64,
- 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x14, 0x74, 0x66, 0x64, 0x74,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44,
- 0x74, 0x72, 0x75, 0x6e, 0x01, 0x00, 0x0f, 0x01,
- 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xe0,
- 0x00, 0x02, 0xbf, 0x20, 0x00, 0x00, 0x00, 0x12,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x02, 0xbf, 0x20, 0x00, 0x00, 0x00, 0x05,
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x01, 0x5f, 0x90, 0x00, 0x00, 0x00, 0x05,
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x50, 0x74, 0x72, 0x61, 0x66,
- 0x00, 0x00, 0x00, 0x10, 0x74, 0x66, 0x68, 0x64,
- 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
- 0x00, 0x00, 0x00, 0x14, 0x74, 0x66, 0x64, 0x74,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x02, 0x32, 0x80, 0x00, 0x00, 0x00, 0x24,
- 0x74, 0x72, 0x75, 0x6e, 0x01, 0x00, 0x03, 0x01,
- 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xfc,
- 0x00, 0x00, 0x5d, 0xc0, 0x00, 0x00, 0x00, 0x04,
- 0x00, 0x00, 0xbb, 0x80, 0x00, 0x00, 0x00, 0x04,
- 0x00, 0x00, 0x00, 0x2c, 0x6d, 0x64, 0x61, 0x74,
- 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x03, 0x04,
- 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00,
- 0x01, 0x05, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00,
- 0x00, 0x00, 0x01, 0x01, 0x01, 0x02, 0x03, 0x04,
- 0x01, 0x02, 0x03, 0x04,
- }
-
- var parts Parts
- err := parts.Unmarshal(byts)
- require.NoError(t, err)
-
- require.Equal(t, Parts{{
- Tracks: []*PartTrack{
- {
- ID: 1,
- Samples: []*PartSample{
- {
- Duration: 2 * 90000,
- Payload: []byte{
- 0x00, 0x00, 0x00, 0x04,
- 0x01, 0x02, 0x03, 0x04, // SPS
- 0x00, 0x00, 0x00, 0x01,
- 0x08, // PPS
- 0x00, 0x00, 0x00, 0x01,
- 0x05, // IDR
- },
- },
- {
- Duration: 2 * 90000,
- Payload: []byte{
- 0x00, 0x00, 0x00, 0x01,
- 0x01, // non-IDR
- },
- IsNonSyncSample: true,
- },
- {
- Duration: 1 * 90000,
- Payload: []byte{
- 0x00, 0x00, 0x00, 0x01,
- 0x01, // non-IDR
- },
- IsNonSyncSample: true,
- },
- },
- },
- {
- ID: 2,
- BaseTime: 3 * 48000,
- Samples: []*PartSample{
- {
- Duration: 500 * 48000 / 1000,
- Payload: []byte{
- 0x01, 0x02, 0x03, 0x04,
- },
- },
- {
- Duration: 1 * 48000,
- Payload: []byte{
- 0x01, 0x02, 0x03, 0x04,
- },
- },
- },
- },
- },
- }}, parts)
-}
diff --git a/internal/hls/fmp4/part_track.go b/internal/hls/fmp4/part_track.go
deleted file mode 100644
index 1c996cb1..00000000
--- a/internal/hls/fmp4/part_track.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package fmp4
-
-import (
- gomp4 "github.com/abema/go-mp4"
-)
-
-// PartSample is a sample of a PartTrack.
-type PartSample struct {
- Duration uint32
- PTSOffset int32
- IsNonSyncSample bool
- Payload []byte
-}
-
-// PartTrack is a track of Part.
-type PartTrack struct {
- ID int
- BaseTime uint64
- Samples []*PartSample
- IsVideo bool // marshal only
-}
-
-func (pt *PartTrack) marshal(w *mp4Writer) (*gomp4.Trun, int, error) {
- /*
- traf
- - tfhd
- - tfdt
- - trun
- */
-
- _, err := w.writeBoxStart(&gomp4.Traf{}) //
- if err != nil {
- return nil, 0, err
- }
-
- flags := 0
-
- _, err = w.WriteBox(&gomp4.Tfhd{ //
- FullBox: gomp4.FullBox{
- Flags: [3]byte{2, byte(flags >> 8), byte(flags)},
- },
- TrackID: uint32(pt.ID),
- })
- if err != nil {
- return nil, 0, err
- }
-
- _, err = w.WriteBox(&gomp4.Tfdt{ //
- FullBox: gomp4.FullBox{
- Version: 1,
- },
- // sum of decode durations of all earlier samples
- BaseMediaDecodeTimeV1: pt.BaseTime,
- })
- if err != nil {
- return nil, 0, err
- }
-
- if pt.IsVideo {
- flags = trunFlagDataOffsetPreset |
- trunFlagSampleDurationPresent |
- trunFlagSampleSizePresent |
- trunFlagSampleFlagsPresent |
- trunFlagSampleCompositionTimeOffsetPresentOrV1
- } else {
- flags = trunFlagDataOffsetPreset |
- trunFlagSampleDurationPresent |
- trunFlagSampleSizePresent
- }
-
- trun := &gomp4.Trun{ //
- FullBox: gomp4.FullBox{
- Version: 1,
- Flags: [3]byte{0, byte(flags >> 8), byte(flags)},
- },
- SampleCount: uint32(len(pt.Samples)),
- }
-
- for _, sample := range pt.Samples {
- if pt.IsVideo {
- var flags uint32
- if sample.IsNonSyncSample {
- flags |= sampleFlagIsNonSyncSample
- }
-
- trun.Entries = append(trun.Entries, gomp4.TrunEntry{
- SampleDuration: sample.Duration,
- SampleSize: uint32(len(sample.Payload)),
- SampleFlags: flags,
- SampleCompositionTimeOffsetV1: sample.PTSOffset,
- })
- } else {
- trun.Entries = append(trun.Entries, gomp4.TrunEntry{
- SampleDuration: sample.Duration,
- SampleSize: uint32(len(sample.Payload)),
- })
- }
- }
-
- trunOffset, err := w.WriteBox(trun)
- if err != nil {
- return nil, 0, err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return nil, 0, err
- }
-
- return trun, trunOffset, nil
-}
diff --git a/internal/hls/m3u8/m3u8.go b/internal/hls/m3u8/m3u8.go
deleted file mode 100644
index 4955a871..00000000
--- a/internal/hls/m3u8/m3u8.go
+++ /dev/null
@@ -1,106 +0,0 @@
-// Package m3u8 contains a M3U8 parser.
-package m3u8
-
-import (
- "bytes"
- "errors"
- "regexp"
- "strings"
-
- gm3u8 "github.com/grafov/m3u8"
-)
-
-var reKeyValue = regexp.MustCompile(`([a-zA-Z0-9_-]+)=("[^"]+"|[^",]+)`)
-
-func decodeParamsLine(line string) map[string]string {
- out := make(map[string]string)
- for _, kv := range reKeyValue.FindAllStringSubmatch(line, -1) {
- k, v := kv[1], kv[2]
- out[k] = strings.Trim(v, ` "`)
- }
- return out
-}
-
-// MasterPlaylist is a master playlist.
-type MasterPlaylist struct {
- gm3u8.MasterPlaylist
- Alternatives []*gm3u8.Alternative
-}
-
-func (MasterPlaylist) isPlaylist() {}
-
-func newMasterPlaylist(byts []byte, mpl *gm3u8.MasterPlaylist) (*MasterPlaylist, error) {
- var alternatives []*gm3u8.Alternative
-
- // https://github.com/grafov/m3u8/blob/036100c52a87e26c62be56df85450e9c703201a6/reader.go#L301
- for _, line := range strings.Split(string(byts), "\n") {
- if strings.HasPrefix(line, "#EXT-X-MEDIA:") {
- var alt gm3u8.Alternative
- for k, v := range decodeParamsLine(line[13:]) {
- switch k {
- case "TYPE":
- alt.Type = v
- case "GROUP-ID":
- alt.GroupId = v
- case "LANGUAGE":
- alt.Language = v
- case "NAME":
- alt.Name = v
- case "DEFAULT":
- switch {
- case strings.ToUpper(v) == "YES":
- alt.Default = true
- case strings.ToUpper(v) == "NO":
- alt.Default = false
- default:
- return nil, errors.New("value must be YES or NO")
- }
- case "AUTOSELECT":
- alt.Autoselect = v
- case "FORCED":
- alt.Forced = v
- case "CHARACTERISTICS":
- alt.Characteristics = v
- case "SUBTITLES":
- alt.Subtitles = v
- case "URI":
- alt.URI = v
- }
- }
- alternatives = append(alternatives, &alt)
- }
- }
-
- return &MasterPlaylist{
- MasterPlaylist: *mpl,
- Alternatives: alternatives,
- }, nil
-}
-
-// MediaPlaylist is a media playlist.
-type MediaPlaylist gm3u8.MediaPlaylist
-
-func (MediaPlaylist) isPlaylist() {}
-
-// Playlist is a M3U8 playlist.
-type Playlist interface {
- isPlaylist()
-}
-
-// Unmarshal decodes a M3U8 Playlist.
-func Unmarshal(byts []byte) (Playlist, error) {
- pl, _, err := gm3u8.Decode(*(bytes.NewBuffer(byts)), true)
- if err != nil {
- return nil, err
- }
-
- switch tpl := pl.(type) {
- case *gm3u8.MasterPlaylist:
- return newMasterPlaylist(byts, tpl)
-
- case *gm3u8.MediaPlaylist:
- return (*MediaPlaylist)(tpl), nil
- }
-
- panic("unexpected playlist type")
-}
diff --git a/internal/hls/mpegts/tracks.go b/internal/hls/mpegts/tracks.go
deleted file mode 100644
index cb3a0fc5..00000000
--- a/internal/hls/mpegts/tracks.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package mpegts
-
-import (
- "bytes"
- "context"
- "fmt"
-
- "github.com/aler9/gortsplib/v2/pkg/codecs/mpeg4audio"
- "github.com/aler9/gortsplib/v2/pkg/format"
- "github.com/asticode/go-astits"
-)
-
-func findMPEG4AudioConfig(dem *astits.Demuxer, pid uint16) (*mpeg4audio.Config, error) {
- for {
- data, err := dem.NextData()
- if err != nil {
- return nil, err
- }
-
- if data.PES == nil || data.PID != pid {
- continue
- }
-
- var adtsPkts mpeg4audio.ADTSPackets
- err = adtsPkts.Unmarshal(data.PES.Data)
- if err != nil {
- return nil, fmt.Errorf("unable to decode ADTS: %s", err)
- }
-
- pkt := adtsPkts[0]
- return &mpeg4audio.Config{
- Type: pkt.Type,
- SampleRate: pkt.SampleRate,
- ChannelCount: pkt.ChannelCount,
- }, nil
- }
-}
-
-// Track is a MPEG-TS track.
-type Track struct {
- ES *astits.PMTElementaryStream
- Format format.Format
-}
-
-// FindTracks finds the tracks in a MPEG-TS stream.
-func FindTracks(byts []byte) ([]*Track, error) {
- var tracks []*Track
- dem := astits.NewDemuxer(context.Background(), bytes.NewReader(byts))
-
- for {
- data, err := dem.NextData()
- if err != nil {
- return nil, err
- }
-
- if data.PMT != nil {
- for _, es := range data.PMT.ElementaryStreams {
- switch es.StreamType {
- case astits.StreamTypeH264Video,
- astits.StreamTypeAACAudio:
-
- case astits.StreamTypeMetadata:
- continue
-
- default:
- return nil, fmt.Errorf("track type %d not supported (yet)", es.StreamType)
- }
-
- tracks = append(tracks, &Track{
- ES: es,
- })
- }
- break
- }
- }
-
- if tracks == nil {
- return nil, fmt.Errorf("no tracks found")
- }
-
- for _, t := range tracks {
- switch t.ES.StreamType {
- case astits.StreamTypeH264Video:
- t.Format = &format.H264{
- PayloadTyp: 96,
- PacketizationMode: 1,
- }
-
- case astits.StreamTypeAACAudio:
- conf, err := findMPEG4AudioConfig(dem, t.ES.ElementaryPID)
- if err != nil {
- return nil, err
- }
-
- t.Format = &format.MPEG4Audio{
- PayloadTyp: 96,
- Config: conf,
- SizeLength: 13,
- IndexLength: 3,
- IndexDeltaLength: 3,
- }
- }
- }
-
- return tracks, nil
-}
diff --git a/internal/hls/mpegts/writer.go b/internal/hls/mpegts/writer.go
deleted file mode 100644
index 4f112612..00000000
--- a/internal/hls/mpegts/writer.go
+++ /dev/null
@@ -1,201 +0,0 @@
-// Package mpegts contains a MPEG-TS reader and writer.
-package mpegts
-
-import (
- "bytes"
- "context"
- "time"
-
- "github.com/aler9/gortsplib/v2/pkg/codecs/h264"
- "github.com/aler9/gortsplib/v2/pkg/codecs/mpeg4audio"
- "github.com/aler9/gortsplib/v2/pkg/format"
- "github.com/asticode/go-astits"
-)
-
-const (
- pcrOffset = 400 * time.Millisecond // 2 samples @ 5fps
-)
-
-type writerFunc func(p []byte) (int, error)
-
-func (f writerFunc) Write(p []byte) (int, error) {
- return f(p)
-}
-
-// Writer is a MPEG-TS writer.
-type Writer struct {
- videoFormat *format.H264
- audioFormat *format.MPEG4Audio
-
- buf *bytes.Buffer
- inner *astits.Muxer
- pcrCounter int
-}
-
-// NewWriter allocates a Writer.
-func NewWriter(
- videoFormat *format.H264,
- audioFormat *format.MPEG4Audio,
-) *Writer {
- w := &Writer{
- videoFormat: videoFormat,
- audioFormat: audioFormat,
- buf: bytes.NewBuffer(nil),
- }
-
- w.inner = astits.NewMuxer(
- context.Background(),
- writerFunc(func(p []byte) (int, error) {
- return w.buf.Write(p)
- }))
-
- if videoFormat != nil {
- w.inner.AddElementaryStream(astits.PMTElementaryStream{
- ElementaryPID: 256,
- StreamType: astits.StreamTypeH264Video,
- })
- }
-
- if audioFormat != nil {
- w.inner.AddElementaryStream(astits.PMTElementaryStream{
- ElementaryPID: 257,
- StreamType: astits.StreamTypeAACAudio,
- })
- }
-
- if videoFormat != nil {
- w.inner.SetPCRPID(256)
- } else {
- w.inner.SetPCRPID(257)
- }
-
- // WriteTable() is not necessary
- // since it's called automatically when WriteData() is called with
- // * PID == PCRPID
- // * AdaptationField != nil
- // * RandomAccessIndicator = true
-
- return w
-}
-
-// GenerateSegment generates a MPEG-TS segment.
-func (w *Writer) GenerateSegment() []byte {
- w.pcrCounter = 0
- ret := w.buf.Bytes()
- w.buf = bytes.NewBuffer(nil)
- return ret
-}
-
-// WriteH264 writes a H264 access unit.
-func (w *Writer) WriteH264(
- pcr time.Duration,
- dts time.Duration,
- pts time.Duration,
- idrPresent bool,
- nalus [][]byte,
-) error {
- // prepend an AUD. This is required by video.js and iOS
- nalus = append([][]byte{{byte(h264.NALUTypeAccessUnitDelimiter), 240}}, nalus...)
-
- enc, err := h264.AnnexBMarshal(nalus)
- if err != nil {
- return err
- }
-
- var af *astits.PacketAdaptationField
-
- if idrPresent {
- af = &astits.PacketAdaptationField{}
- af.RandomAccessIndicator = true
- }
-
- // send PCR once in a while
- if w.pcrCounter == 0 {
- if af == nil {
- af = &astits.PacketAdaptationField{}
- }
- af.HasPCR = true
- af.PCR = &astits.ClockReference{Base: int64(pcr.Seconds() * 90000)}
- w.pcrCounter = 3
- }
- w.pcrCounter--
-
- oh := &astits.PESOptionalHeader{
- MarkerBits: 2,
- }
-
- if dts == pts {
- oh.PTSDTSIndicator = astits.PTSDTSIndicatorOnlyPTS
- oh.PTS = &astits.ClockReference{Base: int64((pts + pcrOffset).Seconds() * 90000)}
- } else {
- oh.PTSDTSIndicator = astits.PTSDTSIndicatorBothPresent
- oh.DTS = &astits.ClockReference{Base: int64((dts + pcrOffset).Seconds() * 90000)}
- oh.PTS = &astits.ClockReference{Base: int64((pts + pcrOffset).Seconds() * 90000)}
- }
-
- _, err = w.inner.WriteData(&astits.MuxerData{
- PID: 256,
- AdaptationField: af,
- PES: &astits.PESData{
- Header: &astits.PESHeader{
- OptionalHeader: oh,
- StreamID: 224, // video
- },
- Data: enc,
- },
- })
- return err
-}
-
-// WriteAAC writes an AAC AU.
-func (w *Writer) WriteAAC(
- pcr time.Duration,
- pts time.Duration,
- au []byte,
-) error {
- pkts := mpeg4audio.ADTSPackets{
- {
- Type: w.audioFormat.Config.Type,
- SampleRate: w.audioFormat.Config.SampleRate,
- ChannelCount: w.audioFormat.Config.ChannelCount,
- AU: au,
- },
- }
-
- enc, err := pkts.Marshal()
- if err != nil {
- return err
- }
-
- af := &astits.PacketAdaptationField{
- RandomAccessIndicator: true,
- }
-
- if w.videoFormat == nil {
- // send PCR once in a while
- if w.pcrCounter == 0 {
- af.HasPCR = true
- af.PCR = &astits.ClockReference{Base: int64(pcr.Seconds() * 90000)}
- w.pcrCounter = 3
- }
- w.pcrCounter--
- }
-
- _, err = w.inner.WriteData(&astits.MuxerData{
- PID: 257,
- AdaptationField: af,
- PES: &astits.PESData{
- Header: &astits.PESHeader{
- OptionalHeader: &astits.PESOptionalHeader{
- MarkerBits: 2,
- PTSDTSIndicator: astits.PTSDTSIndicatorOnlyPTS,
- PTS: &astits.ClockReference{Base: int64((pts + pcrOffset).Seconds() * 90000)},
- },
- PacketLength: uint16(len(enc) + 8),
- StreamID: 192, // audio
- },
- Data: enc,
- },
- })
- return err
-}
diff --git a/internal/hls/mpegts/writer_test.go b/internal/hls/mpegts/writer_test.go
deleted file mode 100644
index 9e750b34..00000000
--- a/internal/hls/mpegts/writer_test.go
+++ /dev/null
@@ -1,371 +0,0 @@
-package mpegts
-
-import (
- "bytes"
- "context"
- "testing"
- "time"
-
- "github.com/aler9/gortsplib/v2/pkg/codecs/h264"
- "github.com/aler9/gortsplib/v2/pkg/codecs/mpeg4audio"
- "github.com/aler9/gortsplib/v2/pkg/format"
- "github.com/asticode/go-astits"
- "github.com/stretchr/testify/require"
-)
-
-func TestWriter(t *testing.T) {
- testSPS := []byte{
- 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
- 0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04,
- 0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9,
- 0x20,
- }
-
- testVideoTrack := &format.H264{
- PayloadTyp: 96,
- SPS: testSPS,
- PPS: []byte{0x08},
- PacketizationMode: 1,
- }
-
- testAudioTrack := &format.MPEG4Audio{
- PayloadTyp: 97,
- Config: &mpeg4audio.Config{
- Type: 2,
- SampleRate: 44100,
- ChannelCount: 2,
- },
- SizeLength: 13,
- IndexLength: 3,
- IndexDeltaLength: 3,
- }
-
- type videoSample struct {
- NALUs [][]byte
- PTS time.Duration
- DTS time.Duration
- }
-
- type audioSample struct {
- AU []byte
- PTS time.Duration
- }
-
- type sample interface{}
-
- testSamples := []sample{
- videoSample{
- NALUs: [][]byte{
- testSPS, // SPS
- {8}, // PPS
- {5}, // IDR
- },
- PTS: 2 * time.Second,
- DTS: 2 * time.Second,
- },
- audioSample{
- AU: []byte{
- 0x01, 0x02, 0x03, 0x04,
- },
- PTS: 3 * time.Second,
- },
- audioSample{
- AU: []byte{
- 0x01, 0x02, 0x03, 0x04,
- },
- PTS: 3500 * time.Millisecond,
- },
- videoSample{
- NALUs: [][]byte{
- {1}, // non-IDR
- },
- PTS: 4 * time.Second,
- DTS: 4 * time.Second,
- },
- audioSample{
- AU: []byte{
- 0x01, 0x02, 0x03, 0x04,
- },
- PTS: 4500 * time.Millisecond,
- },
- videoSample{
- NALUs: [][]byte{
- {1}, // non-IDR
- },
- PTS: 6 * time.Second,
- DTS: 6 * time.Second,
- },
- }
-
- t.Run("video + audio", func(t *testing.T) {
- w := NewWriter(testVideoTrack, testAudioTrack)
-
- for _, sample := range testSamples {
- switch tsample := sample.(type) {
- case videoSample:
- err := w.WriteH264(
- tsample.DTS-2*time.Second,
- tsample.DTS,
- tsample.PTS,
- h264.IDRPresent(tsample.NALUs),
- tsample.NALUs)
- require.NoError(t, err)
-
- case audioSample:
- err := w.WriteAAC(
- tsample.PTS-2*time.Second,
- tsample.PTS,
- tsample.AU)
- require.NoError(t, err)
- }
- }
-
- byts := w.GenerateSegment()
-
- dem := astits.NewDemuxer(context.Background(), bytes.NewReader(byts),
- astits.DemuxerOptPacketSize(188))
-
- // PMT
- pkt, err := dem.NextPacket()
- require.NoError(t, err)
- require.Equal(t, &astits.Packet{
- Header: &astits.PacketHeader{
- HasPayload: true,
- PayloadUnitStartIndicator: true,
- PID: 0,
- },
- Payload: append([]byte{
- 0x00, 0x00, 0xb0, 0x0d, 0x00, 0x00, 0xc1, 0x00,
- 0x00, 0x00, 0x01, 0xf0, 0x00, 0x71, 0x10, 0xd8,
- 0x78,
- }, bytes.Repeat([]byte{0xff}, 167)...),
- }, pkt)
-
- // PAT
- pkt, err = dem.NextPacket()
- require.NoError(t, err)
- require.Equal(t, &astits.Packet{
- Header: &astits.PacketHeader{
- HasPayload: true,
- PayloadUnitStartIndicator: true,
- PID: 4096,
- },
- Payload: append([]byte{
- 0x00, 0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00,
- 0x00, 0xe1, 0x00, 0xf0, 0x00, 0x1b, 0xe1, 0x00,
- 0xf0, 0x00, 0x0f, 0xe1, 0x01, 0xf0, 0x00, 0x2f,
- 0x44, 0xb9, 0x9b,
- }, bytes.Repeat([]byte{0xff}, 157)...),
- }, pkt)
-
- // PES (H264)
- pkt, err = dem.NextPacket()
- require.NoError(t, err)
- require.Equal(t, &astits.Packet{
- AdaptationField: &astits.PacketAdaptationField{
- Length: 124,
- StuffingLength: 117,
- HasPCR: true,
- PCR: &astits.ClockReference{},
- RandomAccessIndicator: true,
- },
- Header: &astits.PacketHeader{
- HasAdaptationField: true,
- HasPayload: true,
- PayloadUnitStartIndicator: true,
- PID: 256,
- },
- Payload: []byte{
- 0x00, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x80, 0x80,
- 0x05, 0x21, 0x00, 0x0d, 0x97, 0x81, 0x00, 0x00,
- 0x00, 0x01, 0x09, 0xf0, 0x00, 0x00, 0x00, 0x01,
- 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
- 0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04,
- 0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9,
- 0x20, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00,
- 0x00, 0x01, 0x05,
- },
- }, pkt)
-
- // PES (AAC)
- pkt, err = dem.NextPacket()
- require.NoError(t, err)
- require.Equal(t, &astits.Packet{
- AdaptationField: &astits.PacketAdaptationField{
- Length: 158,
- StuffingLength: 157,
- RandomAccessIndicator: true,
- },
- Header: &astits.PacketHeader{
- HasAdaptationField: true,
- HasPayload: true,
- PayloadUnitStartIndicator: true,
- PID: 257,
- },
- Payload: []byte{
- 0x00, 0x00, 0x01, 0xc0, 0x00, 0x13, 0x80, 0x80,
- 0x05, 0x21, 0x00, 0x13, 0x56, 0xa1, 0xff, 0xf1,
- 0x50, 0x80, 0x01, 0x7f, 0xfc, 0x01, 0x02, 0x03,
- 0x04,
- },
- }, pkt)
- })
-
- t.Run("video only", func(t *testing.T) {
- w := NewWriter(testVideoTrack, nil)
-
- for _, sample := range testSamples {
- if tsample, ok := sample.(videoSample); ok {
- err := w.WriteH264(
- tsample.DTS-2*time.Second,
- tsample.DTS,
- tsample.PTS,
- h264.IDRPresent(tsample.NALUs),
- tsample.NALUs)
- require.NoError(t, err)
- }
- }
-
- byts := w.GenerateSegment()
-
- dem := astits.NewDemuxer(context.Background(), bytes.NewReader(byts),
- astits.DemuxerOptPacketSize(188))
-
- // PMT
- pkt, err := dem.NextPacket()
- require.NoError(t, err)
- require.Equal(t, &astits.Packet{
- Header: &astits.PacketHeader{
- HasPayload: true,
- PayloadUnitStartIndicator: true,
- PID: 0,
- },
- Payload: append([]byte{
- 0x00, 0x00, 0xb0, 0x0d, 0x00, 0x00, 0xc1, 0x00,
- 0x00, 0x00, 0x01, 0xf0, 0x00, 0x71, 0x10, 0xd8,
- 0x78,
- }, bytes.Repeat([]byte{0xff}, 167)...),
- }, pkt)
-
- // PAT
- pkt, err = dem.NextPacket()
- require.NoError(t, err)
- require.Equal(t, &astits.Packet{
- Header: &astits.PacketHeader{
- HasPayload: true,
- PayloadUnitStartIndicator: true,
- PID: 4096,
- },
- Payload: append([]byte{
- 0x00, 0x02, 0xb0, 0x12, 0x00, 0x01, 0xc1, 0x00,
- 0x00, 0xe1, 0x00, 0xf0, 0x00, 0x1b, 0xe1, 0x00,
- 0xf0, 0x00, 0x15, 0xbd, 0x4d, 0x56,
- }, bytes.Repeat([]byte{0xff}, 162)...),
- }, pkt)
-
- // PES (H264)
- pkt, err = dem.NextPacket()
- require.NoError(t, err)
- require.Equal(t, &astits.Packet{
- AdaptationField: &astits.PacketAdaptationField{
- Length: 124,
- StuffingLength: 117,
- HasPCR: true,
- PCR: &astits.ClockReference{},
- RandomAccessIndicator: true,
- },
- Header: &astits.PacketHeader{
- HasAdaptationField: true,
- HasPayload: true,
- PayloadUnitStartIndicator: true,
- PID: 256,
- },
- Payload: []byte{
- 0x00, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x80, 0x80,
- 0x05, 0x21, 0x00, 0x0d, 0x97, 0x81, 0x00, 0x00,
- 0x00, 0x01, 0x09, 0xf0, 0x00, 0x00, 0x00, 0x01,
- 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
- 0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04,
- 0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9,
- 0x20, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00,
- 0x00, 0x01, 0x05,
- },
- }, pkt)
- })
-
- t.Run("audio only", func(t *testing.T) {
- w := NewWriter(nil, testAudioTrack)
-
- for _, sample := range testSamples {
- if tsample, ok := sample.(audioSample); ok {
- err := w.WriteAAC(
- tsample.PTS-2*time.Second,
- tsample.PTS,
- tsample.AU)
- require.NoError(t, err)
- }
- }
-
- byts := w.GenerateSegment()
-
- dem := astits.NewDemuxer(context.Background(), bytes.NewReader(byts),
- astits.DemuxerOptPacketSize(188))
-
- // PMT
- pkt, err := dem.NextPacket()
- require.NoError(t, err)
- require.Equal(t, &astits.Packet{
- Header: &astits.PacketHeader{
- HasPayload: true,
- PayloadUnitStartIndicator: true,
- PID: 0,
- },
- Payload: append([]byte{
- 0x00, 0x00, 0xb0, 0x0d, 0x00, 0x00, 0xc1, 0x00,
- 0x00, 0x00, 0x01, 0xf0, 0x00, 0x71, 0x10, 0xd8,
- 0x78,
- }, bytes.Repeat([]byte{0xff}, 167)...),
- }, pkt)
-
- // PAT
- pkt, err = dem.NextPacket()
- require.NoError(t, err)
- require.Equal(t, &astits.Packet{
- Header: &astits.PacketHeader{
- HasPayload: true,
- PayloadUnitStartIndicator: true,
- PID: 4096,
- },
- Payload: append([]byte{
- 0x00, 0x02, 0xb0, 0x12, 0x00, 0x01, 0xc1, 0x00,
- 0x00, 0xe1, 0x01, 0xf0, 0x00, 0x0f, 0xe1, 0x01,
- 0xf0, 0x00, 0xec, 0xe2, 0xb0, 0x94,
- }, bytes.Repeat([]byte{0xff}, 162)...),
- }, pkt)
-
- // PES (AAC)
- pkt, err = dem.NextPacket()
- require.NoError(t, err)
- require.Equal(t, &astits.Packet{
- AdaptationField: &astits.PacketAdaptationField{
- Length: 158,
- StuffingLength: 151,
- RandomAccessIndicator: true,
- HasPCR: true,
- PCR: &astits.ClockReference{Base: 90000},
- },
- Header: &astits.PacketHeader{
- HasAdaptationField: true,
- HasPayload: true,
- PayloadUnitStartIndicator: true,
- PID: 257,
- },
- Payload: []byte{
- 0x00, 0x00, 0x01, 0xc0, 0x00, 0x13, 0x80, 0x80,
- 0x05, 0x21, 0x00, 0x13, 0x56, 0xa1, 0xff, 0xf1,
- 0x50, 0x80, 0x01, 0x7f, 0xfc, 0x01, 0x02, 0x03,
- 0x04,
- },
- }, pkt)
- })
-}
diff --git a/internal/hls/mpegtstimedec/decoder.go b/internal/hls/mpegtstimedec/decoder.go
deleted file mode 100644
index 6edbbd6d..00000000
--- a/internal/hls/mpegtstimedec/decoder.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// Package mpegtstimedec contains a MPEG-TS timestamp decoder.
-package mpegtstimedec
-
-import (
- "time"
-)
-
-const (
- maximum = 0x1FFFFFFFF // 33 bits
- negativeThreshold = 0x1FFFFFFFF / 2
- clockRate = 90000
-)
-
-// Decoder is a MPEG-TS timestamp decoder.
-type Decoder struct {
- overall time.Duration
- prev int64
-}
-
-// New allocates a Decoder.
-func New(start int64) *Decoder {
- return &Decoder{
- prev: start,
- }
-}
-
-// Decode decodes a MPEG-TS timestamp.
-func (d *Decoder) Decode(ts int64) time.Duration {
- diff := (ts - d.prev) & maximum
-
- // negative difference
- if diff > negativeThreshold {
- diff = (d.prev - ts) & maximum
- d.prev = ts
- d.overall -= time.Duration(diff)
- } else {
- d.prev = ts
- d.overall += time.Duration(diff)
- }
-
- // avoid an int64 overflow and preserve resolution by splitting division into two parts:
- // first add the integer part, then the decimal part.
- secs := d.overall / clockRate
- dec := d.overall % clockRate
- return secs*time.Second + dec*time.Second/clockRate
-}
diff --git a/internal/hls/mpegtstimedec/decoder_test.go b/internal/hls/mpegtstimedec/decoder_test.go
deleted file mode 100644
index 5e3c4444..00000000
--- a/internal/hls/mpegtstimedec/decoder_test.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package mpegtstimedec
-
-import (
- "testing"
- "time"
-
- "github.com/stretchr/testify/require"
-)
-
-func TestNegativeDiff(t *testing.T) {
- d := New(64523434)
-
- ts := d.Decode(64523434 - 90000)
- require.Equal(t, -1*time.Second, ts)
-
- ts = d.Decode(64523434)
- require.Equal(t, time.Duration(0), ts)
-
- ts = d.Decode(64523434 + 90000*2)
- require.Equal(t, 2*time.Second, ts)
-
- ts = d.Decode(64523434 + 90000)
- require.Equal(t, 1*time.Second, ts)
-}
-
-func TestOverflow(t *testing.T) {
- d := New(0x1FFFFFFFF - 20)
-
- i := int64(0x1FFFFFFFF - 20)
- secs := time.Duration(0)
- const stride = 150
- lim := int64(uint64(0x1FFFFFFFF - (stride * 90000)))
-
- for n := 0; n < 100; n++ {
- // overflow
- i += 90000 * stride
- secs += stride
- ts := d.Decode(i)
- require.Equal(t, secs*time.Second, ts)
-
- // reach 2^32 slowly
- secs += stride
- i += 90000 * stride
- for ; i < lim; i += 90000 * stride {
- ts = d.Decode(i)
- require.Equal(t, secs*time.Second, ts)
- secs += stride
- }
- }
-}
-
-func TestOverflowAndBack(t *testing.T) {
- d := New(0x1FFFFFFFF - 90000 + 1)
-
- ts := d.Decode(0x1FFFFFFFF - 90000 + 1)
- require.Equal(t, time.Duration(0), ts)
-
- ts = d.Decode(90000)
- require.Equal(t, 2*time.Second, ts)
-
- ts = d.Decode(0x1FFFFFFFF - 90000 + 1)
- require.Equal(t, time.Duration(0), ts)
-
- ts = d.Decode(0x1FFFFFFFF - 90000*2 + 1)
- require.Equal(t, -1*time.Second, ts)
-
- ts = d.Decode(0x1FFFFFFFF - 90000 + 1)
- require.Equal(t, time.Duration(0), ts)
-
- ts = d.Decode(90000)
- require.Equal(t, 2*time.Second, ts)
-}
diff --git a/internal/hls/muxer.go b/internal/hls/muxer.go
deleted file mode 100644
index 771c01b2..00000000
--- a/internal/hls/muxer.go
+++ /dev/null
@@ -1,100 +0,0 @@
-// Package hls contains a HLS muxer and client.
-package hls
-
-import (
- "io"
- "time"
-
- "github.com/aler9/gortsplib/v2/pkg/format"
-)
-
-// MuxerFileResponse is a response of the Muxer's File() func.
-type MuxerFileResponse struct {
- Status int
- Header map[string]string
- Body io.Reader
-}
-
-// Muxer is a HLS muxer.
-type Muxer struct {
- primaryPlaylist *muxerPrimaryPlaylist
- variant muxerVariant
-}
-
-// NewMuxer allocates a Muxer.
-func NewMuxer(
- variant MuxerVariant,
- segmentCount int,
- segmentDuration time.Duration,
- partDuration time.Duration,
- segmentMaxSize uint64,
- videoTrack format.Format,
- audioTrack format.Format,
-) (*Muxer, error) {
- m := &Muxer{}
-
- switch variant {
- case MuxerVariantMPEGTS:
- var err error
- m.variant, err = newMuxerVariantMPEGTS(
- segmentCount,
- segmentDuration,
- segmentMaxSize,
- videoTrack,
- audioTrack,
- )
- if err != nil {
- return nil, err
- }
-
- case MuxerVariantFMP4:
- m.variant = newMuxerVariantFMP4(
- false,
- segmentCount,
- segmentDuration,
- partDuration,
- segmentMaxSize,
- videoTrack,
- audioTrack,
- )
-
- default: // MuxerVariantLowLatency
- m.variant = newMuxerVariantFMP4(
- true,
- segmentCount,
- segmentDuration,
- partDuration,
- segmentMaxSize,
- videoTrack,
- audioTrack,
- )
- }
-
- m.primaryPlaylist = newMuxerPrimaryPlaylist(variant != MuxerVariantMPEGTS, videoTrack, audioTrack)
-
- return m, nil
-}
-
-// Close closes a Muxer.
-func (m *Muxer) Close() {
- m.variant.close()
-}
-
-// WriteH26x writes an H264 or an H265 access unit.
-func (m *Muxer) WriteH26x(ntp time.Time, pts time.Duration, au [][]byte) error {
- return m.variant.writeH26x(ntp, pts, au)
-}
-
-// WriteAudio writes an audio access unit.
-func (m *Muxer) WriteAudio(ntp time.Time, pts time.Duration, au []byte) error {
- return m.variant.writeAudio(ntp, pts, au)
-}
-
-// File returns a file reader.
-func (m *Muxer) File(name string, msn string, part string, skip string) *MuxerFileResponse {
- if name == "index.m3u8" {
- return m.primaryPlaylist.file()
- }
-
- return m.variant.file(name, msn, part, skip)
-}
diff --git a/internal/hls/muxer_primary_playlist.go b/internal/hls/muxer_primary_playlist.go
deleted file mode 100644
index 5724abb1..00000000
--- a/internal/hls/muxer_primary_playlist.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package hls
-
-import (
- "bytes"
- "io"
- "net/http"
- "strconv"
- "strings"
-
- "github.com/aler9/gortsplib/v2/pkg/format"
-)
-
-type muxerPrimaryPlaylist struct {
- fmp4 bool
- videoTrack format.Format
- audioTrack format.Format
-}
-
-func newMuxerPrimaryPlaylist(
- fmp4 bool,
- videoTrack format.Format,
- audioTrack format.Format,
-) *muxerPrimaryPlaylist {
- return &muxerPrimaryPlaylist{
- fmp4: fmp4,
- videoTrack: videoTrack,
- audioTrack: audioTrack,
- }
-}
-
-func (p *muxerPrimaryPlaylist) file() *MuxerFileResponse {
- return &MuxerFileResponse{
- Status: http.StatusOK,
- Header: map[string]string{
- "Content-Type": `application/x-mpegURL`,
- },
- Body: func() io.Reader {
- var codecs []string
-
- if p.videoTrack != nil {
- codecs = append(codecs, codecParametersGenerate(p.videoTrack))
- }
- if p.audioTrack != nil {
- codecs = append(codecs, codecParametersGenerate(p.audioTrack))
- }
-
- var version int
- if !p.fmp4 {
- version = 3
- } else {
- version = 9
- }
-
- return bytes.NewReader([]byte("#EXTM3U\n" +
- "#EXT-X-VERSION:" + strconv.FormatInt(int64(version), 10) + "\n" +
- "#EXT-X-INDEPENDENT-SEGMENTS\n" +
- "\n" +
- "#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"" + strings.Join(codecs, ",") + "\"\n" +
- "stream.m3u8\n"))
- }(),
- }
-}
diff --git a/internal/hls/muxer_test.go b/internal/hls/muxer_test.go
deleted file mode 100644
index 61b36356..00000000
--- a/internal/hls/muxer_test.go
+++ /dev/null
@@ -1,595 +0,0 @@
-package hls
-
-import (
- "io"
- "net/http"
- "regexp"
- "testing"
- "time"
-
- "github.com/aler9/gortsplib/v2/pkg/codecs/mpeg4audio"
- "github.com/aler9/gortsplib/v2/pkg/format"
- "github.com/stretchr/testify/require"
-)
-
-var testTime = time.Date(2010, 0o1, 0o1, 0o1, 0o1, 0o1, 0, time.UTC)
-
-// baseline profile without POC
-var testSPS = []byte{
- 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
- 0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04,
- 0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9,
- 0x20,
-}
-
-func TestMuxerVideoAudio(t *testing.T) {
- videoTrack := &format.H264{
- PayloadTyp: 96,
- SPS: testSPS,
- PPS: []byte{0x08},
- PacketizationMode: 1,
- }
-
- audioTrack := &format.MPEG4Audio{
- PayloadTyp: 97,
- Config: &mpeg4audio.Config{
- Type: 2,
- SampleRate: 44100,
- ChannelCount: 2,
- },
- SizeLength: 13,
- IndexLength: 3,
- IndexDeltaLength: 3,
- }
-
- for _, ca := range []string{
- "mpegts",
- "fmp4",
- "lowLatency",
- } {
- t.Run(ca, func(t *testing.T) {
- var v MuxerVariant
- switch ca {
- case "mpegts":
- v = MuxerVariantMPEGTS
-
- case "fmp4":
- v = MuxerVariantFMP4
-
- case "lowLatency":
- v = MuxerVariantLowLatency
- }
-
- m, err := NewMuxer(v, 3, 1*time.Second, 0, 50*1024*1024, videoTrack, audioTrack)
- require.NoError(t, err)
- defer m.Close()
-
- // access unit without IDR
- d := 1 * time.Second
- err = m.WriteH26x(testTime.Add(d-1*time.Second), d, [][]byte{
- {0x06},
- {0x07},
- })
- require.NoError(t, err)
-
- // access unit with IDR
- d = 2 * time.Second
- err = m.WriteH26x(testTime.Add(d-1*time.Second), d, [][]byte{
- testSPS, // SPS
- {8}, // PPS
- {5}, // IDR
- })
- require.NoError(t, err)
-
- d = 3 * time.Second
- err = m.WriteAudio(testTime.Add(d-1*time.Second), d, []byte{
- 0x01, 0x02, 0x03, 0x04,
- })
- require.NoError(t, err)
-
- d = 3500 * time.Millisecond
- err = m.WriteAudio(testTime.Add(d-1*time.Second), d, []byte{
- 0x01, 0x02, 0x03, 0x04,
- })
- require.NoError(t, err)
-
- // access unit without IDR
- d = 4 * time.Second
- err = m.WriteH26x(testTime.Add(d-1*time.Second), d, [][]byte{
- {1}, // non-IDR
- })
- require.NoError(t, err)
-
- d = 4500 * time.Millisecond
- err = m.WriteAudio(testTime.Add(d-1*time.Second), d, []byte{
- 0x01, 0x02, 0x03, 0x04,
- })
- require.NoError(t, err)
-
- // access unit with IDR
- d = 6 * time.Second
- err = m.WriteH26x(testTime.Add(d-1*time.Second), d, [][]byte{
- {5}, // IDR
- })
- require.NoError(t, err)
-
- // access unit with IDR
- d = 7 * time.Second
- err = m.WriteH26x(testTime.Add(d-1*time.Second), d, [][]byte{
- {5}, // IDR
- })
- require.NoError(t, err)
-
- byts, err := io.ReadAll(m.File("index.m3u8", "", "", "").Body)
- require.NoError(t, err)
-
- switch ca {
- case "mpegts":
- require.Equal(t, "#EXTM3U\n"+
- "#EXT-X-VERSION:3\n"+
- "#EXT-X-INDEPENDENT-SEGMENTS\n"+
- "\n"+
- "#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"avc1.42c028,mp4a.40.2\"\n"+
- "stream.m3u8\n", string(byts))
-
- case "fmp4", "lowLatency":
- require.Equal(t, "#EXTM3U\n"+
- "#EXT-X-VERSION:9\n"+
- "#EXT-X-INDEPENDENT-SEGMENTS\n"+
- "\n"+
- "#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"avc1.42c028,mp4a.40.2\"\n"+
- "stream.m3u8\n", string(byts))
- }
-
- byts, err = io.ReadAll(m.File("stream.m3u8", "", "", "").Body)
- require.NoError(t, err)
-
- switch ca {
- case "mpegts":
- re := regexp.MustCompile(`^#EXTM3U\n` +
- `#EXT-X-VERSION:3\n` +
- `#EXT-X-ALLOW-CACHE:NO\n` +
- `#EXT-X-TARGETDURATION:4\n` +
- `#EXT-X-MEDIA-SEQUENCE:0\n` +
- `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` +
- `#EXTINF:4,\n` +
- `(seg0\.ts)\n` +
- `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` +
- `#EXTINF:1,\n` +
- `(seg1\.ts)\n$`)
- ma := re.FindStringSubmatch(string(byts))
- require.NotEqual(t, 0, len(ma))
-
- seg := m.File(ma[2], "", "", "")
- require.Equal(t, http.StatusOK, seg.Status)
- _, err := io.ReadAll(seg.Body)
- require.NoError(t, err)
-
- case "fmp4":
- re := regexp.MustCompile(`^#EXTM3U\n` +
- `#EXT-X-VERSION:9\n` +
- `#EXT-X-TARGETDURATION:4\n` +
- `#EXT-X-MEDIA-SEQUENCE:0\n` +
- `#EXT-X-MAP:URI="init.mp4"\n` +
- `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` +
- `#EXTINF:4.00000,\n` +
- `(seg0\.mp4)\n` +
- `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` +
- `#EXTINF:1.00000,\n` +
- `(seg1\.mp4)\n$`)
- ma := re.FindStringSubmatch(string(byts))
- require.NotEqual(t, 0, len(ma))
-
- init := m.File("init.mp4", "", "", "")
- require.Equal(t, http.StatusOK, init.Status)
- _, err := io.ReadAll(init.Body)
- require.NoError(t, err)
-
- seg := m.File(ma[2], "", "", "")
- require.Equal(t, http.StatusOK, seg.Status)
- _, err = io.ReadAll(seg.Body)
- require.NoError(t, err)
-
- case "lowLatency":
- require.Equal(t,
- "#EXTM3U\n"+
- "#EXT-X-VERSION:9\n"+
- "#EXT-X-TARGETDURATION:4\n"+
- "#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=5.00000,CAN-SKIP-UNTIL=24\n"+
- "#EXT-X-PART-INF:PART-TARGET=2\n"+
- "#EXT-X-MEDIA-SEQUENCE:2\n"+
- "#EXT-X-MAP:URI=\"init.mp4\"\n"+
- "#EXT-X-GAP\n"+
- "#EXTINF:4.00000,\n"+
- "gap.mp4\n"+
- "#EXT-X-GAP\n"+
- "#EXTINF:4.00000,\n"+
- "gap.mp4\n"+
- "#EXT-X-GAP\n"+
- "#EXTINF:4.00000,\n"+
- "gap.mp4\n"+
- "#EXT-X-GAP\n"+
- "#EXTINF:4.00000,\n"+
- "gap.mp4\n"+
- "#EXT-X-GAP\n"+
- "#EXTINF:4.00000,\n"+
- "gap.mp4\n"+
- "#EXT-X-PROGRAM-DATE-TIME:2010-01-01T01:01:02Z\n"+
- "#EXT-X-PART:DURATION=2.00000,URI=\"part0.mp4\",INDEPENDENT=YES\n"+
- "#EXT-X-PART:DURATION=2.00000,URI=\"part1.mp4\"\n"+
- "#EXTINF:4.00000,\n"+
- "seg7.mp4\n"+
- "#EXT-X-PROGRAM-DATE-TIME:2010-01-01T01:01:06Z\n"+
- "#EXT-X-PART:DURATION=1.00000,URI=\"part3.mp4\",INDEPENDENT=YES\n"+
- "#EXTINF:1.00000,\n"+
- "seg8.mp4\n"+
- "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"part4.mp4\"\n", string(byts))
-
- part := m.File("part3.mp4", "", "", "")
- require.Equal(t, http.StatusOK, part.Status)
- _, err = io.ReadAll(part.Body)
- require.NoError(t, err)
-
- recv := make(chan struct{})
-
- go func() {
- part = m.File("part4.mp4", "", "", "")
- _, err := io.ReadAll(part.Body)
- require.NoError(t, err)
- close(recv)
- }()
-
- d = 9 * time.Second
- err = m.WriteH26x(testTime.Add(d-1*time.Second), d, [][]byte{
- {1}, // non-IDR
- })
- require.NoError(t, err)
-
- <-recv
- }
- })
- }
-}
-
-func TestMuxerVideoOnly(t *testing.T) {
- videoTrack := &format.H264{
- PayloadTyp: 96,
- SPS: testSPS,
- PPS: []byte{0x08},
- PacketizationMode: 1,
- }
-
- for _, ca := range []string{
- "mpegts",
- "fmp4",
- } {
- t.Run(ca, func(t *testing.T) {
- var v MuxerVariant
- if ca == "mpegts" {
- v = MuxerVariantMPEGTS
- } else {
- v = MuxerVariantFMP4
- }
-
- m, err := NewMuxer(v, 3, 1*time.Second, 0, 50*1024*1024, videoTrack, nil)
- require.NoError(t, err)
- defer m.Close()
-
- // access unit with IDR
- d := 2 * time.Second
- err = m.WriteH26x(testTime.Add(d-2*time.Second), d, [][]byte{
- testSPS, // SPS
- {8}, // PPS
- {5}, // IDR
- })
- require.NoError(t, err)
-
- // access unit with IDR
- d = 6 * time.Second
- err = m.WriteH26x(testTime.Add(d-2*time.Second), d, [][]byte{
- {5}, // IDR
- })
- require.NoError(t, err)
-
- // access unit with IDR
- d = 7 * time.Second
- err = m.WriteH26x(testTime.Add(d-2*time.Second), d, [][]byte{
- {5}, // IDR
- })
- require.NoError(t, err)
-
- byts, err := io.ReadAll(m.File("index.m3u8", "", "", "").Body)
- require.NoError(t, err)
-
- if ca == "mpegts" {
- require.Equal(t, "#EXTM3U\n"+
- "#EXT-X-VERSION:3\n"+
- "#EXT-X-INDEPENDENT-SEGMENTS\n"+
- "\n"+
- "#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"avc1.42c028\"\n"+
- "stream.m3u8\n", string(byts))
- } else {
- require.Equal(t, "#EXTM3U\n"+
- "#EXT-X-VERSION:9\n"+
- "#EXT-X-INDEPENDENT-SEGMENTS\n"+
- "\n"+
- "#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"avc1.42c028\"\n"+
- "stream.m3u8\n", string(byts))
- }
-
- byts, err = io.ReadAll(m.File("stream.m3u8", "", "", "").Body)
- require.NoError(t, err)
-
- var ma []string
- if ca == "mpegts" {
- re := regexp.MustCompile(`^#EXTM3U\n` +
- `#EXT-X-VERSION:3\n` +
- `#EXT-X-ALLOW-CACHE:NO\n` +
- `#EXT-X-TARGETDURATION:4\n` +
- `#EXT-X-MEDIA-SEQUENCE:0\n` +
- `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` +
- `#EXTINF:4,\n` +
- `(seg0\.ts)\n` +
- `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` +
- `#EXTINF:1,\n` +
- `(seg1\.ts)\n$`)
- ma = re.FindStringSubmatch(string(byts))
- } else {
- re := regexp.MustCompile(`^#EXTM3U\n` +
- `#EXT-X-VERSION:9\n` +
- `#EXT-X-TARGETDURATION:4\n` +
- `#EXT-X-MEDIA-SEQUENCE:0\n` +
- `#EXT-X-MAP:URI="init.mp4"\n` +
- `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` +
- `#EXTINF:4.00000,\n` +
- `(seg0\.mp4)\n` +
- `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` +
- `#EXTINF:1.00000,\n` +
- `(seg1\.mp4)\n$`)
- ma = re.FindStringSubmatch(string(byts))
- }
- require.NotEqual(t, 0, len(ma))
-
- if ca == "mpegts" {
- _, err := io.ReadAll(m.File(ma[2], "", "", "").Body)
- require.NoError(t, err)
- } else {
- _, err := io.ReadAll(m.File("init.mp4", "", "", "").Body)
- require.NoError(t, err)
-
- _, err = io.ReadAll(m.File(ma[2], "", "", "").Body)
- require.NoError(t, err)
- }
- })
- }
-}
-
-func TestMuxerAudioOnly(t *testing.T) {
- audioTrack := &format.MPEG4Audio{
- PayloadTyp: 97,
- Config: &mpeg4audio.Config{
- Type: 2,
- SampleRate: 44100,
- ChannelCount: 2,
- },
- SizeLength: 13,
- IndexLength: 3,
- IndexDeltaLength: 3,
- }
-
- for _, ca := range []string{
- "mpegts",
- "fmp4",
- } {
- t.Run(ca, func(t *testing.T) {
- var v MuxerVariant
- if ca == "mpegts" {
- v = MuxerVariantMPEGTS
- } else {
- v = MuxerVariantFMP4
- }
-
- m, err := NewMuxer(v, 3, 1*time.Second, 0, 50*1024*1024, nil, audioTrack)
- require.NoError(t, err)
- defer m.Close()
-
- for i := 0; i < 100; i++ {
- d := 1 * time.Second
- err = m.WriteAudio(testTime.Add(d-1*time.Second), d, []byte{
- 0x01, 0x02, 0x03, 0x04,
- })
- require.NoError(t, err)
- }
-
- d := 2 * time.Second
- err = m.WriteAudio(testTime.Add(d-1*time.Second), d, []byte{
- 0x01, 0x02, 0x03, 0x04,
- })
- require.NoError(t, err)
-
- d = 3 * time.Second
- err = m.WriteAudio(testTime.Add(d-1*time.Second), d, []byte{
- 0x01, 0x02, 0x03, 0x04,
- })
- require.NoError(t, err)
-
- byts, err := io.ReadAll(m.File("index.m3u8", "", "", "").Body)
- require.NoError(t, err)
-
- if ca == "mpegts" {
- require.Equal(t, "#EXTM3U\n"+
- "#EXT-X-VERSION:3\n"+
- "#EXT-X-INDEPENDENT-SEGMENTS\n"+
- "\n"+
- "#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"mp4a.40.2\"\n"+
- "stream.m3u8\n", string(byts))
- } else {
- require.Equal(t, "#EXTM3U\n"+
- "#EXT-X-VERSION:9\n"+
- "#EXT-X-INDEPENDENT-SEGMENTS\n"+
- "\n"+
- "#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"mp4a.40.2\"\n"+
- "stream.m3u8\n", string(byts))
- }
-
- byts, err = io.ReadAll(m.File("stream.m3u8", "", "", "").Body)
- require.NoError(t, err)
-
- var ma []string
- if ca == "mpegts" {
- re := regexp.MustCompile(`^#EXTM3U\n` +
- `#EXT-X-VERSION:3\n` +
- `#EXT-X-ALLOW-CACHE:NO\n` +
- `#EXT-X-TARGETDURATION:1\n` +
- `#EXT-X-MEDIA-SEQUENCE:0\n` +
- `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` +
- `#EXTINF:1,\n` +
- `(seg0\.ts)\n$`)
- ma = re.FindStringSubmatch(string(byts))
- } else {
- re := regexp.MustCompile(`^#EXTM3U\n` +
- `#EXT-X-VERSION:9\n` +
- `#EXT-X-TARGETDURATION:2\n` +
- `#EXT-X-MEDIA-SEQUENCE:0\n` +
- `#EXT-X-MAP:URI="init.mp4"\n` +
- `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` +
- `#EXTINF:2.32200,\n` +
- `(seg0\.mp4)\n` +
- `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` +
- `#EXTINF:0.02322,\n` +
- `(seg1\.mp4)\n$`)
- ma = re.FindStringSubmatch(string(byts))
- }
- require.NotEqual(t, 0, len(ma))
-
- if ca == "mpegts" {
- _, err := io.ReadAll(m.File(ma[2], "", "", "").Body)
- require.NoError(t, err)
- } else {
- _, err := io.ReadAll(m.File("init.mp4", "", "", "").Body)
- require.NoError(t, err)
-
- _, err = io.ReadAll(m.File(ma[2], "", "", "").Body)
- require.NoError(t, err)
- }
- })
- }
-}
-
-func TestMuxerCloseBeforeFirstSegmentReader(t *testing.T) {
- videoTrack := &format.H264{
- PayloadTyp: 96,
- SPS: testSPS,
- PPS: []byte{0x08},
- PacketizationMode: 1,
- }
-
- m, err := NewMuxer(MuxerVariantMPEGTS, 3, 1*time.Second, 0, 50*1024*1024, videoTrack, nil)
- require.NoError(t, err)
-
- // access unit with IDR
- err = m.WriteH26x(testTime, 2*time.Second, [][]byte{
- testSPS, // SPS
- {8}, // PPS
- {5}, // IDR
- })
- require.NoError(t, err)
-
- m.Close()
-
- b := m.File("stream.m3u8", "", "", "").Body
- require.Equal(t, nil, b)
-}
-
-func TestMuxerMaxSegmentSize(t *testing.T) {
- videoTrack := &format.H264{
- PayloadTyp: 96,
- SPS: testSPS,
- PPS: []byte{0x08},
- PacketizationMode: 1,
- }
-
- m, err := NewMuxer(MuxerVariantMPEGTS, 3, 1*time.Second, 0, 0, videoTrack, nil)
- require.NoError(t, err)
- defer m.Close()
-
- err = m.WriteH26x(testTime, 2*time.Second, [][]byte{
- testSPS,
- {5}, // IDR
- })
- require.EqualError(t, err, "reached maximum segment size")
-}
-
-func TestMuxerDoubleRead(t *testing.T) {
- videoTrack := &format.H264{
- PayloadTyp: 96,
- SPS: testSPS,
- PPS: []byte{0x08},
- PacketizationMode: 1,
- }
-
- m, err := NewMuxer(MuxerVariantMPEGTS, 3, 1*time.Second, 0, 50*1024*1024, videoTrack, nil)
- require.NoError(t, err)
- defer m.Close()
-
- err = m.WriteH26x(testTime, 0, [][]byte{
- testSPS,
- {5}, // IDR
- {1},
- })
- require.NoError(t, err)
-
- err = m.WriteH26x(testTime, 2*time.Second, [][]byte{
- {5}, // IDR
- {2},
- })
- require.NoError(t, err)
-
- byts, err := io.ReadAll(m.File("stream.m3u8", "", "", "").Body)
- require.NoError(t, err)
-
- re := regexp.MustCompile(`^#EXTM3U\n` +
- `#EXT-X-VERSION:3\n` +
- `#EXT-X-ALLOW-CACHE:NO\n` +
- `#EXT-X-TARGETDURATION:2\n` +
- `#EXT-X-MEDIA-SEQUENCE:0\n` +
- `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` +
- `#EXTINF:2,\n` +
- `(seg0\.ts)\n$`)
- ma := re.FindStringSubmatch(string(byts))
- require.NotEqual(t, 0, len(ma))
-
- byts1, err := io.ReadAll(m.File(ma[2], "", "", "").Body)
- require.NoError(t, err)
-
- byts2, err := io.ReadAll(m.File(ma[2], "", "", "").Body)
- require.NoError(t, err)
- require.Equal(t, byts1, byts2)
-}
-
-func TestMuxerFMP4ZeroDuration(t *testing.T) {
- videoTrack := &format.H264{
- PayloadTyp: 96,
- SPS: testSPS,
- PPS: []byte{0x08},
- PacketizationMode: 1,
- }
-
- m, err := NewMuxer(MuxerVariantLowLatency, 3, 1*time.Second, 0, 50*1024*1024, videoTrack, nil)
- require.NoError(t, err)
- defer m.Close()
-
- err = m.WriteH26x(time.Now(), 0, [][]byte{
- testSPS, // SPS
- {8}, // PPS
- {5}, // IDR
- })
- require.NoError(t, err)
-
- err = m.WriteH26x(time.Now(), 1*time.Nanosecond, [][]byte{
- testSPS, // SPS
- {8}, // PPS
- {5}, // IDR
- })
- require.NoError(t, err)
-}
diff --git a/internal/hls/muxer_variant.go b/internal/hls/muxer_variant.go
deleted file mode 100644
index a7881f75..00000000
--- a/internal/hls/muxer_variant.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package hls
-
-import (
- "time"
-)
-
-// MuxerVariant is a muxer variant.
-type MuxerVariant int
-
-// supported variants.
-const (
- MuxerVariantMPEGTS MuxerVariant = iota
- MuxerVariantFMP4
- MuxerVariantLowLatency
-)
-
-type muxerVariant interface {
- close()
- writeH26x(time.Time, time.Duration, [][]byte) error
- writeAudio(time.Time, time.Duration, []byte) error
- file(name string, msn string, part string, skip string) *MuxerFileResponse
-}
diff --git a/internal/hls/muxer_variant_fmp4.go b/internal/hls/muxer_variant_fmp4.go
deleted file mode 100644
index b0bf17c4..00000000
--- a/internal/hls/muxer_variant_fmp4.go
+++ /dev/null
@@ -1,163 +0,0 @@
-package hls
-
-import (
- "bytes"
- "net/http"
- "sync"
- "time"
-
- "github.com/aler9/gortsplib/v2/pkg/format"
-
- "github.com/aler9/rtsp-simple-server/internal/hls/fmp4"
-)
-
-func extractVideoParams(track format.Format) [][]byte {
- switch ttrack := track.(type) {
- case *format.H264:
- params := make([][]byte, 2)
- params[0] = ttrack.SafeSPS()
- params[1] = ttrack.SafePPS()
- return params
-
- case *format.H265:
- params := make([][]byte, 3)
- params[0] = ttrack.SafeVPS()
- params[1] = ttrack.SafeSPS()
- params[2] = ttrack.SafePPS()
- return params
-
- default:
- return nil
- }
-}
-
-func videoParamsEqual(p1 [][]byte, p2 [][]byte) bool {
- if len(p1) != len(p2) {
- return true
- }
-
- for i, p := range p1 {
- if !bytes.Equal(p2[i], p) {
- return false
- }
- }
- return true
-}
-
-type muxerVariantFMP4 struct {
- playlist *muxerVariantFMP4Playlist
- segmenter *muxerVariantFMP4Segmenter
- videoTrack format.Format
- audioTrack format.Format
-
- mutex sync.Mutex
- lastVideoParams [][]byte
- initContent []byte
-}
-
-func newMuxerVariantFMP4(
- lowLatency bool,
- segmentCount int,
- segmentDuration time.Duration,
- partDuration time.Duration,
- segmentMaxSize uint64,
- videoTrack format.Format,
- audioTrack format.Format,
-) *muxerVariantFMP4 {
- v := &muxerVariantFMP4{
- videoTrack: videoTrack,
- audioTrack: audioTrack,
- }
-
- v.playlist = newMuxerVariantFMP4Playlist(
- lowLatency,
- segmentCount,
- videoTrack,
- audioTrack,
- )
-
- v.segmenter = newMuxerVariantFMP4Segmenter(
- lowLatency,
- segmentCount,
- segmentDuration,
- partDuration,
- segmentMaxSize,
- videoTrack,
- audioTrack,
- v.playlist.onSegmentFinalized,
- v.playlist.onPartFinalized,
- )
-
- return v
-}
-
-func (v *muxerVariantFMP4) close() {
- v.playlist.close()
-}
-
-func (v *muxerVariantFMP4) writeH26x(ntp time.Time, pts time.Duration, au [][]byte) error {
- return v.segmenter.writeH26x(ntp, pts, au)
-}
-
-func (v *muxerVariantFMP4) writeAudio(ntp time.Time, pts time.Duration, au []byte) error {
- return v.segmenter.writeAudio(ntp, pts, au)
-}
-
-func (v *muxerVariantFMP4) mustRegenerateInit() bool {
- if v.videoTrack == nil {
- return false
- }
-
- videoParams := extractVideoParams(v.videoTrack)
- if !videoParamsEqual(videoParams, v.lastVideoParams) {
- v.lastVideoParams = videoParams
- return true
- }
-
- return false
-}
-
-func (v *muxerVariantFMP4) file(name string, msn string, part string, skip string) *MuxerFileResponse {
- if name == "init.mp4" {
- v.mutex.Lock()
- defer v.mutex.Unlock()
-
- if v.initContent == nil || v.mustRegenerateInit() {
- init := fmp4.Init{}
- trackID := 1
-
- if v.videoTrack != nil {
- init.Tracks = append(init.Tracks, &fmp4.InitTrack{
- ID: trackID,
- TimeScale: 90000,
- Format: v.videoTrack,
- })
- trackID++
- }
-
- if v.audioTrack != nil {
- init.Tracks = append(init.Tracks, &fmp4.InitTrack{
- ID: trackID,
- TimeScale: uint32(v.audioTrack.ClockRate()),
- Format: v.audioTrack,
- })
- }
-
- var err error
- v.initContent, err = init.Marshal()
- if err != nil {
- return &MuxerFileResponse{Status: http.StatusNotFound}
- }
- }
-
- return &MuxerFileResponse{
- Status: http.StatusOK,
- Header: map[string]string{
- "Content-Type": "video/mp4",
- },
- Body: bytes.NewReader(v.initContent),
- }
- }
-
- return v.playlist.file(name, msn, part, skip)
-}
diff --git a/internal/hls/muxer_variant_fmp4_part.go b/internal/hls/muxer_variant_fmp4_part.go
deleted file mode 100644
index 9a25dad3..00000000
--- a/internal/hls/muxer_variant_fmp4_part.go
+++ /dev/null
@@ -1,140 +0,0 @@
-package hls
-
-import (
- "bytes"
- "io"
- "strconv"
- "time"
-
- "github.com/aler9/gortsplib/v2/pkg/codecs/mpeg4audio"
- "github.com/aler9/gortsplib/v2/pkg/format"
-
- "github.com/aler9/rtsp-simple-server/internal/hls/fmp4"
-)
-
-func fmp4PartName(id uint64) string {
- return "part" + strconv.FormatUint(id, 10)
-}
-
-type muxerVariantFMP4Part struct {
- videoTrack format.Format
- audioTrack format.Format
- id uint64
-
- isIndependent bool
- videoSamples []*fmp4.PartSample
- audioSamples []*fmp4.PartSample
- content []byte
- renderedDuration time.Duration
- videoStartDTSFilled bool
- videoStartDTS time.Duration
- audioStartDTSFilled bool
- audioStartDTS time.Duration
-}
-
-func newMuxerVariantFMP4Part(
- videoTrack format.Format,
- audioTrack format.Format,
- id uint64,
-) *muxerVariantFMP4Part {
- p := &muxerVariantFMP4Part{
- videoTrack: videoTrack,
- audioTrack: audioTrack,
- id: id,
- }
-
- if videoTrack == nil {
- p.isIndependent = true
- }
-
- return p
-}
-
-func (p *muxerVariantFMP4Part) name() string {
- return fmp4PartName(p.id)
-}
-
-func (p *muxerVariantFMP4Part) reader() io.Reader {
- return bytes.NewReader(p.content)
-}
-
-func (p *muxerVariantFMP4Part) duration() time.Duration {
- if p.videoTrack != nil {
- ret := uint64(0)
- for _, e := range p.videoSamples {
- ret += uint64(e.Duration)
- }
- return durationMp4ToGo(ret, 90000)
- }
-
- // use the sum of the default duration of all samples,
- // not the real duration,
- // otherwise on iPhone iOS the stream freezes.
- return time.Duration(len(p.audioSamples)) * time.Second *
- time.Duration(mpeg4audio.SamplesPerAccessUnit) / time.Duration(p.audioTrack.ClockRate())
-}
-
-func (p *muxerVariantFMP4Part) finalize() error {
- if p.videoSamples != nil || p.audioSamples != nil {
- part := fmp4.Part{}
-
- if p.videoSamples != nil {
- part.Tracks = append(part.Tracks, &fmp4.PartTrack{
- ID: 1,
- BaseTime: durationGoToMp4(p.videoStartDTS, 90000),
- Samples: p.videoSamples,
- IsVideo: true,
- })
- }
-
- if p.audioSamples != nil {
- var id int
- if p.videoTrack != nil {
- id = 2
- } else {
- id = 1
- }
-
- part.Tracks = append(part.Tracks, &fmp4.PartTrack{
- ID: id,
- BaseTime: durationGoToMp4(p.audioStartDTS, uint32(p.audioTrack.ClockRate())),
- Samples: p.audioSamples,
- })
- }
-
- var err error
- p.content, err = part.Marshal()
- if err != nil {
- return err
- }
-
- p.renderedDuration = p.duration()
- }
-
- p.videoSamples = nil
- p.audioSamples = nil
-
- return nil
-}
-
-func (p *muxerVariantFMP4Part) writeH264(sample *augmentedVideoSample) {
- if !p.videoStartDTSFilled {
- p.videoStartDTSFilled = true
- p.videoStartDTS = sample.dts
- }
-
- if !sample.IsNonSyncSample {
- p.isIndependent = true
- }
-
- p.videoSamples = append(p.videoSamples, &sample.PartSample)
-}
-
-func (p *muxerVariantFMP4Part) writeAudio(sample *augmentedAudioSample) {
- if !p.audioStartDTSFilled {
- p.audioStartDTSFilled = true
- p.audioStartDTS = sample.dts
- }
-
- p.audioSamples = append(p.audioSamples, &sample.PartSample)
-}
diff --git a/internal/hls/muxer_variant_fmp4_playlist.go b/internal/hls/muxer_variant_fmp4_playlist.go
deleted file mode 100644
index 5d801873..00000000
--- a/internal/hls/muxer_variant_fmp4_playlist.go
+++ /dev/null
@@ -1,489 +0,0 @@
-package hls
-
-import (
- "bytes"
- "io"
- "math"
- "net/http"
- "strconv"
- "strings"
- "sync"
- "time"
-
- "github.com/aler9/gortsplib/v2/pkg/format"
-)
-
-type muxerVariantFMP4SegmentOrGap interface {
- getRenderedDuration() time.Duration
-}
-
-type muxerVariantFMP4Gap struct {
- renderedDuration time.Duration
-}
-
-func (g muxerVariantFMP4Gap) getRenderedDuration() time.Duration {
- return g.renderedDuration
-}
-
-func targetDuration(segments []muxerVariantFMP4SegmentOrGap) uint {
- ret := uint(0)
-
- // EXTINF, when rounded to the nearest integer, must be <= EXT-X-TARGETDURATION
- for _, sog := range segments {
- v := uint(math.Round(sog.getRenderedDuration().Seconds()))
- if v > ret {
- ret = v
- }
- }
-
- return ret
-}
-
-func partTargetDuration(
- segments []muxerVariantFMP4SegmentOrGap,
- nextSegmentParts []*muxerVariantFMP4Part,
-) time.Duration {
- var ret time.Duration
-
- for _, sog := range segments {
- seg, ok := sog.(*muxerVariantFMP4Segment)
- if !ok {
- continue
- }
-
- for _, part := range seg.parts {
- if part.renderedDuration > ret {
- ret = part.renderedDuration
- }
- }
- }
-
- for _, part := range nextSegmentParts {
- if part.renderedDuration > ret {
- ret = part.renderedDuration
- }
- }
-
- return ret
-}
-
-type muxerVariantFMP4Playlist struct {
- lowLatency bool
- segmentCount int
- videoTrack format.Format
- audioTrack format.Format
-
- mutex sync.Mutex
- cond *sync.Cond
- closed bool
- segments []muxerVariantFMP4SegmentOrGap
- segmentsByName map[string]*muxerVariantFMP4Segment
- segmentDeleteCount int
- parts []*muxerVariantFMP4Part
- partsByName map[string]*muxerVariantFMP4Part
- nextSegmentID uint64
- nextSegmentParts []*muxerVariantFMP4Part
- nextPartID uint64
-}
-
-func newMuxerVariantFMP4Playlist(
- lowLatency bool,
- segmentCount int,
- videoTrack format.Format,
- audioTrack format.Format,
-) *muxerVariantFMP4Playlist {
- p := &muxerVariantFMP4Playlist{
- lowLatency: lowLatency,
- segmentCount: segmentCount,
- videoTrack: videoTrack,
- audioTrack: audioTrack,
- segmentsByName: make(map[string]*muxerVariantFMP4Segment),
- partsByName: make(map[string]*muxerVariantFMP4Part),
- }
- p.cond = sync.NewCond(&p.mutex)
-
- return p
-}
-
-func (p *muxerVariantFMP4Playlist) close() {
- func() {
- p.mutex.Lock()
- defer p.mutex.Unlock()
- p.closed = true
- }()
-
- p.cond.Broadcast()
-}
-
-func (p *muxerVariantFMP4Playlist) hasContent() bool {
- if p.lowLatency {
- return len(p.segments) >= 1
- }
- return len(p.segments) >= 2
-}
-
-func (p *muxerVariantFMP4Playlist) hasPart(segmentID uint64, partID uint64) bool {
- if !p.hasContent() {
- return false
- }
-
- for _, sop := range p.segments {
- seg, ok := sop.(*muxerVariantFMP4Segment)
- if !ok {
- continue
- }
-
- if segmentID != seg.id {
- continue
- }
-
- // If the Client requests a Part Index greater than that of the final
- // Partial Segment of the Parent Segment, the Server MUST treat the
- // request as one for Part Index 0 of the following Parent Segment.
- if partID >= uint64(len(seg.parts)) {
- segmentID++
- partID = 0
- continue
- }
-
- return true
- }
-
- if segmentID != p.nextSegmentID {
- return false
- }
-
- if partID >= uint64(len(p.nextSegmentParts)) {
- return false
- }
-
- return true
-}
-
-func (p *muxerVariantFMP4Playlist) file(name string, msn string, part string, skip string) *MuxerFileResponse {
- switch {
- case name == "stream.m3u8":
- return p.playlistReader(msn, part, skip)
-
- case strings.HasSuffix(name, ".mp4"):
- return p.segmentReader(name)
-
- default:
- return &MuxerFileResponse{Status: http.StatusNotFound}
- }
-}
-
-func (p *muxerVariantFMP4Playlist) playlistReader(msn string, part string, skip string) *MuxerFileResponse {
- isDeltaUpdate := false
-
- if p.lowLatency {
- isDeltaUpdate = skip == "YES" || skip == "v2"
-
- var msnint uint64
- if msn != "" {
- var err error
- msnint, err = strconv.ParseUint(msn, 10, 64)
- if err != nil {
- return &MuxerFileResponse{Status: http.StatusBadRequest}
- }
- }
-
- var partint uint64
- if part != "" {
- var err error
- partint, err = strconv.ParseUint(part, 10, 64)
- if err != nil {
- return &MuxerFileResponse{Status: http.StatusBadRequest}
- }
- }
-
- if msn != "" {
- p.mutex.Lock()
- defer p.mutex.Unlock()
-
- // If the _HLS_msn is greater than the Media Sequence Number of the last
- // Media Segment in the current Playlist plus two, or if the _HLS_part
- // exceeds the last Partial Segment in the current Playlist by the
- // Advance Part Limit, then the server SHOULD immediately return Bad
- // Request, such as HTTP 400.
- if msnint > (p.nextSegmentID + 1) {
- return &MuxerFileResponse{Status: http.StatusBadRequest}
- }
-
- for !p.closed && !p.hasPart(msnint, partint) {
- p.cond.Wait()
- }
-
- if p.closed {
- return &MuxerFileResponse{Status: http.StatusNotFound}
- }
-
- return &MuxerFileResponse{
- Status: http.StatusOK,
- Header: map[string]string{
- "Content-Type": `application/x-mpegURL`,
- },
- Body: p.fullPlaylist(isDeltaUpdate),
- }
- }
-
- // part without msn is not supported.
- if part != "" {
- return &MuxerFileResponse{Status: http.StatusBadRequest}
- }
- }
-
- p.mutex.Lock()
- defer p.mutex.Unlock()
-
- for !p.closed && !p.hasContent() {
- p.cond.Wait()
- }
-
- if p.closed {
- return &MuxerFileResponse{Status: http.StatusNotFound}
- }
-
- return &MuxerFileResponse{
- Status: http.StatusOK,
- Header: map[string]string{
- "Content-Type": `application/x-mpegURL`,
- },
- Body: p.fullPlaylist(isDeltaUpdate),
- }
-}
-
-func (p *muxerVariantFMP4Playlist) fullPlaylist(isDeltaUpdate bool) io.Reader {
- cnt := "#EXTM3U\n"
- cnt += "#EXT-X-VERSION:9\n"
-
- targetDuration := targetDuration(p.segments)
- cnt += "#EXT-X-TARGETDURATION:" + strconv.FormatUint(uint64(targetDuration), 10) + "\n"
-
- skipBoundary := float64(targetDuration * 6)
-
- if p.lowLatency {
- partTargetDuration := partTargetDuration(p.segments, p.nextSegmentParts)
-
- // The value is an enumerated-string whose value is YES if the server
- // supports Blocking Playlist Reload
- cnt += "#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES"
-
- // The value is a decimal-floating-point number of seconds that
- // indicates the server-recommended minimum distance from the end of
- // the Playlist at which clients should begin to play or to which
- // they should seek when playing in Low-Latency Mode. Its value MUST
- // be at least twice the Part Target Duration. Its value SHOULD be
- // at least three times the Part Target Duration.
- cnt += ",PART-HOLD-BACK=" + strconv.FormatFloat((partTargetDuration).Seconds()*2.5, 'f', 5, 64)
-
- // Indicates that the Server can produce Playlist Delta Updates in
- // response to the _HLS_skip Delivery Directive. Its value is the
- // Skip Boundary, a decimal-floating-point number of seconds. The
- // Skip Boundary MUST be at least six times the Target Duration.
- cnt += ",CAN-SKIP-UNTIL=" + strconv.FormatFloat(skipBoundary, 'f', -1, 64)
-
- cnt += "\n"
-
- cnt += "#EXT-X-PART-INF:PART-TARGET=" + strconv.FormatFloat(partTargetDuration.Seconds(), 'f', -1, 64) + "\n"
- }
-
- cnt += "#EXT-X-MEDIA-SEQUENCE:" + strconv.FormatInt(int64(p.segmentDeleteCount), 10) + "\n"
-
- skipped := 0
-
- if !isDeltaUpdate {
- cnt += "#EXT-X-MAP:URI=\"init.mp4\"\n"
- } else {
- var curDuration time.Duration
- shown := 0
- for _, segment := range p.segments {
- curDuration += segment.getRenderedDuration()
- if curDuration.Seconds() >= skipBoundary {
- break
- }
- shown++
- }
- skipped = len(p.segments) - shown
- cnt += "#EXT-X-SKIP:SKIPPED-SEGMENTS=" + strconv.FormatInt(int64(skipped), 10) + "\n"
- }
-
- for i, sog := range p.segments {
- if i < skipped {
- continue
- }
-
- switch seg := sog.(type) {
- case *muxerVariantFMP4Segment:
- if (len(p.segments) - i) <= 2 {
- cnt += "#EXT-X-PROGRAM-DATE-TIME:" + seg.startTime.Format("2006-01-02T15:04:05.999Z07:00") + "\n"
- }
-
- if p.lowLatency && (len(p.segments)-i) <= 2 {
- for _, part := range seg.parts {
- cnt += "#EXT-X-PART:DURATION=" + strconv.FormatFloat(part.renderedDuration.Seconds(), 'f', 5, 64) +
- ",URI=\"" + part.name() + ".mp4\""
- if part.isIndependent {
- cnt += ",INDEPENDENT=YES"
- }
- cnt += "\n"
- }
- }
-
- cnt += "#EXTINF:" + strconv.FormatFloat(seg.renderedDuration.Seconds(), 'f', 5, 64) + ",\n" +
- seg.name + ".mp4\n"
-
- case *muxerVariantFMP4Gap:
- cnt += "#EXT-X-GAP\n" +
- "#EXTINF:" + strconv.FormatFloat(seg.renderedDuration.Seconds(), 'f', 5, 64) + ",\n" +
- "gap.mp4\n"
- }
- }
-
- if p.lowLatency {
- for _, part := range p.nextSegmentParts {
- cnt += "#EXT-X-PART:DURATION=" + strconv.FormatFloat(part.renderedDuration.Seconds(), 'f', 5, 64) +
- ",URI=\"" + part.name() + ".mp4\""
- if part.isIndependent {
- cnt += ",INDEPENDENT=YES"
- }
- cnt += "\n"
- }
-
- // preload hint must always be present
- // otherwise hls.js goes into a loop
- cnt += "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"" + fmp4PartName(p.nextPartID) + ".mp4\"\n"
- }
-
- return bytes.NewReader([]byte(cnt))
-}
-
-func (p *muxerVariantFMP4Playlist) segmentReader(fname string) *MuxerFileResponse {
- switch {
- case strings.HasPrefix(fname, "seg"):
- base := strings.TrimSuffix(fname, ".mp4")
-
- p.mutex.Lock()
- segment, ok := p.segmentsByName[base]
- p.mutex.Unlock()
-
- if !ok {
- return &MuxerFileResponse{Status: http.StatusNotFound}
- }
-
- return &MuxerFileResponse{
- Status: http.StatusOK,
- Header: map[string]string{
- "Content-Type": "video/mp4",
- },
- Body: segment.reader(),
- }
-
- case strings.HasPrefix(fname, "part"):
- base := strings.TrimSuffix(fname, ".mp4")
-
- p.mutex.Lock()
- part, ok := p.partsByName[base]
- nextPartID := p.nextPartID
- p.mutex.Unlock()
-
- if ok {
- return &MuxerFileResponse{
- Status: http.StatusOK,
- Header: map[string]string{
- "Content-Type": "video/mp4",
- },
- Body: part.reader(),
- }
- }
-
- // EXT-X-PRELOAD-HINT support
- nextPartName := fmp4PartName(p.nextPartID)
- if base == nextPartName {
- p.mutex.Lock()
- defer p.mutex.Unlock()
-
- for {
- if p.closed {
- break
- }
-
- if p.nextPartID > nextPartID {
- break
- }
-
- p.cond.Wait()
- }
-
- if p.closed {
- return &MuxerFileResponse{Status: http.StatusNotFound}
- }
-
- return &MuxerFileResponse{
- Status: http.StatusOK,
- Header: map[string]string{
- "Content-Type": "video/mp4",
- },
- Body: p.partsByName[nextPartName].reader(),
- }
- }
-
- return &MuxerFileResponse{Status: http.StatusNotFound}
-
- default:
- return &MuxerFileResponse{Status: http.StatusNotFound}
- }
-}
-
-func (p *muxerVariantFMP4Playlist) onSegmentFinalized(segment *muxerVariantFMP4Segment) {
- func() {
- p.mutex.Lock()
- defer p.mutex.Unlock()
-
- // add initial gaps, required by iOS LL-HLS
- if p.lowLatency && len(p.segments) == 0 {
- for i := 0; i < 7; i++ {
- p.segments = append(p.segments, &muxerVariantFMP4Gap{
- renderedDuration: segment.renderedDuration,
- })
- }
- }
-
- p.segmentsByName[segment.name] = segment
- p.segments = append(p.segments, segment)
- p.nextSegmentID = segment.id + 1
- p.nextSegmentParts = p.nextSegmentParts[:0]
-
- if len(p.segments) > p.segmentCount {
- toDelete := p.segments[0]
-
- if toDeleteSeg, ok := toDelete.(*muxerVariantFMP4Segment); ok {
- for _, part := range toDeleteSeg.parts {
- delete(p.partsByName, part.name())
- }
- p.parts = p.parts[len(toDeleteSeg.parts):]
-
- delete(p.segmentsByName, toDeleteSeg.name)
- }
-
- p.segments = p.segments[1:]
- p.segmentDeleteCount++
- }
- }()
-
- p.cond.Broadcast()
-}
-
-func (p *muxerVariantFMP4Playlist) onPartFinalized(part *muxerVariantFMP4Part) {
- func() {
- p.mutex.Lock()
- defer p.mutex.Unlock()
-
- p.partsByName[part.name()] = part
- p.parts = append(p.parts, part)
- p.nextSegmentParts = append(p.nextSegmentParts, part)
- p.nextPartID = part.id + 1
- }()
-
- p.cond.Broadcast()
-}
diff --git a/internal/hls/muxer_variant_fmp4_segment.go b/internal/hls/muxer_variant_fmp4_segment.go
deleted file mode 100644
index 4e226cd2..00000000
--- a/internal/hls/muxer_variant_fmp4_segment.go
+++ /dev/null
@@ -1,186 +0,0 @@
-package hls
-
-import (
- "fmt"
- "io"
- "strconv"
- "time"
-
- "github.com/aler9/gortsplib/v2/pkg/format"
-)
-
-type partsReader struct {
- parts []*muxerVariantFMP4Part
- curPart int
- curPos int
-}
-
-func (mbr *partsReader) Read(p []byte) (int, error) {
- n := 0
- lenp := len(p)
-
- for {
- if mbr.curPart >= len(mbr.parts) {
- return n, io.EOF
- }
-
- copied := copy(p[n:], mbr.parts[mbr.curPart].content[mbr.curPos:])
- mbr.curPos += copied
- n += copied
-
- if mbr.curPos == len(mbr.parts[mbr.curPart].content) {
- mbr.curPart++
- mbr.curPos = 0
- }
-
- if n == lenp {
- return n, nil
- }
- }
-}
-
-type muxerVariantFMP4Segment struct {
- lowLatency bool
- id uint64
- startTime time.Time
- startDTS time.Duration
- segmentMaxSize uint64
- videoTrack format.Format
- audioTrack format.Format
- genPartID func() uint64
- onPartFinalized func(*muxerVariantFMP4Part)
-
- name string
- size uint64
- parts []*muxerVariantFMP4Part
- currentPart *muxerVariantFMP4Part
- renderedDuration time.Duration
-}
-
-func newMuxerVariantFMP4Segment(
- lowLatency bool,
- id uint64,
- startTime time.Time,
- startDTS time.Duration,
- segmentMaxSize uint64,
- videoTrack format.Format,
- audioTrack format.Format,
- genPartID func() uint64,
- onPartFinalized func(*muxerVariantFMP4Part),
-) *muxerVariantFMP4Segment {
- s := &muxerVariantFMP4Segment{
- lowLatency: lowLatency,
- id: id,
- startTime: startTime,
- startDTS: startDTS,
- segmentMaxSize: segmentMaxSize,
- videoTrack: videoTrack,
- audioTrack: audioTrack,
- genPartID: genPartID,
- onPartFinalized: onPartFinalized,
- name: "seg" + strconv.FormatUint(id, 10),
- }
-
- s.currentPart = newMuxerVariantFMP4Part(
- s.videoTrack,
- s.audioTrack,
- s.genPartID(),
- )
-
- return s
-}
-
-func (s *muxerVariantFMP4Segment) reader() io.Reader {
- return &partsReader{parts: s.parts}
-}
-
-func (s *muxerVariantFMP4Segment) getRenderedDuration() time.Duration {
- return s.renderedDuration
-}
-
-func (s *muxerVariantFMP4Segment) finalize(
- nextVideoSampleDTS time.Duration,
-) error {
- err := s.currentPart.finalize()
- if err != nil {
- return err
- }
-
- if s.currentPart.content != nil {
- s.onPartFinalized(s.currentPart)
- s.parts = append(s.parts, s.currentPart)
- }
-
- s.currentPart = nil
-
- if s.videoTrack != nil {
- s.renderedDuration = nextVideoSampleDTS - s.startDTS
- } else {
- s.renderedDuration = 0
- for _, pa := range s.parts {
- s.renderedDuration += pa.renderedDuration
- }
- }
-
- return nil
-}
-
-func (s *muxerVariantFMP4Segment) writeH264(sample *augmentedVideoSample, adjustedPartDuration time.Duration) error {
- size := uint64(len(sample.Payload))
- if (s.size + size) > s.segmentMaxSize {
- return fmt.Errorf("reached maximum segment size")
- }
- s.size += size
-
- s.currentPart.writeH264(sample)
-
- // switch part
- if s.lowLatency &&
- s.currentPart.duration() >= adjustedPartDuration {
- err := s.currentPart.finalize()
- if err != nil {
- return err
- }
-
- s.parts = append(s.parts, s.currentPart)
- s.onPartFinalized(s.currentPart)
-
- s.currentPart = newMuxerVariantFMP4Part(
- s.videoTrack,
- s.audioTrack,
- s.genPartID(),
- )
- }
-
- return nil
-}
-
-func (s *muxerVariantFMP4Segment) writeAudio(sample *augmentedAudioSample, adjustedPartDuration time.Duration) error {
- size := uint64(len(sample.Payload))
- if (s.size + size) > s.segmentMaxSize {
- return fmt.Errorf("reached maximum segment size")
- }
- s.size += size
-
- s.currentPart.writeAudio(sample)
-
- // switch part
- if s.lowLatency && s.videoTrack == nil &&
- s.currentPart.duration() >= adjustedPartDuration {
- err := s.currentPart.finalize()
- if err != nil {
- return err
- }
-
- s.parts = append(s.parts, s.currentPart)
- s.onPartFinalized(s.currentPart)
-
- s.currentPart = newMuxerVariantFMP4Part(
- s.videoTrack,
- s.audioTrack,
- s.genPartID(),
- )
- }
-
- return nil
-}
diff --git a/internal/hls/muxer_variant_fmp4_segmenter.go b/internal/hls/muxer_variant_fmp4_segmenter.go
deleted file mode 100644
index 8ea8496d..00000000
--- a/internal/hls/muxer_variant_fmp4_segmenter.go
+++ /dev/null
@@ -1,406 +0,0 @@
-package hls
-
-import (
- "fmt"
- "time"
-
- "github.com/aler9/gortsplib/v2/pkg/codecs/h264"
- "github.com/aler9/gortsplib/v2/pkg/codecs/h265"
- "github.com/aler9/gortsplib/v2/pkg/format"
-
- "github.com/aler9/rtsp-simple-server/internal/hls/fmp4"
-)
-
-func partDurationIsCompatible(partDuration time.Duration, sampleDuration time.Duration) bool {
- if sampleDuration > partDuration {
- return false
- }
-
- f := (partDuration / sampleDuration)
- if (partDuration % sampleDuration) != 0 {
- f++
- }
- f *= sampleDuration
-
- return partDuration > ((f * 85) / 100)
-}
-
-func partDurationIsCompatibleWithAll(partDuration time.Duration, sampleDurations map[time.Duration]struct{}) bool {
- for sd := range sampleDurations {
- if !partDurationIsCompatible(partDuration, sd) {
- return false
- }
- }
- return true
-}
-
-func findCompatiblePartDuration(
- minPartDuration time.Duration,
- sampleDurations map[time.Duration]struct{},
-) time.Duration {
- i := minPartDuration
- for ; i < 5*time.Second; i += 5 * time.Millisecond {
- if partDurationIsCompatibleWithAll(i, sampleDurations) {
- break
- }
- }
- return i
-}
-
-type dtsExtractor interface {
- Extract([][]byte, time.Duration) (time.Duration, error)
-}
-
-func allocateDTSExtractor(track format.Format) dtsExtractor {
- switch track.(type) {
- case *format.H264:
- return h264.NewDTSExtractor()
-
- case *format.H265:
- return h265.NewDTSExtractor()
- }
- return nil
-}
-
-type augmentedVideoSample struct {
- fmp4.PartSample
- dts time.Duration
- ntp time.Time
-}
-
-type augmentedAudioSample struct {
- fmp4.PartSample
- dts time.Duration
- ntp time.Time
-}
-
-type muxerVariantFMP4Segmenter struct {
- lowLatency bool
- segmentDuration time.Duration
- partDuration time.Duration
- segmentMaxSize uint64
- videoTrack format.Format
- audioTrack format.Format
- onSegmentFinalized func(*muxerVariantFMP4Segment)
- onPartFinalized func(*muxerVariantFMP4Part)
-
- startDTS time.Duration
- videoFirstRandomAccessReceived bool
- videoDTSExtractor dtsExtractor
- lastVideoParams [][]byte
- currentSegment *muxerVariantFMP4Segment
- nextSegmentID uint64
- nextPartID uint64
- nextVideoSample *augmentedVideoSample
- nextAudioSample *augmentedAudioSample
- firstSegmentFinalized bool
- sampleDurations map[time.Duration]struct{}
- adjustedPartDuration time.Duration
-}
-
-func newMuxerVariantFMP4Segmenter(
- lowLatency bool,
- segmentCount int,
- segmentDuration time.Duration,
- partDuration time.Duration,
- segmentMaxSize uint64,
- videoTrack format.Format,
- audioTrack format.Format,
- onSegmentFinalized func(*muxerVariantFMP4Segment),
- onPartFinalized func(*muxerVariantFMP4Part),
-) *muxerVariantFMP4Segmenter {
- m := &muxerVariantFMP4Segmenter{
- lowLatency: lowLatency,
- segmentDuration: segmentDuration,
- partDuration: partDuration,
- segmentMaxSize: segmentMaxSize,
- videoTrack: videoTrack,
- audioTrack: audioTrack,
- onSegmentFinalized: onSegmentFinalized,
- onPartFinalized: onPartFinalized,
- sampleDurations: make(map[time.Duration]struct{}),
- }
-
- // add initial gaps, required by iOS LL-HLS
- if m.lowLatency {
- m.nextSegmentID = 7
- }
-
- return m
-}
-
-func (m *muxerVariantFMP4Segmenter) genSegmentID() uint64 {
- id := m.nextSegmentID
- m.nextSegmentID++
- return id
-}
-
-func (m *muxerVariantFMP4Segmenter) genPartID() uint64 {
- id := m.nextPartID
- m.nextPartID++
- return id
-}
-
-// iPhone iOS fails if part durations are less than 85% of maximum part duration.
-// find a part duration that is compatible with all received sample durations
-func (m *muxerVariantFMP4Segmenter) adjustPartDuration(du time.Duration) {
- if !m.lowLatency || m.firstSegmentFinalized {
- return
- }
-
- // avoid a crash by skipping invalid durations
- if du == 0 {
- return
- }
-
- if _, ok := m.sampleDurations[du]; !ok {
- m.sampleDurations[du] = struct{}{}
- m.adjustedPartDuration = findCompatiblePartDuration(
- m.partDuration,
- m.sampleDurations,
- )
- }
-}
-
-func (m *muxerVariantFMP4Segmenter) writeH26x(ntp time.Time, pts time.Duration, au [][]byte) error {
- randomAccessPresent := false
-
- switch m.videoTrack.(type) {
- case *format.H264:
- nonIDRPresent := false
-
- for _, nalu := range au {
- typ := h264.NALUType(nalu[0] & 0x1F)
-
- switch typ {
- case h264.NALUTypeIDR:
- randomAccessPresent = true
-
- case h264.NALUTypeNonIDR:
- nonIDRPresent = true
- }
- }
-
- if !randomAccessPresent && !nonIDRPresent {
- return nil
- }
-
- case *format.H265:
- for _, nalu := range au {
- typ := h265.NALUType((nalu[0] >> 1) & 0b111111)
-
- switch typ {
- case h265.NALUType_IDR_W_RADL, h265.NALUType_IDR_N_LP, h265.NALUType_CRA_NUT:
- randomAccessPresent = true
- }
- }
- }
-
- return m.writeH26xEntry(ntp, pts, au, randomAccessPresent)
-}
-
-func (m *muxerVariantFMP4Segmenter) writeH26xEntry(
- ntp time.Time,
- pts time.Duration,
- au [][]byte,
- randomAccessPresent bool,
-) error {
- var dts time.Duration
-
- if !m.videoFirstRandomAccessReceived {
- // skip sample silently until we find one with an IDR
- if !randomAccessPresent {
- return nil
- }
-
- m.videoFirstRandomAccessReceived = true
- m.videoDTSExtractor = allocateDTSExtractor(m.videoTrack)
- m.lastVideoParams = extractVideoParams(m.videoTrack)
-
- var err error
- dts, err = m.videoDTSExtractor.Extract(au, pts)
- if err != nil {
- return fmt.Errorf("unable to extract DTS: %v", err)
- }
-
- m.startDTS = dts
- dts = 0
- pts -= m.startDTS
- } else {
- var err error
- dts, err = m.videoDTSExtractor.Extract(au, pts)
- if err != nil {
- return fmt.Errorf("unable to extract DTS: %v", err)
- }
-
- dts -= m.startDTS
- pts -= m.startDTS
- }
-
- avcc, err := h264.AVCCMarshal(au)
- if err != nil {
- return err
- }
-
- sample := &augmentedVideoSample{
- PartSample: fmp4.PartSample{
- PTSOffset: int32(durationGoToMp4(pts-dts, 90000)),
- IsNonSyncSample: !randomAccessPresent,
- Payload: avcc,
- },
- dts: dts,
- ntp: ntp,
- }
-
- // put samples into a queue in order to
- // - compute sample duration
- // - check if next sample is IDR
- sample, m.nextVideoSample = m.nextVideoSample, sample
- if sample == nil {
- return nil
- }
- sample.Duration = uint32(durationGoToMp4(m.nextVideoSample.dts-sample.dts, 90000))
-
- if m.currentSegment == nil {
- // create first segment
- m.currentSegment = newMuxerVariantFMP4Segment(
- m.lowLatency,
- m.genSegmentID(),
- sample.ntp,
- sample.dts,
- m.segmentMaxSize,
- m.videoTrack,
- m.audioTrack,
- m.genPartID,
- m.onPartFinalized,
- )
- }
-
- m.adjustPartDuration(durationMp4ToGo(uint64(sample.Duration), 90000))
-
- err = m.currentSegment.writeH264(sample, m.adjustedPartDuration)
- if err != nil {
- return err
- }
-
- // switch segment
- if randomAccessPresent {
- videoParams := extractVideoParams(m.videoTrack)
- paramsChanged := !videoParamsEqual(m.lastVideoParams, videoParams)
-
- if (m.nextVideoSample.dts-m.currentSegment.startDTS) >= m.segmentDuration ||
- paramsChanged {
- err := m.currentSegment.finalize(m.nextVideoSample.dts)
- if err != nil {
- return err
- }
- m.onSegmentFinalized(m.currentSegment)
-
- m.firstSegmentFinalized = true
-
- m.currentSegment = newMuxerVariantFMP4Segment(
- m.lowLatency,
- m.genSegmentID(),
- m.nextVideoSample.ntp,
- m.nextVideoSample.dts,
- m.segmentMaxSize,
- m.videoTrack,
- m.audioTrack,
- m.genPartID,
- m.onPartFinalized,
- )
-
- if paramsChanged {
- m.lastVideoParams = videoParams
- m.firstSegmentFinalized = false
-
- // reset adjusted part duration
- m.sampleDurations = make(map[time.Duration]struct{})
- }
- }
- }
-
- return nil
-}
-
-func (m *muxerVariantFMP4Segmenter) writeAudio(ntp time.Time, dts time.Duration, au []byte) error {
- if m.videoTrack != nil {
- // wait for the video track
- if !m.videoFirstRandomAccessReceived {
- return nil
- }
-
- dts -= m.startDTS
- if dts < 0 {
- return nil
- }
- }
-
- sample := &augmentedAudioSample{
- PartSample: fmp4.PartSample{
- Payload: au,
- },
- dts: dts,
- ntp: ntp,
- }
-
- // put samples into a queue in order to compute the sample duration
- sample, m.nextAudioSample = m.nextAudioSample, sample
- if sample == nil {
- return nil
- }
- sample.Duration = uint32(durationGoToMp4(m.nextAudioSample.dts-sample.dts, uint32(m.audioTrack.ClockRate())))
-
- if m.videoTrack == nil {
- if m.currentSegment == nil {
- // create first segment
- m.currentSegment = newMuxerVariantFMP4Segment(
- m.lowLatency,
- m.genSegmentID(),
- sample.ntp,
- sample.dts,
- m.segmentMaxSize,
- m.videoTrack,
- m.audioTrack,
- m.genPartID,
- m.onPartFinalized,
- )
- }
- } else {
- // wait for the video track
- if m.currentSegment == nil {
- return nil
- }
- }
-
- err := m.currentSegment.writeAudio(sample, m.partDuration)
- if err != nil {
- return err
- }
-
- // switch segment
- if m.videoTrack == nil &&
- (m.nextAudioSample.dts-m.currentSegment.startDTS) >= m.segmentDuration {
- err := m.currentSegment.finalize(0)
- if err != nil {
- return err
- }
- m.onSegmentFinalized(m.currentSegment)
-
- m.firstSegmentFinalized = true
-
- m.currentSegment = newMuxerVariantFMP4Segment(
- m.lowLatency,
- m.genSegmentID(),
- m.nextAudioSample.ntp,
- m.nextAudioSample.dts,
- m.segmentMaxSize,
- m.videoTrack,
- m.audioTrack,
- m.genPartID,
- m.onPartFinalized,
- )
- }
-
- return nil
-}
diff --git a/internal/hls/muxer_variant_mpegts.go b/internal/hls/muxer_variant_mpegts.go
deleted file mode 100644
index b92ebcb1..00000000
--- a/internal/hls/muxer_variant_mpegts.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package hls
-
-import (
- "fmt"
- "time"
-
- "github.com/aler9/gortsplib/v2/pkg/format"
-)
-
-type muxerVariantMPEGTS struct {
- playlist *muxerVariantMPEGTSPlaylist
- segmenter *muxerVariantMPEGTSSegmenter
-}
-
-func newMuxerVariantMPEGTS(
- segmentCount int,
- segmentDuration time.Duration,
- segmentMaxSize uint64,
- videoTrack format.Format,
- audioTrack format.Format,
-) (*muxerVariantMPEGTS, error) {
- var videoTrackH264 *format.H264
- if videoTrack != nil {
- var ok bool
- videoTrackH264, ok = videoTrack.(*format.H264)
- if !ok {
- return nil, fmt.Errorf(
- "the MPEG-TS variant of HLS only supports H264 video. Use the fMP4 or Low-Latency variants instead")
- }
- }
-
- var audioTrackMPEG4Audio *format.MPEG4Audio
- if audioTrack != nil {
- var ok bool
- audioTrackMPEG4Audio, ok = audioTrack.(*format.MPEG4Audio)
- if !ok {
- return nil, fmt.Errorf(
- "the MPEG-TS variant of HLS only supports MPEG4-audio. Use the fMP4 or Low-Latency variants instead")
- }
- }
-
- v := &muxerVariantMPEGTS{}
-
- v.playlist = newMuxerVariantMPEGTSPlaylist(segmentCount)
-
- v.segmenter = newMuxerVariantMPEGTSSegmenter(
- segmentDuration,
- segmentMaxSize,
- videoTrackH264,
- audioTrackMPEG4Audio,
- func(seg *muxerVariantMPEGTSSegment) {
- v.playlist.pushSegment(seg)
- },
- )
-
- return v, nil
-}
-
-func (v *muxerVariantMPEGTS) close() {
- v.playlist.close()
-}
-
-func (v *muxerVariantMPEGTS) writeH26x(ntp time.Time, pts time.Duration, nalus [][]byte) error {
- return v.segmenter.writeH264(ntp, pts, nalus)
-}
-
-func (v *muxerVariantMPEGTS) writeAudio(ntp time.Time, pts time.Duration, au []byte) error {
- return v.segmenter.writeAAC(ntp, pts, au)
-}
-
-func (v *muxerVariantMPEGTS) file(name string, msn string, part string, skip string) *MuxerFileResponse {
- return v.playlist.file(name)
-}
diff --git a/internal/hls/muxer_variant_mpegts_playlist.go b/internal/hls/muxer_variant_mpegts_playlist.go
deleted file mode 100644
index dbe25f57..00000000
--- a/internal/hls/muxer_variant_mpegts_playlist.go
+++ /dev/null
@@ -1,145 +0,0 @@
-package hls
-
-import (
- "bytes"
- "io"
- "math"
- "net/http"
- "strconv"
- "strings"
- "sync"
-)
-
-type muxerVariantMPEGTSPlaylist struct {
- segmentCount int
-
- mutex sync.Mutex
- cond *sync.Cond
- closed bool
- segments []*muxerVariantMPEGTSSegment
- segmentByName map[string]*muxerVariantMPEGTSSegment
- segmentDeleteCount int
-}
-
-func newMuxerVariantMPEGTSPlaylist(segmentCount int) *muxerVariantMPEGTSPlaylist {
- p := &muxerVariantMPEGTSPlaylist{
- segmentCount: segmentCount,
- segmentByName: make(map[string]*muxerVariantMPEGTSSegment),
- }
- p.cond = sync.NewCond(&p.mutex)
-
- return p
-}
-
-func (p *muxerVariantMPEGTSPlaylist) close() {
- func() {
- p.mutex.Lock()
- defer p.mutex.Unlock()
- p.closed = true
- }()
-
- p.cond.Broadcast()
-}
-
-func (p *muxerVariantMPEGTSPlaylist) file(name string) *MuxerFileResponse {
- switch {
- case name == "stream.m3u8":
- return p.playlistReader()
-
- case strings.HasSuffix(name, ".ts"):
- return p.segmentReader(name)
-
- default:
- return &MuxerFileResponse{Status: http.StatusNotFound}
- }
-}
-
-func (p *muxerVariantMPEGTSPlaylist) playlist() io.Reader {
- cnt := "#EXTM3U\n"
- cnt += "#EXT-X-VERSION:3\n"
- cnt += "#EXT-X-ALLOW-CACHE:NO\n"
-
- targetDuration := func() uint {
- ret := uint(0)
-
- // EXTINF, when rounded to the nearest integer, must be <= EXT-X-TARGETDURATION
- for _, s := range p.segments {
- v2 := uint(math.Round(s.duration().Seconds()))
- if v2 > ret {
- ret = v2
- }
- }
-
- return ret
- }()
- cnt += "#EXT-X-TARGETDURATION:" + strconv.FormatUint(uint64(targetDuration), 10) + "\n"
-
- cnt += "#EXT-X-MEDIA-SEQUENCE:" + strconv.FormatInt(int64(p.segmentDeleteCount), 10) + "\n"
-
- for _, s := range p.segments {
- cnt += "#EXT-X-PROGRAM-DATE-TIME:" + s.startTime.Format("2006-01-02T15:04:05.999Z07:00") + "\n" +
- "#EXTINF:" + strconv.FormatFloat(s.duration().Seconds(), 'f', -1, 64) + ",\n" +
- s.name + ".ts\n"
- }
-
- return bytes.NewReader([]byte(cnt))
-}
-
-func (p *muxerVariantMPEGTSPlaylist) playlistReader() *MuxerFileResponse {
- p.mutex.Lock()
- defer p.mutex.Unlock()
-
- if !p.closed && len(p.segments) == 0 {
- p.cond.Wait()
- }
-
- if p.closed {
- return &MuxerFileResponse{Status: http.StatusNotFound}
- }
-
- return &MuxerFileResponse{
- Status: http.StatusOK,
- Header: map[string]string{
- "Content-Type": `application/x-mpegURL`,
- },
- Body: p.playlist(),
- }
-}
-
-func (p *muxerVariantMPEGTSPlaylist) segmentReader(fname string) *MuxerFileResponse {
- base := strings.TrimSuffix(fname, ".ts")
-
- p.mutex.Lock()
- f, ok := p.segmentByName[base]
- p.mutex.Unlock()
-
- if !ok {
- return &MuxerFileResponse{Status: http.StatusNotFound}
- }
-
- return &MuxerFileResponse{
- Status: http.StatusOK,
- Header: map[string]string{
- "Content-Type": "video/MP2T",
- },
- Body: f.reader(),
- }
-}
-
-func (p *muxerVariantMPEGTSPlaylist) pushSegment(t *muxerVariantMPEGTSSegment) {
- func() {
- p.mutex.Lock()
- defer p.mutex.Unlock()
-
- p.segmentByName[t.name] = t
- p.segments = append(p.segments, t)
-
- if len(p.segments) > p.segmentCount {
- delete(p.segmentByName, p.segments[0].name)
- p.segments = p.segments[1:]
- p.segmentDeleteCount++
- }
- }()
-
- p.cond.Broadcast()
-}
diff --git a/internal/hls/muxer_variant_mpegts_segment.go b/internal/hls/muxer_variant_mpegts_segment.go
deleted file mode 100644
index 4cc822d7..00000000
--- a/internal/hls/muxer_variant_mpegts_segment.go
+++ /dev/null
@@ -1,118 +0,0 @@
-package hls
-
-import (
- "bytes"
- "fmt"
- "io"
- "strconv"
- "time"
-
- "github.com/aler9/gortsplib/v2/pkg/format"
-
- "github.com/aler9/rtsp-simple-server/internal/hls/mpegts"
-)
-
-type muxerVariantMPEGTSSegment struct {
- segmentMaxSize uint64
- videoTrack *format.H264
- audioTrack *format.MPEG4Audio
- writer *mpegts.Writer
-
- size uint64
- startTime time.Time
- name string
- startDTS *time.Duration
- endDTS time.Duration
- audioAUCount int
- content []byte
-}
-
-func newMuxerVariantMPEGTSSegment(
- id uint64,
- startTime time.Time,
- segmentMaxSize uint64,
- videoTrack *format.H264,
- audioTrack *format.MPEG4Audio,
- writer *mpegts.Writer,
-) *muxerVariantMPEGTSSegment {
- t := &muxerVariantMPEGTSSegment{
- segmentMaxSize: segmentMaxSize,
- videoTrack: videoTrack,
- audioTrack: audioTrack,
- writer: writer,
- startTime: startTime,
- name: "seg" + strconv.FormatUint(id, 10),
- }
-
- return t
-}
-
-func (t *muxerVariantMPEGTSSegment) duration() time.Duration {
- return t.endDTS - *t.startDTS
-}
-
-func (t *muxerVariantMPEGTSSegment) reader() io.Reader {
- return bytes.NewReader(t.content)
-}
-
-func (t *muxerVariantMPEGTSSegment) finalize(endDTS time.Duration) {
- t.endDTS = endDTS
- t.content = t.writer.GenerateSegment()
-}
-
-func (t *muxerVariantMPEGTSSegment) writeH264(
- pcr time.Duration,
- dts time.Duration,
- pts time.Duration,
- idrPresent bool,
- nalus [][]byte,
-) error {
- size := uint64(0)
- for _, nalu := range nalus {
- size += uint64(len(nalu))
- }
- if (t.size + size) > t.segmentMaxSize {
- return fmt.Errorf("reached maximum segment size")
- }
- t.size += size
-
- err := t.writer.WriteH264(pcr, dts, pts, idrPresent, nalus)
- if err != nil {
- return err
- }
-
- if t.startDTS == nil {
- t.startDTS = &dts
- }
- t.endDTS = dts
-
- return nil
-}
-
-func (t *muxerVariantMPEGTSSegment) writeAAC(
- pcr time.Duration,
- pts time.Duration,
- au []byte,
-) error {
- size := uint64(len(au))
- if (t.size + size) > t.segmentMaxSize {
- return fmt.Errorf("reached maximum segment size")
- }
- t.size += size
-
- err := t.writer.WriteAAC(pcr, pts, au)
- if err != nil {
- return err
- }
-
- if t.videoTrack == nil {
- t.audioAUCount++
-
- if t.startDTS == nil {
- t.startDTS = &pts
- }
- t.endDTS = pts
- }
-
- return nil
-}
diff --git a/internal/hls/muxer_variant_mpegts_segmenter.go b/internal/hls/muxer_variant_mpegts_segmenter.go
deleted file mode 100644
index a9542499..00000000
--- a/internal/hls/muxer_variant_mpegts_segmenter.go
+++ /dev/null
@@ -1,193 +0,0 @@
-package hls
-
-import (
- "fmt"
- "time"
-
- "github.com/aler9/gortsplib/v2/pkg/codecs/h264"
- "github.com/aler9/gortsplib/v2/pkg/format"
-
- "github.com/aler9/rtsp-simple-server/internal/hls/mpegts"
-)
-
-const (
- mpegtsSegmentMinAUCount = 100
-)
-
-type muxerVariantMPEGTSSegmenter struct {
- segmentDuration time.Duration
- segmentMaxSize uint64
- videoTrack *format.H264
- audioTrack *format.MPEG4Audio
- onSegmentReady func(*muxerVariantMPEGTSSegment)
-
- writer *mpegts.Writer
- nextSegmentID uint64
- currentSegment *muxerVariantMPEGTSSegment
- videoDTSExtractor *h264.DTSExtractor
- startPCR time.Time
- startDTS time.Duration
-}
-
-func newMuxerVariantMPEGTSSegmenter(
- segmentDuration time.Duration,
- segmentMaxSize uint64,
- videoTrack *format.H264,
- audioTrack *format.MPEG4Audio,
- onSegmentReady func(*muxerVariantMPEGTSSegment),
-) *muxerVariantMPEGTSSegmenter {
- m := &muxerVariantMPEGTSSegmenter{
- segmentDuration: segmentDuration,
- segmentMaxSize: segmentMaxSize,
- videoTrack: videoTrack,
- audioTrack: audioTrack,
- onSegmentReady: onSegmentReady,
- }
-
- m.writer = mpegts.NewWriter(
- videoTrack,
- audioTrack)
-
- return m
-}
-
-func (m *muxerVariantMPEGTSSegmenter) genSegmentID() uint64 {
- id := m.nextSegmentID
- m.nextSegmentID++
- return id
-}
-
-func (m *muxerVariantMPEGTSSegmenter) writeH264(ntp time.Time, pts time.Duration, nalus [][]byte) error {
- idrPresent := false
- nonIDRPresent := false
-
- for _, nalu := range nalus {
- typ := h264.NALUType(nalu[0] & 0x1F)
- switch typ {
- case h264.NALUTypeIDR:
- idrPresent = true
-
- case h264.NALUTypeNonIDR:
- nonIDRPresent = true
- }
- }
-
- var dts time.Duration
-
- if m.currentSegment == nil {
- // skip groups silently until we find one with a IDR
- if !idrPresent {
- return nil
- }
-
- m.videoDTSExtractor = h264.NewDTSExtractor()
-
- var err error
- dts, err = m.videoDTSExtractor.Extract(nalus, pts)
- if err != nil {
- return fmt.Errorf("unable to extract DTS: %v", err)
- }
-
- m.startPCR = ntp
- m.startDTS = dts
- dts = 0
- pts -= m.startDTS
-
- // create first segment
- m.currentSegment = newMuxerVariantMPEGTSSegment(
- m.genSegmentID(),
- ntp,
- m.segmentMaxSize,
- m.videoTrack,
- m.audioTrack,
- m.writer)
- } else {
- if !idrPresent && !nonIDRPresent {
- return nil
- }
-
- var err error
- dts, err = m.videoDTSExtractor.Extract(nalus, pts)
- if err != nil {
- return fmt.Errorf("unable to extract DTS: %v", err)
- }
-
- dts -= m.startDTS
- pts -= m.startDTS
-
- // switch segment
- if idrPresent &&
- (dts-*m.currentSegment.startDTS) >= m.segmentDuration {
- m.currentSegment.finalize(dts)
- m.onSegmentReady(m.currentSegment)
- m.currentSegment = newMuxerVariantMPEGTSSegment(
- m.genSegmentID(),
- ntp,
- m.segmentMaxSize,
- m.videoTrack,
- m.audioTrack,
- m.writer)
- }
- }
-
- err := m.currentSegment.writeH264(
- ntp.Sub(m.startPCR),
- dts,
- pts,
- idrPresent,
- nalus)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (m *muxerVariantMPEGTSSegmenter) writeAAC(ntp time.Time, pts time.Duration, au []byte) error {
- if m.videoTrack == nil {
- if m.currentSegment == nil {
- m.startPCR = ntp
- m.startDTS = pts
- pts = 0
-
- // create first segment
- m.currentSegment = newMuxerVariantMPEGTSSegment(
- m.genSegmentID(),
- ntp,
- m.segmentMaxSize,
- m.videoTrack,
- m.audioTrack,
- m.writer)
- } else {
- pts -= m.startDTS
-
- // switch segment
- if m.currentSegment.audioAUCount >= mpegtsSegmentMinAUCount &&
- (pts-*m.currentSegment.startDTS) >= m.segmentDuration {
- m.currentSegment.finalize(pts)
- m.onSegmentReady(m.currentSegment)
- m.currentSegment = newMuxerVariantMPEGTSSegment(
- m.genSegmentID(),
- ntp,
- m.segmentMaxSize,
- m.videoTrack,
- m.audioTrack,
- m.writer)
- }
- }
- } else {
- // wait for the video track
- if m.currentSegment == nil {
- return nil
- }
-
- pts -= m.startDTS
- }
-
- err := m.currentSegment.writeAAC(ntp.Sub(m.startPCR), pts, au)
- if err != nil {
- return err
- }
-
- return nil
-}