修改 TensorFlow C API 的代码,使得固定的 saved_model.pb 文件名变为可设置的
我在 2021 年 5 月份发出了一个 SpleeterMsvcExe 开源项目,当时是支持 11kHz 和 16kHz 两种模型,年底的时候又添加了对 22kHz 模型的支持。这三种不同频率上限的模型, variables 目录中的文件是完全相同的,只有 saved_model.pb 不同。但 TensorFlow 源码中这个文件名在 constants.h 中被定义为了固定值:

  1. // SavedModel proto filename.
  2. constexpr char kSavedModelFilenamePb[] = "saved_model.pb";
  3.  
  4. // SavedModel text format proto filename.
  5. constexpr char kSavedModelFilenamePbTxt[] = "saved_model.pbtxt";

用于加载模型的 ReadMetaGraphDefFromSavedModel() API 函数会调用 reader.cc 中的 ReadSavedModel() 函数:

  1. Status ReadSavedModel(const string& export_dir, SavedModel* saved_model_proto) {
  2.   LOG(INFO) << "Reading SavedModel from: " << export_dir;
  3.  
  4.   const string saved_model_pb_path =
  5.       io::JoinPath(export_dir, kSavedModelFilenamePb);
  6.   if (Env::Default()->FileExists(saved_model_pb_path).ok()) {
  7.     return ReadBinaryProto(Env::Default(), saved_model_pb_path,
  8.                            saved_model_proto);
  9.   }
  10.   const string saved_model_pbtxt_path =
  11.       io::JoinPath(export_dir, kSavedModelFilenamePbTxt);
  12.   if (Env::Default()->FileExists(saved_model_pbtxt_path).ok()) {
  13.     return ReadTextProto(Env::Default(), saved_model_pbtxt_path,
  14.                          saved_model_proto);
  15.   }
  16.   return Status(error::Code::NOT_FOUND,
  17.                 "Could not find SavedModel .pb or .pbtxt at supplied export "
  18.                 "directory path: " +
  19.                     export_dir);
  20. }

可以看到完全没有考虑让这个文件名可以被指定,而且一直到现在最新的 v2.12.0 版本都是这样的。

我之前曾经考虑过从模型下手,试图把三种种不同频率上限的模型合并为一个。搜索过几次 tensorflow saved model merge 之类的关键词,也借助 ChatGPT 修改过 checkpoint 到 saved model 的转换脚本,最终都没成功。而且从把 saved_model.pb 转换为 .pbtext 格式的结果看,整个 protobuf 文件中和频率上限相关的参数非常多,而且看文件里这些参数所在位置,也没法合并。

之前一直想尽量用官方提供的二进制版本,是考虑到这对于杀毒软件比较友好,自己编译的会有全新的 hash 值,有误报的风险。但现在考虑到 SpleeterMsvcExe, 即将发出的 WPF 版 Spleeter GUI 以及 BeatShow Player 程序的易用性,还是打算对 TensorFlow 的源码进行修改,自行编译一个版本来用了。

只是这一点需求,代码还是很好改的。直接在 ReadSavedModel() 中添加一段识别和处理环境变量的代码就可以了:

  1. Status ReadSavedModel(const string& export_dir, SavedModel* saved_model_proto) {
  2.   LOG(INFO) << "Reading SavedModel from: " << export_dir;
  3.  
  4.   const char* tf_alt_saved_model_pb = getenv("TF_ALT_SAVED_MODEL_PB");
  5.   if (tf_alt_saved_model_pb != nullptr) {
  6.     LOG(INFO) << "Environment variable TF_ALT_SAVED_MODEL_PB is set: " << tf_alt_saved_model_pb;
  7.     const string alt_saved_model_pb_path =
  8.         io::JoinPath(export_dir, tf_alt_saved_model_pb);
  9.     if (Env::Default()->FileExists(alt_saved_model_pb_path).ok()) {
  10.       LOG(INFO) << "Will use " << tf_alt_saved_model_pb << " instead of saved_model.pb";
  11.       return ReadBinaryProto(Env::Default(), alt_saved_model_pb_path,
  12.                              saved_model_proto);
  13.     } else {
  14.       return Status(error::Code::NOT_FOUND,
  15.                     "Could not find the specified .pb file: " + alt_saved_model_pb_path);
  16.     }
  17.   }
  18.  
  19.   const string saved_model_pb_path =
  20.       io::JoinPath(export_dir, kSavedModelFilenamePb);

不用改 API 接口的定义,兼容性和灵活性都比较好。程序中调用 TensorFlow C API 前,设置一下 TF_ALT_SAVED_MODEL_PB 环境变量的值就可以了。

对于 TensorFlow 的编译过程,可以参考上一篇文章:
TensorFlow C API 动态库 v1.15 版本的编译过程

2023-04-22 添加:

修改过的项目已经发到了 GitHub 上了: https://github.com/wudicgi/tensorflow-mod

实际的修改和之前贴的有差异,具体修改可以看 c5cfda2 这个提交。也可以直接下载 release 版本使用: https://github.com/wudicgi/tensorflow-mod/releases/tag/v1.15.5-mod.1
当前语言: 中文 (简体)
TensorFlow C API 动态库 v1.15 版本的编译过程

整个编译工程基本参照官方的 在 Windows 环境中从源代码构建 说明就可以完成。之前搜到一些更早的版本是用 Cmake + Visual Studio 编译的,貌似编译过程中容易产生一些问题。TensorFlow v1 的最后一个版本 1.15.5 是是用 Bazel 进行构建的,它会直接调用 MSVC 编译器,整个编译过程还是比较顺利的。

经过测试的构建配置一节中, tensorflow-1.15.0 编译时使用的 Python 版本为 3.5-3.7, 编译器为 MSVC 2017, 构建工具为 Bazel 0.26.1. 下面会使用这些推荐的版本。

1. 安装 Python 和 TensorFlow 软件包依赖项

我的电脑上已安装有 Python 3.7 和 3.9, 但是 PATH 中 3.9 的目录在前边,因此先调整顺序将 3.7 放到前边。不调整的话,调用 Python 前通过以下命令临时设置一下环境变量也可以:

set PATH=C:\Python\Python37-64\Scripts;C:\Python\Python37-64;%PATH%

因为 v1.15.5 版本的 /tensorflow/tools/pip_package/setup.py 文件里有 'keras_applications >= 1.0.8', 因此我将官方构建说明中 keras_applications 的安装版本由 1.0.6 改为了 1.0.8, 执行:

pip3 install six numpy wheel
pip3 install keras_applications==1.0.8 --no-deps
pip3 install keras_preprocessing==1.0.5 --no-deps

2. 安装 Bazel

我电脑上没有 Bazelisk, 也没有 Chocolatey 和 Scoop, 因此我选择手动安装 Bazel.

https://github.com/bazelbuild/bazel/releases/tag/0.26.1 下载 bazel-0.26.1-windows-x86_64.zip, 把它解压到任意一个目录,并且把这个目录加入到 PATH 中后, Bazel 就可以使用了。

首次运行 bazel.exe 时,它会解压一些文件到 C:\Users\%USERNAME% 目录下,之后编译时生成的一些中间文件也会在用户的 Profile 目录下。

3. 安装 MSYS2

MSYS2 的安装可以参考 https://www.msys2.org/ 首页的说明。我的电脑上已经安装有 MSYS2 了,就只执行了一下 pacman:

pacman -S git patch unzip

并且确保 C:\msys64\usr\bin 在 PATH 中。

4. 安装 Visual Studio

我的电脑已安装有 Visual Studio 2017 和 2022.

如果没有安装任何版本的 Visual Studio, 要安装 VS 2017 可以到 https://visualstudio.microsoft.com/zh-hans/vs/older-downloads/ 下载,可能需要登录 Microsoft 账号。

如果已经安装有 VS 2019, 2022 等更高版本,建议先尝试是否能正常编译。在已安装高版本 VS 的情况下安装低版本 VS 可能会有问题。

5. 下载 TensorFlow 源代码

使用 TortoiseGit 克隆版本库 https://github.com/tensorflow/tensorflow.git, 分支填写 v1.15.5, 深度填写 1.

6. 配置 build

在克隆得到的 tensorflow 目录中,执行:

python ./configure.py

如果不需要 GPU 支持,可以所有问题都选择默认值。

7. 构建 C API 库文件

执行以下命令构建 C API 的 dll 和 lib 文件:

bazel build --config=v1 --config=opt //tensorflow:tensorflow.dll
bazel build --config=v1 --config=opt //tensorflow:tensorflow.lib

在我的 5900X 上整个编译过程耗时约 14 分钟,生成文件大小和官方提供的 libtensorflow-cpu-windows-x86_64-1.15.0.zip 基本相同。

2023-04-22 添加:

编译时可以使用命令:

bazel build --config=v1 --copt=/arch:AVX --copt=/arch:AVX2 tensorflow:tensorflow.dll tensorflow:tensorflow_dll_import_lib

这样可以忽略掉为 opt config 配置的编译参数,直接指定编译支持 AVX 和 AVX2 的版本 (执行 python ./configure.py 时, opt config 的编译参数默认只有 /arch:AVX, 会缺少对 AVX2 的支持)。
当前语言: 中文 (简体)
入手 RTX 4070 显卡
去年夏天装的 AMD 5900X 的电脑,因为显卡价格原因没有买新显卡,而是接着用 2016 年买的 1050ti 显卡。6 年前的显卡显然已经非常不够用了,除了运算速度慢, 4GB 显存有些现在流行的开源项目根本跑不了。

前两天看到 4070 显卡发售了,查了下非常适合我用。华硕有一个双风扇的 DUAL GeForce RTX4070-12G 显卡,长度 27cm, 不用换机箱了。最大功耗 200W, 现在用的 750W 的电源也不用换了。为了保险又等了两天,稍微查了下 40 系另外 3 个型号的区别,在不需要 4090 算力的情况下, 4080 和 4090 比毫无性价比, 4070ti 比 4070 又强得有限,基本不用犹豫了。

今天上午下单,下午收到显卡。装上后简单测了下性能提升,和新的超分辨率功能。

性能提升

Whisper 的 ggml-large.bin 模型试了一下,之前用 1050ti 对一个 06:24 (384 秒) 的音频文件进行处理,耗时 160 秒:

Using GPU "NVIDIA GeForce GTX 1050 Ti", feature level 12.1, effective flags Wave32 | NoReshapedMatMul
Loaded MEL filters, 62.8 kb RAM
Loaded vocabulary, 51865 strings, 3037.1 kb RAM
Loaded 1259 GPU tensors, 2950.66 MB VRAM
Loaded model from "E:\Softwares\whisper-bin-x64\models\ggml-large.bin" to VRAM
Created source reader from the file "E:\CloudMusic\Nikita Fomin\Stranger (Dream Version).mp3"
[00:00:00.000 --> 00:00:03.480]   [MUSIC PLAYING]
...
[00:06:20.600 --> 00:06:23.600]   [MUSIC PLAYING]
    CPU Tasks
LoadModel       2.77043 seconds
RunComplete     161.1 seconds
Run     160.168 seconds
...
    GPU Tasks
LoadModel       2.10459 seconds
Run     160.019 seconds
...

换成 4070 显卡后,耗时 26 秒:

Using GPU "NVIDIA GeForce RTX 4070", feature level 12.1, effective flags Wave32 | NoReshapedMatMul
Loaded MEL filters, 62.8 kb RAM
Loaded vocabulary, 51865 strings, 3037.1 kb RAM
Loaded 1259 GPU tensors, 2950.66 MB VRAM
Computed CPU base frequency: 3.69996 GHz
Loaded model from "E:\Softwares\whisper-bin-x64\models\ggml-large.bin" to VRAM
Press Control+C or Control+Break to close this window
Created source reader from the file "E:\CloudMusic\Nikita Fomin\Stranger (Dream Version).mp3"
[00:00:00.000 --> 00:00:03.480]   [MUSIC PLAYING]
...
[00:06:20.600 --> 00:06:23.600]   [MUSIC PLAYING]
    CPU Tasks
LoadModel       1.56206 seconds
RunComplete     27.2146 seconds
Run     26.2726 seconds
...
    GPU Tasks
LoadModel       917.515 milliseconds
Run     26.1418 seconds
...

仅就这个应用来说,处理速度是之前的 6.15 倍。

超分辨率 (RTX Video Super Resolution) 功能

https://downloads.videolan.org/testing/vlc-rtx-upscaler/

https://github.com/emoose/VideoRenderer/releases/tag/rtx-1.1

https://www.videohelp.com/software/MPC-BE

K-Lite Mega Codec Pack 17.0.0, 2022-6-8

MPC-HC (Nightly, 64-bit)
------------------------

Build information:
    Version:            1.9.21.24 (1c7cdd24a)
    Compiler:           MSVC v19.29.30143
    Build date:         May 12 2022

LAV Filters:
    LAV Splitter:       0.76.1.3
    LAV Video:          0.76.1.3
    LAV Audio:          0.76.1.3
    FFmpeg compiler:    MinGW-w64 GCC 10.2.0

Operating system:
    Name:               Windows 10 (Build 21H2)
    Version:            10.0.19045 (64-bit)

Hardware:
    CPU:                AMD Ryzen 9 5900X 12-Core Processor
    GPU:                NVIDIA GeForce RTX 4070 (driver version: 31.0.15.3161)
当前语言: 中文 (简体)
自己写了个 Windows 10 上用的快速启动栏,解决系统自带功能的对齐和缩放问题
在 Windows 10 上,当任务栏选择使用小按钮时,快速启动栏中的图标会有对齐和缩放问题:



而且缩放比例越高,问题越明显。(例如在 4k 分辨率下通常会使用 200% 以上的缩放比例)

所以我基于 WPF 和 DeskBand 写了一个自己的快速启动栏,可正确对图标进行对齐和缩放:



GitHub 项目地址是 https://github.com/wudicgi/Win10QuickLaunchBar

需要的小伙伴可以直接下载 release 压缩包使用。

PS, 写之前找了一圈示例程序,只找到 https://github.com/20154530/DeskBandTest 这个默认就能很好的自动缩放。所以最终是以这个项目为基础修改来的。
当前语言: 中文 (简体) · also available in: English
搞了一台 DIY 级别的雕刻机 CNC 3018
前两台在淘宝上以不到 650 的价格,购买了一个 CNC 3018 雕刻机的套件 (其中脱机控制器占 90 元)。
到货后这一盒子东西还有点分量:



除了主轴电机部分是装配好的一个整体,其他所有部分全是散件:



需要自己根据说明书完成组装:





实际装配时还看了下厂家在 ytb 上发的视频,装的顺序不对的话,对齐的时候会困难一些:



大约耗时两个晚上组装完成,看起来是这样的:



本来加工平台应该是居中放置的,我特意把它靠左放了。这是在购买前就考虑好的,目的是为了利用右侧的空间,来对塑料外壳的上下侧面进行加工。否则按这台 DIY 机器只有 45mm 的 Z 轴行程,塑料外壳根本没法立着固定在平台上来加工。



用脱机控制器简单铣了个方孔,感觉还不错。即使是不画图纸,临时手动随便开个孔,也比以前用电钻支架+十字工作台手动操作要方便一些。



当然这个之后准备是用 3D 打印制作好塑料外壳的夹具后,由 PC 来控制自动进行加工的。
当前语言: 中文 (简体)
入手 Sermoon V1 3D 打印机,试打了第一个自己画的零件
赶在春节前购买了一台创想三维的 3D 打印机,型号 Sermoon V1:



选这个型号主要是因为它正好能放到我的货架上。Core XY 结构的,平台不会前后移动,这样机器就固定占据这么个空间就可以使用了:



买的时候这款是京东发货,夜里下单,下午就到了。晚上回去拆箱放好后,先插上附带的 U 盘打了个哨子。结果它附带这个哨子的 G-code 文件太精细了,用了 2 小时 20 分钟才打完:



今天简单研究了一下,在 Fusion 360 中画了一个小电磨的固定件。试了下用创想三维提供的配套软件生成 G-code 来打印,总体操作差不多属于傻瓜式了,默认的参数基本都没动,就打印成功了:



设计的时候留了一些公差,实际试了下尺寸 OK, 可以使用。这是把电磨装到电钻支架上之后的效果:


当前语言: 中文 (简体) · also available in: English
写了一个纯 C 语言版本的 Spleeter 人声、伴奏分离命令行程序
Spleeter 是 2019 年底 Deezer 公司开源的一个可以分离人声和背景音乐的程序 (也可分离出鼓、贝斯和钢琴),效果拔群。售价 400 刀的 RX 8 软件中的 Music Rebalance 功能,用的就是 Spleeter 的模型。

一年多过去了,现在 Github 上的 Spleeter 程序,包括官方原版程序,基本还都是需要有 Python 环境或者内部包含了一个 Python 的。C++ 的几个静态库和命令行程序,不是用了 CMake 就是只支持 Linux 系统。对 Windows 平台开发者不太友好,如果是一般用户想直接使用就更不方便了。

最近将我的 BeatShow 程序中用到 Spleeter 的部分代码整理了一下,形成了一个独立的纯 C 语言编写的 Spleeter 命令行程序。编译完就是一个 exe 程序,其余 dll 文件是 TensorFlow C API 和 FFmpeg 的动态库。可以直接运行使用,不需要 Python 环境。



Github 项目地址是 https://github.com/wudicgi/SpleeterMsvcExe

根据说明直接下载 release 文件就好。models 文件比较大,如果下载速度不给力可以试试 https://ghproxy.com/

早上在 v2ex 发了个分享帖,地址是 https://www.v2ex.com/t/776618
当前语言: 中文 (简体) · also available in: English
When We Disco 的 BeatShow 演示
最近被这首歌洗脑了,忍不住又录了个 demo. 现在业余时间一直在完善这个项目,主要功能基本都完成了,还剩下一些收尾工作,应该很快就能公开提供下载了。


在优酷上观看: https://v.youku.com/v_show/id_XNDg0ODgwMTMyMA==.html
在 bilibili 上观看: https://www.bilibili.com/video/BV1N64y1F73D
当前语言: 中文 (简体)
CH552 官方 USB HID 示例程序中的一处 bug
最近在开发 BeatShow 设备的固件恢复功能时发现,使用 CH552 实现的 USB HID 设备总是获取不到设备名称,而 STM32F072 的就没问题。即使将所有 USB 描述符都改成一样的,问题也仍然存在。

调试 PC 端程序看到底层调用的 HidD_GetProductString() 函数执行结果是成功的,只是返回的字符串长度为 0, 所以就得从硬件下手了。

经过抓包,发现对于 CH552 设备,除了设备枚举过程中的正常 GET_DESCRIPTOR 请求,在 PC 端程序调用 HidD_GetProductString() 时系统又发送了额外的 GET_DESCRIPTOR 请求。而且这些请求的返回数据长度只有 2 字节,明显是不正常的。

CH552 设备枚举过程中正常的 GET_DESCRIPTOR 请求, wLength = 0x00FF = 255:


CH552 设备完成枚举后的额外 GET_DESCRIPTOR 请求, wLength = 0x0102 = 258:


STM32 设备在完成枚举后则没有这些额外的 GET_DESCRIPTOR 请求:


对于为什么系统只对 CH552 设备发送了这些额外的 GET_DESCRIPTOR 请求,经过一番搜索没有找到答案。但显然 CH552 的程序在这块的处理上有 bug。

CH552 实现的 bootloader 是以 WCH 官方的 USB HID 示例程序 CompatibilityHID.C 为基础,逐步重构和修改而来的。这个示例中有一段处理 SETUP 事物的代码是这样的:

case UIS_TOKEN_SETUP | 0:                       // SETUP 事务
    len = USB_RX_LEN;
    if (len == (sizeof(USB_SETUP_REQ)))
    {
        SetupLen = UsbSetupBuf->wLengthL;
        len = 0;                                // 默认为成功并且上传 0 长度
        SetupReq = UsbSetupBuf->bRequest;
        // ...

可以看到只取了 wLength 的低字节部分 wLengthL 作为 SetupLen, 完全没有使用高字节部分 wLengthH。而另一个 VendorDefinedDev.C 示例程序中,就对高字节部分 wLengthH 做了检查:

case UIS_TOKEN_SETUP | 0:                       // endpoint 0# SETUP
    len = USB_RX_LEN;
    if (len == sizeof(USB_SETUP_REQ)) {         // SETUP 包长度
        SetupLen = UsbSetupBuf->wLengthL;
        if (UsbSetupBuf->wLengthH || SetupLen > 0x7F) SetupLen = 0x7F;      // 限制总长度
        len = 0;                                // 默认为成功并且上传 0 长度
        SetupReqCode = UsbSetupBuf->bRequest;
        // ...

CompositeKM.C 示例程序中也一样检查了 wLengthH:

case UIS_TOKEN_SETUP | 0:                       // SETUP 事务
    len = USB_RX_LEN;
    if (len == (sizeof(USB_SETUP_REQ)))
    {
        SetupLen = UsbSetupBuf->wLengthL;
        if (UsbSetupBuf->wLengthH || SetupLen > 0x7F)
        {
            SetupLen = 0x7F;                    // 限制总长度
        }
        len = 0;                                // 默认为成功并且上传 0 长度
        SetupReq = UsbSetupBuf->bRequest;
        // ...

CompatibilityHID.C 示例程序中原本没有任何字符串描述符,也没有对 GET_DESCRIPTOR 请求的处理代码,相关的内容都是我后添加的。这可能是官方程序中这个 bug 存在了这么长时间的原因。

知道原因后就好修改了,只要在 wLengthH 不为 0 时把 SetupLen 限制到 uint8_t 范围内的最大值 0xFF 就可以了:

case UIS_TOKEN_SETUP | 0:                       // SETUP 事务
    len = USB_RX_LEN;
    if (len == (sizeof(USB_SETUP_REQ)))
    {
        SetupLen = UsbSetupBuf->wLengthL;
        if (UsbSetupBuf->wLengthH != 0)
        {
            SetupLen = 0xFF;                    // 限制总长度
        }
        len = 0;                                // 默认为成功并且上传 0 长度
        SetupReq = UsbSetupBuf->bRequest;
        // ...
当前语言: 中文 (简体)
简单“修正”了一下 AStyle 中的一个重复缩进的问题
GitHub 地址: https://github.com/wudicgi/astyle-modified
效果对比脚本: https://github.com/wudicgi/astyle-modified/tree/master/bin
修改后程序下载: https://github.com/wudicgi/astyle-modified/blob/master/bin/AStyle.exe

1. 发现问题

上周我在对 ffmpeg 的 transcoding.c 示例程序进行代码风格美化时,发现 AStyle 在很多处不需要改动的地方添加了额外的缩进:

// 处理前
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
        args, NULL, filter_graph);

// 处理后
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
                args, NULL, filter_graph);

其实以前也遇到过这个问题,但这次出现得比较集中、比较多,就准备解决一下了。打开 AStyle 的 VS2017 项目进行调试,发现对于我使用的选项

indent=spaces=4
indent-after-parens
indent-continuation=2

AStyle 在遇到赋值运算符 '=' 时为后续行添加 2 级缩进,而当继续处理遇到左括号 '(' 时又添加了 2 级缩进,于是后续行就拥有了多余的 2 级缩进。

2. 修改问题

我使用了一个简单粗暴的方式进行修改,就是在 ASBeautifier 类的 registerContinuationIndent() 方法中添加一个条件判断,使它在已经因为 '=' 添加了后续行缩进的情况下,不再因为其后出现的第一个 '(' 再添加缩进。

void ASBeautifier::registerContinuationIndent(const string& line, int i, int spaceIndentCount_,
                                              int tabIncrementIn, int minIndent, bool updateParenStack)
{
    assert(>= -1);
    int remainingCharNum = line.length() - i;
    int nextNonWSChar = getNextProgramCharDistance(line, i);

    // if indent is around the last char in the line OR indent-after-paren is requested,
    // indent with the continuation indent
    if (nextNonWSChar == remainingCharNum || shouldIndentAfterParen)
    {
        // added by Wudi
        bool noDuplicatedIndentForFirstParen = updateParenStack // current indentation is for opening paren '('
                && !continuationIndentStack->empty()            // previously indented for an assignment '='
                && parenIndentStack->empty()                    // current '(' is the first opening paren needs to add indentation
                && (!= 0);                                    // current '(' must not be the first char, otherwise there is no '=' ahead

        int previousIndent = spaceIndentCount_;
        if (!continuationIndentStack->empty())
            previousIndent = continuationIndentStack->back();

        int currIndent = continuationIndent * indentLength + previousIndent;

        // added by Wudi
        if (noDuplicatedIndentForFirstParen) {
            currIndent = previousIndent;
        }

        if (currIndent > maxContinuationIndent && line[i] != '{')
            currIndent = indentLength * 2 + spaceIndentCount_;
        continuationIndentStack->emplace_back(currIndent);
        if (updateParenStack)
            parenIndentStack->emplace_back(previousIndent);
        return;
    }

    // ...

3. 修改前后效果对比

对同样的一段代码进行处理,原版 AStyle 的输出为:

int ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
                args, NULL, filter_graph);

int (*enc_func_2)(AVCodecContext *, AVPacket *, const AVFrame *, int *) = (ifmt_ctx->streams[stream_index]->codecpar->codec_type ==
                AVMEDIA_TYPE_VIDEO) ? avcodec_encode_video2 : avcodec_encode_audio2;

ret = func(a, b, another_func(1, 2,
                        3),
                c, d);

修改后版本的输出为:

int ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
        args, NULL, filter_graph);

int (*enc_func_2)(AVCodecContext *, AVPacket *, const AVFrame *, int *) = (ifmt_ctx->streams[stream_index]->codecpar->codec_type ==
        AVMEDIA_TYPE_VIDEO) ? avcodec_encode_video2 : avcodec_encode_audio2;

ret = func(a, b, another_func(1, 2,
                3),
        c, d);

4. 修改的副作用

标题中的“修正”带引号是因为这并不一定是个 bug, 同时现在所做的这个修改会有一些副作用。对于一些极端情况的代码,例如:

int ret = func(a, b, another_func(1, 2,
3, 4, 5),
c, d)
/ 123;

原版 AStyle 能保留所有缩进等级,输出结果为:

int ret = func(a, b, another_func(1, 2,
                        3, 4, 5),
                c, d)
        / 123;

而该修改版本的输出为:

int ret = func(a, b, another_func(1, 2,
                3, 4, 5),
        c, d)
        / 123;

最外层在缩进等级上没有与内层区分开来。但像这样的代码并不常见,所以为方便自己日常使用,“修正”一下还是有必要的。

附: AStyle 原版链接

AStyle 的官方项目地址为 http://astyle.sourceforge.net/
当前语言: 中文 (简体)
更多条目: [1] [2] [3] [4] [5] [6] ... [17]
« 上一页 · 下一页 »