Closed fastfading closed 2 years ago
我试试看,能不能按照这个要求写个example
I've uploaded this example,play mp4 with hls, please try to test. this cost so long ,I'm sorry for that
I'm just getting ready to develop fmp4, i hope i could finish it on August
try mp4ff, https://github.com/edgeware/mp4ff I find this lib support fmp4 very well, and the mp4 support version is quite new
I tried safari , it does not work ,
I put
http://127.0.0.1:19999/vod/xx.m3u8
in safari
it download m3u8 and first ts, but not played
VLC Report [0000000153e050b0] main libvlc: Running vlc with the default interface. Use 'cvlc' to use vlc without interface. [0000000153ec7a30] ts demux error: libdvbpsi error (PAT decoder): invalid section (section_syntax_indicator == 0) [0000000153ec7a30] ts demux error: libdvbpsi error (PSI decoder): TS discontinuity (received 2, expected 1) for PID 0 [0000000153ec7a30] ts demux error: libdvbpsi error (PAT decoder): invalid section (section_syntax_indicator == 0) [0000000153ec7a30] ts demux error: libdvbpsi error (PSI decoder): TS discontinuity (received 4, expected 3) for PID 0 [0000000153ec7a30] ts demux error: libdvbpsi error (PAT decoder): invalid section (section_syntax_indicator == 0) [0000000153ec7a30] ts demux error: libdvbpsi error (PSI decoder): TS discontinuity (received 6, expected 5) for PID 0
I am following example_demux_ts.go and example_mux_ts.go to do the remux recently.
it report the same error.
TS format seems not right
please upload your test file.
sry, I can not connect to this website.
use ffplay to test ,and show me the log output of ffplay
there is no error in ffplay ,ffplay plays well. but can not be played in quicktime vlc and safari use safari to connect mega.nz , refresh several times you could easy duplicate this case with any progressive mp4 (normal mp4)
please update to the latest version, and try to test again. some bugs about this issue has been fixed
thanks it works well in play mp4 with hls,
but not well in remux which I did a little change base on example_demux_ts.go and example_mux_ts.go
here is the code. I am not sure where is wrong example_demux_ts.go.zip
here is the script , index.html , server.go to help duplicate this case.
all.tar.zip
Test Data could be any ts file
find . -name "*.ts" | head -10 | xargs -I % gomedia/example/example_demux_ts % 1
In this example, the code is quite simple, did nothing but demux the ts , and remux it with its original pts. check the "bDump" related code.
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/yapingcat/gomedia/codec"
"github.com/yapingcat/gomedia/mpeg2"
)
func main() {
args := os.Args
tsfile := args[1]
tsFd, err := os.Open(tsfile)
if err != nil {
fmt.Println(err)
return
}
defer tsFd.Close()
ts2file := `re` + filepath.Base(tsfile)
ts2Fd, err := os.OpenFile(ts2file, os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
fmt.Println(err)
return
}
defer ts2Fd.Close()
muxer := mpeg2.NewTSMuxer()
muxer.OnPacket = func(pkg []byte) {
fmt.Println("write packet")
ts2Fd.Write(pkg)
}
pid_v := muxer.AddStream(mpeg2.TS_STREAM_H264)
pid_a := muxer.AddStream(mpeg2.TS_STREAM_AAC)
demuxer := mpeg2.NewTSDemuxer()
demuxer.OnFrame = func(cid mpeg2.TS_STREAM_TYPE, frame []byte, pts uint64, dts uint64) {
if cid == mpeg2.TS_STREAM_H264 {
muxer.Write(pid_v, frame, pts, dts)
} else if cid == mpeg2.TS_STREAM_AAC {
muxer.Write(pid_a, frame, pts, dts)
}
}
buf, _ := ioutil.ReadAll(tsFd)
fmt.Printf("read %d size\n", len(buf))
fmt.Println(demuxer.Input(bytes.NewReader(buf)))
/*
if ts file is large,please use bufio.NewReader
demuxer.Input(bufio.NewReader(tsFd))
*/
}
please use above code to test
with this code, ts file can be played with QuickTime ,ffplay and vlc
on apple device, Aud nalu should been inserted to the beginning of the vcl frame automatically.but sps/pps/sei is also Access Unit Delimiter . so you need merge sps/pps/sei into vcl nalu (on most case,you should merge into I Frame).
after fix vlc/safari play ts file failed most player plays well.
Firefox player seek very slow.
In android chrome/edge , video.js player when you do seek(拖动进度条) , there will be a weird behaviour .
in xiaomi edge player , there will be a very quick play video before seek to the right position
(拖动后,视频快速播放,直到跳到正确的位置)
in samsung chrome/edge player , there will be a image long ago, then jump to the right position.
(拖动后,先show 一个相对靠前位置的图片, 然后跳到正确位置)
Firefox 用 srs player 播放没问题 其他的场景,我身边没有这么多手机,暂时测试不了
我感觉不是码流的问题,是不是hls协议层面需要增加点东西,才能让seek比较自然
有和其他的 hls vod流 对比测试过吗 ?
seek异常的问题,现在怎样了?
可能是我数据源的问题, 我换了一个就好了。 这个ticket 可以关掉了
每次切ts (func onTs) 之前
都重复的demuxer.ReadHead()
这里能不能做些优化, 前面m3u8直接记录 mp4切片偏移位置
下次就不用读header了。 直接切片
参考 https://www.jianshu.com/p/5eb817ccdd1f 这个适配端主要做的工作就是根据index文件和m3u8文件,计算出真实数据位置,然后向服务器发送Range请求,
仅仅通过”mp4切片偏移位置“ 这个信息没办法构建ts文件的,因为需要知道一个帧的大小和时间戳
时间戳和帧大小的信息都存在moov中,当前只能通过demuxer.ReadHead()
来解析moov。
如果要优化有两种方式
ReadHead
。
一般可以用http重定向或者http Cookie 这两种方法标识这个请求属于哪个客户端最后,既然服务端要保存一些东西
,你就需要去释放他,这个也是你需要考虑的
还有 ReadHead
现在是有什么性能瓶颈吗?
如果文件放磁盘, nfs/samba 上 , 每次ReadHead 问题不大。 如果文件在s3 上, 每次只能range请求文件,每次读head就会有瓶颈。 我比较倾向 方案1, 为每个文件创建一个track samplist 结构, 放到内存里,并设置 ttl, 长时间不访问就删除。 第一次访问读到内存里, 后面人就从内存里获取。
方案2, 不理解为什么要为每个客户端 保存一个 demuxer 变量,我的理解是一个文件对不同客户端的索引结构是公用的。
方案2, 不理解为什么要为每个客户端 保存一个 demuxer 变量,我的理解是一个文件对不同客户端的索引结构是公用的。
demuxer 非线程安全。 内部有个read 索引,多个客户端共用同一个索引,会有问题
方案1
增加一个接口,获取demuxer 中的每个track 的samplelist
变量就可以了
然后读到的每一个视频/音频 sample 你需要做一些处理 参考ReadPacket
接口
我感觉有点麻烦,你可以尝试写写看
我这边想到一个办法,可以一次性把moov读到内存当中,使用bytes.Reader
,这样就可以避免多次io,只操作内存。
代价是内存中需要长期保存moov,在被点播的mp4比较多的时候对于内存大小有一定的要求
参考代码如下
package main
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"os"
"strconv"
"github.com/yapingcat/gomedia/mp4"
)
func mov_tag(tag [4]byte) uint32 {
return binary.LittleEndian.Uint32(tag[:])
}
func main() {
mp4FilePath := os.Args[1]
newTime, err := strconv.Atoi(os.Args[2])
if err != nil {
fmt.Println(err)
return
}
timeBuf := make([]byte, 4)
binary.BigEndian.PutUint32(timeBuf, uint32(newTime))
mp4Fd, err := os.OpenFile(mp4FilePath, os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
fmt.Println(err)
return
}
var moov []byte
Loop:
for err == nil {
basebox := mp4.BasicBox{}
_, err = basebox.Decode(mp4Fd)
if err != nil {
break
}
if basebox.Size < mp4.BasicBoxLen {
err = errors.New("mp4 Parser error")
break
}
fmt.Println(string(basebox.Type[:]))
tagName := mov_tag(basebox.Type)
switch tagName {
case mov_tag([4]byte{'m', 'o', 'o', 'v'}):
moov = make([]byte, basebox.Size)
mp4Fd.Seek(-1*mp4.BasicBoxLen, io.SeekCurrent)
io.ReadFull(mp4Fd, moov)
fmt.Println("Got moov box")
break Loop
default:
_, err = mp4Fd.Seek(int64(basebox.Size)-mp4.BasicBoxLen, io.SeekCurrent)
}
}
demuxer := mp4.CreateMp4Demuxer(bytes.NewReader(moov))
if infos, err := demuxer.ReadHead(); err != nil && err != io.EOF {
fmt.Println(err)
} else {
fmt.Printf("%+v\n", infos)
}
mp4info := demuxer.GetMp4Info()
fmt.Printf("%+v\n", mp4info)
mp4Fd.Seek(0, io.SeekStart)
demuxer.RebindIo(mp4Fd)
vfile, err := os.OpenFile("e.h264", os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
fmt.Println(err)
return
}
defer vfile.Close()
afile, err := os.OpenFile("e.aac", os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
fmt.Println(err)
return
}
defer afile.Close()
for {
pkg, err := demuxer.ReadPacket()
if err != nil {
fmt.Println(err)
break
}
fmt.Printf("track:%d,cid:%+v,pts:%d dts:%d\n", pkg.TrackId, pkg.Cid, pkg.Pts, pkg.Dts)
if pkg.Cid == mp4.MP4_CODEC_H264 {
vfile.Write(pkg.Data)
} else if pkg.Cid == mp4.MP4_CODEC_AAC {
afile.Write(pkg.Data)
}
}
fmt.Println(err)
//mp4Fd.Seek(int64(basebox.Size)-mp4.BasicBoxLen, io.SeekCurrent)
return
}
demuxer 需要增加RebindIo接口
func (demuxer *MovDemuxer) RebindIo(r io.ReadSeeker) {
demuxer.reader = r
}
对于一个mp4文件你只需要在内存中保存他的moov box 就可以
请仔细看 https://www.jianshu.com/p/5eb817ccdd1f 是不是增加 seg_offset seg_size 就能解决这个问题
不能. seg_offset, seg_size的目的是让你能通过一次read 操作就把这个切片的所有sample从mp4文件中读取出来 但是这个还不够,你还需要知道在这个切片中每个sample的大小和时间戳信息。其实就是等于你需要知道mp4整个sample list。
这个samples_变量可能就是整个mp4文件的samplelist
我想问一下,为什么不直接使用http + mp4文件点播的方式呢?
我也想, security 不允许, 说 html5 不安全, 太容易下载。 太操蛋了。 业务需求,既要 web online playback , 又要web download。 所以存储下来只能是 progressive mp4 , 不能是fmp4,否则很多player , 编辑软件都不支持。 video tag 里面是 m3u8 用blob 字段隐藏
理解
这个过程可以反过来吗? 用另外一个思路解决问题 我存一个 fmp4 和m3u8 用于 play back , download 的时候在内存中转换成 普通(progressive) mp4 https://github.com/edgeware/mp4ff/issues/162
这个可以。 但是目前我想到有两个问题 1.各个平台 各个浏览器 对与hls + fmp4 这种方式支持度怎么样? 2.使用fmp4 必然会有大量的小文件,你们的存储能够支持吗?
我修改了代码,让ReadHead的时候 io操作的次数比较少。觉得可行,你可以fork gomedia,自己定制化修改
fmp4 playback 这条路我们已经验证过了 各个平台浏览器都能支持, fmp4 可以是range 模式的, 从头到尾一个mp4 搞定, m3u8 文件里range 指定分片
我们不确定的是这个逆向转化过程是否可行 最简单的方式是,从s3 download 下来整个fmp4 , 然后ffmpeg 转换 mp4,然后让用户下载。 但这个过程太花时间, 最好有in memory 的边下,边转换过程
不行,只有在写入最后一帧之后才能 设置mdat box的size。只能mp4转完了,才能让用户下载
我修改了代码,让ReadHead的时候 io操作的次数比较少。觉得可行,你可以fork gomedia,自己定制化修改
有没有办法(脚本) 我做下, performance 测试 模拟随机请求。
对随机性没要求,可以用脚本起多个ffmpeg 进程拉 hls流。用ffmpeg 切片按序下载。不是随机下载
如果你对码流数据不关心,简单写个http客户端就可以了,hls切片url按照一定规则生成,http测试客户端就按照这个规则生成url 随机下载切片就可以了
progressive.mp4 is a mp4 file on local disk or nfs mount. hls player try to play this file with hls protocol.
example server will read mp4, generate m3u8 and stream it in mpeg-ts format. reference https://www.jianshu.com/p/5eb817ccdd1f