本文主要是介绍FFmpeg支持Cronet(Chromium网络库),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
FFmpeg支持Cronet
- 1 背景
- 2 代码
- 3 Cronet使用介绍
- 3.1 接口
- 3.2 组件及工作流程
- 3.3 Native开发
- 3.3.1 创建并配置Cronet_Engine
- 3.3.2 创建Cronet_UrlRequestCallback
- 3.3.3 创建Cronet_Executor
- 3.3.4 创建并发起Cronet_UrlRequest请求
- 3.3.5 结束、销毁请求
- 4 FFmpeg集成Cronet
- 5 编译Cronet
- 5.1 Windows
- 5.1.1 下载Chromium
- 5.1.2 生成工程
- 5.1.3 编译
- 5.2 Android
- 5.2.1 下载Chromium
- 5.2.2 生成工程
- 5.2.3 编译
- 5.3 iOS
- 5.3.1 下载Chromium
- 5.3.2 生成工程
- 5.3.3 编译
- 6 编译FFmpeg
- 6.1 获取支持Cronet的FFmpeg代码
- 6.2 编译
- 6.2.1 Windows
- 6.2.1.1 安装依赖
- 6.2.1.2 处理cronet库的符号
- 6.2.1.3 编译
- 6.2.2 Android
- 6.2.2.1 编译环境
- 6.2.2.2 编译
- 6.2.3 iOS
- 6.2.3.1 编译环境
- 6.2.3.2 安装gas-preprocessor和yasm
- 6.2.3.3 编译
- 7 测试
- 8 关于QUIC服务端
1 背景
之前在博文《FFmpeg支持QUIC》中介绍了FFmpeg支持谷歌QUIC协议的方法,简单说就是在Chromium中自定义一个模块,把QUIC的接口封装成FFmpeg Protocol插件需要的接口形式。但是这种办法归根结底只是实验性质,实际用到生产环境中,往往QUIC(基于UDP)有可能不通,需要一定的回退策略。
本文将介绍FFmpeg集成Chromium自带网络库Cronet的方法,在Cronet中有使用QUIC的完整策略,可以参考我的博文《Chromium QUIC逻辑》。当然使用Cronet并不仅仅可以使用QUIC,它还支持HTTP/HTTP2,甚至可以支持WebSocket,如果要防止DNS劫持,可以启用其基于异步DNS的DOH(DNS Over HTTP)功能。FFmpeg内部实现的HTTP协议仅支持HTTP/1.1,如果考虑升级FFmpeg的HTTP协议,跨平台的Cronet是一个比较好的选择。
目前Cronet已经在Google的YouTube、Google App等产品中被大量使用,此外国内的各大厂商如腾讯、新浪微博、百度、哔哩哔哩等都陆续使用了Cronet。这段时间主要就是在折腾这个库,我们的产品也已经全面上线Cronet。
2 代码
FFmepg代码:https://github.com/sonysuqin/FFmpeg-Quic-Cronet
3 Cronet使用介绍
3.1 接口
上图描述了Cronet网络库的基本层次结构,在Chromium内核的基础上封装了Cronet Native层,针对不同的平台分别封装了C层、Android、iOS的接口。具体可以参考谷歌官方Cronet接口参考。
3.2 组件及工作流程
上图是Cronet网络库接口的基本工作流程,适用于所有平台。
接口基本组件包括:
序号 | 组件 | 功能 |
---|---|---|
1 | CronetEngine | Cronet引擎,存储Cronet的一些全局数据,例如代理配置、HTTP缓存、DNS缓存等。每个HTTP请求都基于CronetEngine这个上下文,不同的HTTP请求可以通过同一个CronetEngine共享各种缓存。一个APP最好只创建一个CronetEngine。 |
2 | CronetRequest | 一次Cronet请求,封装该请求的方法、数据、状态等。 |
3 | CronetExecutor | CronetRequest的运行环境,由APP实现,是Cronet底层与APP交互的通道,通常是一个线程。 |
4 | CronetCallback | Cronet异步接口的回调对象,每个CronetRequest必须绑定一个CronetCallback对象才能获得通知。 |
工作流程:
- APP创建一个全局的CronetEngine;
- APP创建一个全局的CronetExecutor,用于执行各种Task;
- APP为一次请求创建一个CronetCallback;
- APP从CronetEngine、CronetExecutor、CronetCallback创建一个CronetRequest;
- APP发起CronetRequest请求,CronetRequest请求内部的所有Task都在CronetExecutor中执行;
- CronetRequest的结果通过绑定的CronetCallback回调上报到APP。
3.3 Native开发
基于C层的接口,适用于在所有平台上开发C/C++的程序,Android和iOS版本的开发本文不涉及。
3.3.1 创建并配置Cronet_Engine
Cronet_EnginePtr CreateCronetEngine() {// 创建Cronet_Engine对象, 每个APP最好只创建一个Cronet_Engine对象.Cronet_EnginePtr cronet_engine = Cronet_Engine_Create();// 创建Cronet_EngineParams对象.Cronet_EngineParamsPtr engine_params = Cronet_EngineParams_Create();// 设置User agent.Cronet_EngineParams_user_agent_set(engine_params, "CronetTest/1");// 使能HTTP2.Cronet_EngineParams_enable_http2_set(engine_params, true);// 使能HTTP3/QUIC.Cronet_EngineParams_enable_quic_set(engine_params, true);// 设置QUIC的空闲超时,在已经协商成功的QUIC通道上请求数据,如果// 超过5秒未收到响应,则超时并自动回滚到HTTP,如果在接收QUIC数// 据的过程中UDP不可用,则5秒后上报超时失败。注意QUIC的参数通过// JSON串设置,对全平台适用。Cronet_EngineParams_experimental_options_set(engine_params,"{\"QUIC\":{\"idle_connection_timeout_seconds\":5}}");// 启动Cronet_Engine.Cronet_Engine_StartWithParams(cronet_engine, engine_params);// 销毁Cronet_EngineParams对象.Cronet_EngineParams_Destroy(engine_params);return cronet_engine;
}
3.3.2 创建Cronet_UrlRequestCallback
Cronet_UrlRequestCallback是Cronet所有数据、结果上报的唯一方式,因为Cronet Native层只提供了异步接口,每个Cronet_UrlRequest请求必须绑定一个Cronet_UrlRequestCallback对象才能获得通知。
原型:
CRONET_EXPORT Cronet_UrlRequestCallbackPtr Cronet_UrlRequestCallback_CreateWith(Cronet_UrlRequestCallback_OnRedirectReceivedFunc OnRedirectReceivedFunc,Cronet_UrlRequestCallback_OnResponseStartedFunc OnResponseStartedFunc,Cronet_UrlRequestCallback_OnReadCompletedFunc OnReadCompletedFunc,Cronet_UrlRequestCallback_OnSucceededFunc OnSucceededFunc,Cronet_UrlRequestCallback_OnFailedFunc OnFailedFunc,Cronet_UrlRequestCallback_OnCanceledFunc OnCanceledFunc,Cronet_UrlRequestCallback_OnMetricsCollectedFunc OnMetricsCollectedFunc);
回调 | 作用 |
---|---|
Cronet_UrlRequestCallback_OnRedirectReceivedFunc | 接收到重定向的通知,APP可以决定进行重定向或者取消请求。 |
Cronet_UrlRequestCallback_OnResponseStartedFunc | 开始接收数据的通知,APP可以在此回调中获取HTTP响应头,并开始读取响应数据。 |
Cronet_UrlRequestCallback_OnReadCompletedFunc | 一次读取结束的通知,APP可以获取一次读取的数据,并发起下一次读取操作。 |
Cronet_UrlRequestCallback_OnSucceededFunc | 一次请求成功结束的通知,但是不能代表一次业务请求的成功,APP需要判断响应码并处理响应的数据。 |
Cronet_UrlRequestCallback_OnFailedFunc | 一次请求失败的通知,可能原因是网络错误,并不会因为服务端返回400、500错误调用。 |
Cronet_UrlRequestCallback_OnCanceledFunc | 一次请求被成功取消的通知。 |
Cronet_UrlRequestCallback_OnMetricsCollectedFunc | 一次请求的度量通知,以JSON格式上报HTTP请求的各个阶段:包括DNS请求、连接、握手、收发数据等阶段消耗的时间。 |
该方法需要传入若干回调,为了与某个APP的对象建立关系,可以调用下面的方法:
CRONET_EXPORT void Cronet_UrlRequestCallback_SetClientContext(Cronet_UrlRequestCallbackPtr self, Cronet_ClientContext client_context);
可以看到上述每个回调都会携带Cronet_UrlRequestCallbackPtr self参数,APP可以从回调本身获得绑定的APP的对象,调用下面的方法:
CRONET_EXPORT Cronet_ClientContext Cronet_UrlRequestCallback_GetClientContext(Cronet_UrlRequestCallbackPtr self);
当然从每个回调携带的Cronet_UrlRequestPtr request参数也获得了每个绑定的HTTP请求。
注意:这些回调都不是直接在底层的线程直接调用到APP,而是通过Cronet_Executor来调用。
3.3.3 创建Cronet_Executor
Cronet_Executor是Cronet底层与APP交互的通道,Cronet底层的某些任务(例如回调)会通过Cronet_Executor调用,Cronet_Executor可以选择在自己的线程中调用这些任务,达到与底层线程的隔离。
CRONET_EXPORT Cronet_ExecutorPtr Cronet_Executor_CreateWith(Cronet_Executor_ExecuteFunc ExecuteFunc);
上面的方法创建一个Cronet_Executor,需要传入一个回调:
typedef void (*Cronet_Executor_ExecuteFunc)(Cronet_ExecutorPtr self, Cronet_RunnablePtr command);
该回调会在底层线程调用,通知APP有一个Cronet_RunnablePtr任务需要调度执行,通常APP需要将任务Cronet_RunnablePtr放到独立的线程执行,最后APP负责删除该Cronet_RunnablePtr 任务。
可以通过以下两个方法设置、获取Cronet_Executor绑定的某个APP的自定义对象。
CRONET_EXPORT void Cronet_Executor_SetClientContext(Cronet_ExecutorPtr self, Cronet_ClientContext client_context);
CRONET_EXPORT Cronet_ClientContext Cronet_Executor_GetClientContext(Cronet_ExecutorPtr self);
不需要每个请求都创建一个Cronet_Executor,例如,可以只创建一个Cronet_Executor,所有请求都共享一个Cronet_Executor。
3.3.4 创建并发起Cronet_UrlRequest请求
Cronet_UrlRequestPtr PerformRequest(const std::string& url, // url.Cronet_EnginePtr cronet_engine, // Cronet_Engine对象.Cronet_ExecutorPtr executor, // Cronet_Executor对象.Cronet_UrlRequestCallbackPtr callback) { // Cronet_UrlRequestCallback对象.// 创建Cronet_UrlRequest对象.Cronet_UrlRequestPtr request = Cronet_UrlRequest_Create();// 创建Cronet_UrlRequestParams对象.Cronet_UrlRequestParamsPtr request_params = Cronet_UrlRequestParams_Create();// 设置GET方法.Cronet_UrlRequestParams_http_method_set(request_params, "GET");// 用上述参数初始化请求.Cronet_RESULT ret = Cronet_UrlRequest_InitWithParams(request, // Cronet_UrlRequest对象.cronet_engine, // Cronet_Engine对象.url.c_str(), // url.request_params, // Cronet_UrlRequestParams对象.callback, // Cronet_UrlRequestCallback对象.executor); // Cronet_Executor对象.// 销毁Cronet_UrlRequestParams对象.Cronet_UrlRequestParams_Destroy(request_params);// 判断Cronet_UrlRequest_InitWithParams结果.if (ret != Cronet_RESULT_SUCCESS) {std::cout << "Cronet_UrlRequest_InitWithParams error:" << ret;return NULL;}// 启动Cronet_UrlRequest请求.ret = Cronet_UrlRequest_Start(request);// 判断Cronet_UrlRequest_Start结果.if (ret != Cronet_RESULT_SUCCESS) {std::cout << "Cronet_UrlRequest_Start error:" << ret;return NULL;}return request;
}
3.3.5 结束、销毁请求
APP必须保证在Cronet_UrlRequestCallback_OnSucceededFunc、Cronet_UrlRequestCallback_OnFailedFunc、Cronet_UrlRequestCallback_OnCanceledFunc这些回调调用之后才调用以下方法销毁请求对象以保证安全。
CRONET_EXPORT void Cronet_UrlRequest_Destroy(Cronet_UrlRequestPtr self);
在请求还没有结束前的任何时刻,都可以调用Cronet_UrlRequest_Cancel方法来取消一个请求,然后在Cronet_UrlRequestCallback_OnCanceledFunc回调中调用Cronet_UrlRequest_Destroy释放请求。
将所有调用都POST到Cronet_Executor线程中执行可以保证线程安全性。
4 FFmpeg集成Cronet
跟《FFmpeg支持QUIC》中提到的方法一样,这里在FFmpeg内部增加了一个协议cronet,实现FFmpeg协议要求的基本方法:
const URLProtocol ff_cronet_protocol = {.name = "cronet",.url_open = cronet_open,.url_close = cronet_close,.url_read = cronet_read,.url_write = cronet_write,.url_seek = cronet_seek,.priv_data_size = sizeof(CronetContext),.priv_data_class = &cronet_context_class,.flags = URL_PROTOCOL_FLAG_NETWORK,.default_whitelist = "cronet,cronets"
};
调用上一节介绍的C层接口,并将Cronet异步接口转成FFmpeg要求的同步接口。
在Android下面,APP层(Java)和Native层FFmpeg依赖同一个Cronet动态库,在iOS下,APP层(OC)和Native层FFmpeg依赖同一个Cronet.framework。
具体细节请直接获取、阅读、测试代码:
git clone https://github.com/sonysuqin/FFmpeg-Quic-Cronet.git
git checkout -b 4.1.cronet remotes/origin/4.1.cronet
5 编译Cronet
5.1 Windows
5.1.1 下载Chromium
按照chromium的官方编译文档,配置环境并下载chromium代码。注意checkout到较新的稳定版tag。
5.1.2 生成工程
在chromium/src下执行:
gn gen out/Debug --args="is_debug=true is_component_build=false target_cpu=\"x86\""
这里可以决定是产生Debug版还是Release版。
5.1.3 编译
在chromium/src下执行:
ninja -C out\Debug cronet
在chromium/src/out/Debug目录下会生成:
- cronet.73.0.3683.75.dll
- cronet.73.0.3683.75.dll.lib
- cronet.73.0.3683.75.dll.pdb
5.2 Android
5.2.1 下载Chromium
按照chromium的官方编译文档,配置环境并下载chromium代码。注意checkout到较新的稳定版tag。
5.2.2 生成工程
在chromium/src下执行:
./components/cronet/tools/cr_cronet.py gn --release --out_dir=out/Release
这里可以决定是产生Debug版还是Release版。
5.2.3 编译
在chromium/src下执行:
ninja -C out\Release cronet_package
在chromium/src/out/Release目录下会生成cronet目录:
cronet
|-- api_version.txt
|-- AUTHORS
|-- cronet_api.jar
|-- cronet_api-src.jar
|-- cronet_impl_common_java.jar
|-- cronet_impl_common_java-src.jar
|-- cronet_impl_common_proguard.cfg
|-- cronet_impl_native_java.jar
|-- cronet_impl_native_java-src.jar
|-- cronet_impl_native_proguard.cfg
|-- cronet_impl_platform_java.jar
|-- cronet_impl_platform_java-src.jar
|-- cronet_impl_platform_proguard.cfg
|-- cronet-sample-src.jar
|-- javadoc
|-- libs
|-- LICENSE
|-- README.md.html
|-- res
|-- symbols
|-- test
`-- VERSION
jar包为Android使用的库文件,lib目录下为Native动态库,symbols目录下为Native动态库对应的符号文件。
5.3 iOS
5.3.1 下载Chromium
按照chromium的官方编译文档,配置环境并下载chromium代码。注意checkout到较新的稳定版tag。
5.3.2 生成工程
在chromium/src下执行:
./components/cronet/tools/cr_cronet.py -i gn --release --out_dir=out/Release
这里可以决定是产生Debug版还是Release版。
5.3.3 编译
在chromium/src下执行:
ninja -C out\Release cronet_package
在chromium/src/out/Release目录下会生成:
- Cronet.framework,动态库;
- Static/Cronet.framework,静态库;
- Cronet.dSYM,用于线上崩溃问题的堆栈解析。
6 编译FFmpeg
6.1 获取支持Cronet的FFmpeg代码
git clone https://github.com/sonysuqin/FFmpeg-Quic-Cronet.git
git checkout -b 4.1.cronet remotes/origin/4.1.cronet
6.2 编译
6.2.1 Windows
6.2.1.1 安装依赖
需要安装mingw32/msys2环境,然后安装GCC、SDL2等FFmpeg通常需要依赖的工具,主要参考了以下这些网页:
《msys2和SDL2环境搭建》
《windows下编译FFMPEG篇》
《Windows10平台编译ffmpeg 4.0.2,生成ffplay》
6.2.1.2 处理cronet库的符号
使用mingw32,在chromium/src/out/Debug目录下,执行:
gendef cronet.73.0.3683.75.dll
dlltool --kill-at -d cronet.73.0.3683.75.def --dllname cronet.73.0.3683.75.dll -l cronet.73.0.3683.75.a
注意自己修改cronet的版本号。
6.2.1.3 编译
在FFmpeg目录下,执行:
mkdir build_debug
cd build_debug
../configure --prefix=/d/log/ffmpeg_debug --disable-static --enable-shared \--enable-debug=3 --disable-optimizations --disable-stripping \--enable-gpl --enable-version3 --enable-sdl --disable-mmx \--arch=x86 --enable-libcronet \--extra-cflags="-I/d/work/google/chromium/src/components/cronet/native/include -I/d/work/google/chromium/src/components/cronet/native/generated" \--extra-ldflags=-L/d/work/google/chromium/src/out/Debug \--extra-libs=-lcronet.73.0.3683.75
make && make install
6.2.2 Android
6.2.2.1 编译环境
软件 | 版本 |
---|---|
Ubuntu | 16.04 |
NDK | r17c |
6.2.2.2 编译
在FFmpeg目录下,执行:
./build_android.sh
可以在脚本中自行指定输出目录,默认在当前目录的android目录下。
6.2.3 iOS
6.2.3.1 编译环境
软件 | 版本 |
---|---|
macos | 10.14 |
xcode | 10.3 |
6.2.3.2 安装gas-preprocessor和yasm
参考FFmpeg-iOS-build-script,安装gas-preprocessor和yasm。
6.2.3.3 编译
在FFmpeg目录下,执行:
./build_ios.sh
可以在脚本中自行指定输出目录,默认在当前上级目录的thin目录下。
7 测试
Windows端可以使用ffplay直接进行测试,用mingw32进入FFmpeg的输出目录,执行:
./ffplay cronets://roblin.cn/video/mao.mp4
Debug版需要同时拷贝mingw环境的依赖库到FFmpeg输出目录下,例如/mingw32/bin下的dll。
bin
|-- avcodec-58.dll
|-- avcodec.lib
|-- avdevice-58.dll
|-- avdevice.lib
|-- avfilter-7.dll
|-- avfilter.lib
|-- avformat-58.dll
|-- avformat.lib
|-- avutil-56.dll
|-- avutil.lib
|-- cronet.73.0.3683.75.dll
|-- ffmpeg.exe
|-- ffplay.exe
|-- ffprobe.exe
|-- libatomic-1.dll
|-- libbz2-1.dll
|-- libcharset-1.dll
|-- libgcc_s_dw2-1.dll
|-- libgmp-10.dll
|-- libgmpxx-4.dll
|-- libgomp-1.dll
|-- libiconv-2.dll
|-- liblsmash-2.dll
|-- liblzma-5.dll
|-- libminizip-1.dll
|-- libopenal-1.dll
|-- libquadmath-0.dll
|-- libssp-0.dll
|-- libstdc++-6.dll
|-- libvulkan-1.dll
|-- libwinpthread-1.dll
|-- libx264-157.dll
|-- postproc-55.dll
|-- postproc.lib
|-- SDL2.dll
|-- swresample-3.dll
|-- swresample.lib
|-- swscale-5.dll
|-- swscale.lib
`-- zlib1.dll
其他平台的测试需要编写程序,调用FFmpeg的avformat API,传入URL:cronets://roblin.cn/video/mao.mp4。
如果想用浏览器测试QUIC可以直接点播放。
8 关于QUIC服务端
目前在roblin.cn上部署了一个我们自己开发的支持QUIC的Nginx,代码地址:https://github.com/evansun922/nginx-quic。
这篇关于FFmpeg支持Cronet(Chromium网络库)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!