FFmpeg支持Cronet(Chromium网络库)

2024-01-23 07:08

本文主要是介绍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网络库接口的基本工作流程,适用于所有平台。

接口基本组件包括:

序号组件功能
1CronetEngineCronet引擎,存储Cronet的一些全局数据,例如代理配置、HTTP缓存、DNS缓存等。每个HTTP请求都基于CronetEngine这个上下文,不同的HTTP请求可以通过同一个CronetEngine共享各种缓存。一个APP最好只创建一个CronetEngine。
2CronetRequest一次Cronet请求,封装该请求的方法、数据、状态等。
3CronetExecutorCronetRequest的运行环境,由APP实现,是Cronet底层与APP交互的通道,通常是一个线程。
4CronetCallbackCronet异步接口的回调对象,每个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 编译环境
软件版本
Ubuntu16.04
NDKr17c
6.2.2.2 编译

在FFmpeg目录下,执行:

./build_android.sh

可以在脚本中自行指定输出目录,默认在当前目录的android目录下。

6.2.3 iOS

6.2.3.1 编译环境
软件版本
macos10.14
xcode10.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网络库)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/635596

相关文章

Debian 13升级后网络转发等功能异常怎么办? 并非错误而是管理机制变更

《Debian13升级后网络转发等功能异常怎么办?并非错误而是管理机制变更》很多朋友反馈,更新到Debian13后网络转发等功能异常,这并非BUG而是Debian13Trixie调整... 日前 Debian 13 Trixie 发布后已经有众多网友升级到新版本,只不过升级后发现某些功能存在异常,例如网络转

基于Java和FFmpeg实现视频压缩和剪辑功能

《基于Java和FFmpeg实现视频压缩和剪辑功能》在视频处理开发中,压缩和剪辑是常见的需求,本文将介绍如何使用Java结合FFmpeg实现视频压缩和剪辑功能,同时去除数据库操作,仅专注于视频处理,需... 目录引言1. 环境准备1.1 项目依赖1.2 安装 FFmpeg2. 视频压缩功能实现2.1 主要功

Python开发简易网络服务器的示例详解(新手入门)

《Python开发简易网络服务器的示例详解(新手入门)》网络服务器是互联网基础设施的核心组件,它本质上是一个持续运行的程序,负责监听特定端口,本文将使用Python开发一个简单的网络服务器,感兴趣的小... 目录网络服务器基础概念python内置服务器模块1. HTTP服务器模块2. Socket服务器模块

Go语言网络故障诊断与调试技巧

《Go语言网络故障诊断与调试技巧》在分布式系统和微服务架构的浪潮中,网络编程成为系统性能和可靠性的核心支柱,从高并发的API服务到实时通信应用,网络的稳定性直接影响用户体验,本文面向熟悉Go基本语法和... 目录1. 引言2. Go 语言网络编程的优势与特色2.1 简洁高效的标准库2.2 强大的并发模型2.

Linux中压缩、网络传输与系统监控工具的使用完整指南

《Linux中压缩、网络传输与系统监控工具的使用完整指南》在Linux系统管理中,压缩与传输工具是数据备份和远程协作的桥梁,而系统监控工具则是保障服务器稳定运行的眼睛,下面小编就来和大家详细介绍一下它... 目录引言一、压缩与解压:数据存储与传输的优化核心1. zip/unzip:通用压缩格式的便捷操作2.

Python中对FFmpeg封装开发库FFmpy详解

《Python中对FFmpeg封装开发库FFmpy详解》:本文主要介绍Python中对FFmpeg封装开发库FFmpy,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、FFmpy简介与安装1.1 FFmpy概述1.2 安装方法二、FFmpy核心类与方法2.1 FF

k8s上运行的mysql、mariadb数据库的备份记录(支持x86和arm两种架构)

《k8s上运行的mysql、mariadb数据库的备份记录(支持x86和arm两种架构)》本文记录在K8s上运行的MySQL/MariaDB备份方案,通过工具容器执行mysqldump,结合定时任务实... 目录前言一、获取需要备份的数据库的信息二、备份步骤1.准备工作(X86)1.准备工作(arm)2.手

基于Linux的ffmpeg python的关键帧抽取

《基于Linux的ffmpegpython的关键帧抽取》本文主要介绍了基于Linux的ffmpegpython的关键帧抽取,实现以按帧或时间间隔抽取关键帧,文中通过示例代码介绍的非常详细,对大家的学... 目录1.FFmpeg的环境配置1) 创建一个虚拟环境envjavascript2) ffmpeg-py

华为鸿蒙HarmonyOS 5.1官宣7月开启升级! 首批支持名单公布

《华为鸿蒙HarmonyOS5.1官宣7月开启升级!首批支持名单公布》在刚刚结束的华为Pura80系列及全场景新品发布会上,除了众多新品的发布,还有一个消息也点燃了所有鸿蒙用户的期待,那就是Ha... 在今日的华为 Pura 80 系列及全场景新品发布会上,华为宣布鸿蒙 HarmonyOS 5.1 将于 7

Python使用FFmpeg实现高效音频格式转换工具

《Python使用FFmpeg实现高效音频格式转换工具》在数字音频处理领域,音频格式转换是一项基础但至关重要的功能,本文主要为大家介绍了Python如何使用FFmpeg实现强大功能的图形化音频转换工具... 目录概述功能详解软件效果展示主界面布局转换过程截图完成提示开发步骤详解1. 环境准备2. 项目功能结