背景
承接这篇文章:ffmpeg的安装与简单使用
有一些视频不是被广泛支持的格式,在浏览器上无法播放。在H5时代,MSE是被广泛应用的一种媒体播放技术。所以我的思路是,在浏览器页面上点击文件时,前端打开websocket,发送请求媒体文件,后端接到请求后使用ffmpeg开始转码,然后不落盘直接pipe:到MSE,然后前端就能使用MSE进行播放了。
实现
开启websocket
前端:
使用socket-io:https://socket.io/docs/v4/server-installation/
import { io } from "socket.io-client";
var socket = io({
transports: ["websocket", "polling"],
closeOnBeforeunload: false,
rememberUpgrade: true,
withCredentials: true,
timeout: 43200000,
});
socket.io.engine.on("connection_error", (err) => {
console.log(err.req); // the request object
console.log(err.code); // the error code, for example 1
console.log(err.message); // the error message, for example "Session ID unknown"
console.log(err.context); // some additional error context
});
socket.on('connect', function () {
console.log("socket connect")
});
socket.on("disconnect", (err) => {
console.log(err)
console.log("socket disconnect");
// socket.connect();
// socket.emit('movieStream', {
// moviePath: moivePathCurrent.current + "/" + event.target.innerHTML,
// ss: ss,
// t: t,
// });
});
socket.on("connect_error", (err) => {
console.log(`connect_error due to`);
console.log(err.req + " | " + err.message + " | " + err.context)
socket.disconnect();
console.log("change to polling")
socket.io.opts.transports = ["polling"];
socket.connect();
});
后端:
使用flask-socketio:https://flask-socketio.readthedocs.io/en/latest/intro.html#in...
from flask_socketio import SocketIO
from flask_socketio import send, emit
app = Flask(__name__)
app.config['SECRET_KEY'] = '{your unique secret}'
app.logger.setLevel(logging.INFO)
socketio = SocketIO(app, cors_allowed_origins="*", ping_timeout=43200000)
@socketio.on('connect')
def handle_message(data):
print('connnected')
emit("topic", data)
开启MSE
官网:https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
JS:
const video = document.querySelector('video');
const mime = 'video/mp4; codecs="avc1.64001F, mp4a.40.2"';
var mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', onSourceOpen);
function onSourceOpen(_) {
}
HTML:
<video id="movieScreen" controls width="70%" preload="auto">
{/* <source src={dirpath + "/TV.Episodes/Harley.Quinn/HarleyQuinn.S01E01"} type="video/mp4" /> */}
{/* <source src={"/movie/my_video_manifest.mpd"}></source> */}
Sorry, NO moive available now.
</video>
python后端转码
使用subprocess:https://docs.python.org/3/library/subprocess.html
该段内容放在后端socket处理函数中,如上述的def handle_message(data):
ffmpeg_command = [
"ffmpeg",
"-ss", str(ss),
"-t", str(t),
"-i", video_path,
"-vn",
"-f", "webm",
"-c:a", "libvorbis",
"pipe:",
]
print(ffmpeg_command)
pipe = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
outs, errs = pipe.communicate()
pipe.wait()
if outs:
print("successfully this segment!")
emit("audio", outs)
else:
print("error: ",errs)
问题与回答
基本的实现应该照搬上面的代码,可能稍微自适应修改一下就可以了。
接下来的问题是:
- websocket该传什么内容?
- 使用ffmpeg该转什么格式?
- MSE该怎么使用?
- 为啥分割后的视频在MSE拼接的时候,段与段之间会有短暂的停顿?
回答
1. websocket该传什么内容?
websocket传送的内容取决于MSE接受什么数据。根据官网:https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer...:
The appendBuffer() method of the SourceBuffer interface appends media segment data from an ArrayBuffer, a TypedArray or a DataView object to the SourceBuffer.
我们如果使用MSE。我们应该使用ArrayBuffer
或者TypedArray
或者DataView object
2. 使用ffmpeg该转什么格式?
这个同样应该要问MSE能够播放什么格式的音视频:
https://developer.mozilla.org/en-US/docs/Web/API/Media_Source...:
Currently, MP4 containers with H.264 video and AAC audio codecs have support across all modern browsers, while others don't.
https://developer.mozilla.org/en-US/docs/Web/API/Media_Source...:
While browser support for the various media containers with MSE is spotty, usage of the H.264 video codec, AAC audio codec, and MP4 container format is a common baseline.
所以,我尽可能将视频转换成为MP4容器,H.264视频,AAC音频
3. MSE该怎么使用?
其实比较简单的,可以参考本篇文章的章节:开启MSE
从后端传来的数据,通过appendBuffer
方法塞到SourceBuffer
中去。
通过addSourceBuffer
方法,创建新的SourceBuffer
到MediaSource
中去。
通过URL.createObjectURL()
方法,将video
和MediaSource
建立联系。
注意:MSE多个视频轨道只能播放SourceBufferList
排名第一的那个轨道
4. 为啥分割后的视频在MSE拼接的时候,段与段之间会有短暂的停顿?
说来非常惭愧,这个问题困扰了我起码2个月。尝试了无数的方式想要消除停顿的0.1秒。 当然现在说起就是上面的一句话,而且这些失败的尝试大概率也不会写在文章中,一将成名万骨枯!
原因是:
使用ffmpeg
切割的时候,视频和音频并不会完全按照指定的时间间隔分割,会多出来或者少一点。尤其当需要重新编码的情况下,尤其不可避免。
ffmpeg
切割通常使用的就是如下命令:
ffmpeg -ss {开始秒数/时间点} -t {切割多长} -i {input} {output}
ffmpeg -ss {开始秒数/时间点} -to {结束秒数/时间点} -i {input} {output}
-ss
-t
-to
参数当然是作为input的参数最好。但是因为视频存在关键帧或者各种格式之间不不可调和的问题,音频不知道啥原因。可以看到截取后,音视频都有几ms的延长:
track 2是音频,上面的信息就是视频的。关注点在duration with fragments
,视频多了17ms,音频多了33ms。
为了得到图上的数据,你需要安装Bento4
:https://www.bento4.com/downloads/。提供了很多分析MP4格式的工具。
我的解决办法:
在后台不直接将原视频切割成小块。而是,分成两轨,视频轨和音频轨。因为视频轨可以在ffmpeg参数中加上-filter:v fps=30
来指定framerate,从而让ffmpeg自动抽帧,获得指定时间的视频片段。相信我,在浏览器上播放根本感觉不出来抽帧差别。
音频的话,可以不分段,或者直接分一个很长的段,比如600s。从而减少误差。
送到MSE的时候,分别创建一个SourceBuffer
(轨道,track),一个用来放视频,一个用来放音频。我音频后来采用的webm容器,编码libvorbis。
前端js:
const audioSourceBuffer = mediaSource.addSourceBuffer('audio/webm;codecs=vorbis');
const sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.64001F, mp4a.40.2"');
音频ffmpeg:
ffmpeg_command = [
"ffmpeg",
"-ss", str(ss),
"-t", str(t),
"-i", video_path,
"-vn",
"-f", "webm",
"-c:a", "libvorbis",
"pipe:",
]
视频ffmpeg:
ffmpeg_command = [
"ffmpeg",
"-an",
"-ss", str(ss),
"-t", str(t),
"-i", video_path,
"-f", "mp4",
"-c:v", "libx264",
"-filter:v", "fps=30",
"-movflags", "frag_keyframe+default_base_moof+empty_moov",
"pipe:",
]
后记
两个月的精华总结下来就只有上面这么多。
部分还能找得到的有用的参考:
MDN上关于MSE的示例:https://github.com/nickdesaulniers/netfix/blob/gh-pages/demo/bufferWhenNeeded.html
MSE支持的mimetype:https://chromium.googlesource.com/external/w3c/web-platform-t...
mimetype集合:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_H...
如何就转成MSE支持的格式:https://developer.mozilla.org/en-US/docs/Web/API/Media_Source...
如何使用DASH:https://developer.mozilla.org/en-US/docs/Web/Media/DASH_Adapt...
ffmpeg改变framerate:https://trac.ffmpeg.org/wiki/ChangingFrameRate
ffmpeg合并多个视频:https://trac.ffmpeg.org/wiki/Concatenate
dash.js:https://github.com/Dash-Industry-Forum/dash.js/
MSE示例:https://www.cnblogs.com/Tomato-wang/p/15353705.html
解决之道受到这个回答的启发:https://stackoverflow.com/questions/64839836/playing-one-of-multiple-audio-tracks-in-sync-with-a-video
一些知识点:https://developer.huawei.com/consumer/cn/forum/topic/02024325...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。