[i=s] 本帖最后由 lzy871238103 于 2025-7-25 16:13 编辑 [/i]<br />
<br />
项目背景
最近由于程序调试,需要在两台电脑间需要频繁拷贝文件,拿着U盘每天在两台电脑之间插拔几十次,实在是苦不堪言,于是某宝买了一个USB KMFU对拷线,文件传输只需拖拽鼠标,拷贝速度能有20多MB/s ,完全满足日常所需。
甚至可以在KMFU对拷线自身的HUB上插一个固态硬盘,超大文件传输至固态硬盘后,通过KMFU配置界面将固态硬盘切换到对端电脑上,极为方便!
研究了下该设备,发现可以搭配FFMPEG项目做一些有趣的事情,于是开始了尝试的第一步。
CH9338简介
CH9338是一款用于实现高速USB对拷功能的专用芯片,由CH9338打造的USB KMFU (Keyboard/Mouse/File/USB HUB)对拷共享HUB可用于多台计算机键盘/鼠标/触摸板的跨屏共享,支持Windows、macOS、Android操作系统。
PC间跨屏使用时等同单台PC内操作,省去鼠标键盘、节省空间,同时两台PC间的剪贴板、文件支持共享。
USB KMFU 还提供USB HUB和外设共享功能,无需用户插拔USB设备,实现在两台PC设备间的3个HUB端口自由切换,便捷的同时延长了USB接口的使用寿命。

与传统的USB对拷线相比,KMFU有很多创新,例如:
边界防误穿屏:鼠标穿屏灵敏度6挡可调。
灵敏度高----->鼠标切屏丝滑无阻力
灵敏度低----->鼠标切屏有阻力,减少误操作

外设共享:KMFU自带HUB上USB设备,可随时切换至任意一端。

数据透传通道:提供一条USB数据透传通道。
本次博客主要是利用CH9338的USB数据透传通道,借助FFMPEG项目传输屏幕数据,实现镜像屏及拓展屏功能。
CH9338详细介绍可参考:
用于双机跨屏共享文件和数据的USB对连芯片-CH9338(https://www.wch.cn/news/726.html.html)
项目最终效果
镜像屏:

拓展屏:

FFMPEG简介
FFmpeg是一款开源的跨平台音视频处理工具集,提供录制、转换、流传输等完整解决方案。其核心库包括libavcodec(编解码)、libavformat(封装格式处理)、libavfilter(滤镜处理)等等。
用户可通过项目内置的ffmpeg、ffplay程序快速实现基础功能,也可通过API实现复杂自定义功能。

FFMPEG官网:https://ffmpeg.org/
FFMPEG源码库:https://github.com/FFmpeg/FFmpeg
添加自定义传输协议
FFMPEG本身实现了一些常见的传输协议,如网络的TCP/UDP、HTTP/HTTPS、FTP/FTPS等等。这些协议的驱动位于项目中的libavformat库中,我们将参照原有的实现,添加我们自定义的传输协议。
FFMPEG源码修改
FFMPEG工程中主要是依靠URLProtocol以及URLContext两个结构体实现传输协议的创建、使用和销毁。
URLProtocol 是协议的操作接口定义,其中定义了打开关闭、读写接口等,需要我们自行实现。
URLContext则是协议的运行时实例,管理协议的实际状态。其中成员 priv_data
指向自定义的私有化结构体。
CH9338定义的私有化结构体如下:
typedef struct CH375Context {
const AVClass *class; // 固定值,保证能为上层提供基地址
int dev_index; //设备的index号
int w_endpoint; //设备的写端点号
int r_endpoint; //设备的读端点号
int rw_timeout; //读写超时时间
char* dev_name; //设备标识符
} CH375Context;
除此外,还需设定默认值并为用户态提供修改接口:
#define OFFSET(x) offsetof(CH375Context, x)
static const AVOption ch375_options[] = {
{ "device_index", "CH375 device serial number", OFFSET(dev_index), AV_OPT_TYPE_INT, { .i64 = 0 }, -1, 12, AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_DECODING_PARAM },
{ "w_endpoint", "CH375 write endpoint", OFFSET(w_endpoint), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 8, AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_DECODING_PARAM },
{ "r_endpoint", "CH375 read endpoint", OFFSET(r_endpoint), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 8, AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_DECODING_PARAM },
{ "rw_timeout", "CH375 read/write timeout in milliseconds", OFFSET(rw_timeout), AV_OPT_TYPE_INT, { .i64 = 30 }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_DECODING_PARAM },
{ "dev_name", "CH75 device identifier", OFFSET(dev_name), AV_OPT_TYPE_STRING, {.str = "VID_1A86&PID_8022&MI_03"}, 0, 0, AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_DECODING_PARAM },
{ NULL }
};
接下来则是实现读写等基本功能,详见代码。
以初始化输入设备为例,用户在程序中使用 avformat_open_input
函数,函数会根据提供的URL地址去匹配协议,按照 priv_data_size
去申请内存,创建我们声明的私有结构体,并将URLContext结构体中的 void *priv_data
指针指向CH375Context 。接着则是正常的打开、读取。。。
调用的大致流程如下:

修改后的FFMPEG编译(以Windows系统为例)
Windows平台下FFMPEG的编译可使用MSYS2或Cygwin,这里以MSYS2为例。
前期工作
安装MinGW-w64
pacman -S mingw-w64-x86_64-toolchain
安装git、cmake等:
pacman -S git
pacman -S make
pacman -S automake
pacman -S autoconf
pacman -S perl
pacman -S libtool
pacman -S mingw-w64-i686-cmake
pacman -S pkg-config
pacman -S mingw-w64-x86_64-SDL2
安装SDL2,FFMPEG工程中ffplay默认使用SDL2进行渲染播放:
pacman -S mingw-w64-x86_64-SDL2
源码编译
- 自行git x264、fdk-aac、mp3lame、libvpx源码,并
make install
在自定义目录中。
- git FFMPEG源码,配置编译选项:
./configure \
--prefix=/home/xxx/ffmpeg/build/ffmpeg-7.7.1 \
--arch=x86_64 \ #指定编译架构
--enable-shared \
--enable-protocol=ch375 \ #使能自定义的协议
--enable-ffplay \
--enable-sdl2 \
--enable-gpl \
--enable-libfdk-aac \
--enable-nonfree \
--enable-libvpx \
--enable-libx264 \
--enable-libmp3lame \
--extra-cflags="-I/mingw64/include/SDL2" \
--extra-ldflags="-L/mingw64/lib" \
--extra-cflags="-I/home/xxx/ffmpeg/build/libfdk-aac/include" \
--extra-ldflags="-L/home/xxx/ffmpeg/build/libfdk-aac/lib" \
--extra-cflags="-I/home/xxx/ffmpeg/build/libvpx/include" \
--extra-ldflags="-L/home/xxx/ffmpeg/build/libvpx/lib" \
--extra-cflags="-I/home/xxx/ffmpeg/build/libx264/include" \
--extra-ldflags="-L/home/xxx/ffmpeg/build/libx264/lib" \
--extra-cflags="-I/home/xxx/ffmpeg/build/libmp3lame/include" \
--extra-ldflags="-L/home/xxx/ffmpeg/build/libmp3lame/lib"
常见错误: 如遇 X264
无法找到环境目录,例如:

可按以下方式手动声明,并重新 configure
。
export PKG_CONFIG_PATH=/home/xxx/ffmpeg/build/libx264/lib/pkgconfig:$PKG_CONFIG_PATH
-
make install
configure
如无错误,正常 make install
即可。
如遇到其他问题,可自行百度FFMPEG编译教程。
-
验证
编译完成后,运行ffmpeg.exe、ffplay.exe、ffprobe.exe任意程序,提示缺少的DLL可在源码目录及MSYS2安装目录下找到。
我们命令行调用FFMPEG,使用 -protocols
命令列出当前支持的传输协议,查询当前是否支持CH9338。

应用场景
可以利用CH9338提供的透传通道,搭配FFMPEG去实现屏幕的镜像与拓展。
屏幕镜像
屏幕镜像的本质是通过gdigrab去抓取屏幕、通过CH9338发送H264数据、另一台PC中使用ffplay.exe解码并播放画面。
ffmpeg.exe抓屏并传输:
ffmpeg \
-probesize 32 \
-fflags flush_packets \
-fflags discardcorrupt \
-flags low_delay \
-analyzeduration 0 \
-f gdigrab \
-framerate 30 \ #指定30帧录制传输
-i desktop \
-preset ultrafast \ #指定ultrafast编码预设
-c:v libx264 \ #指定libx264编码
-crf 28 \ #H264恒定速率因子,数值越小,画质越高
-g 1 \
-tune zerolatency \
-pix_fmt yuv420p \
-max_delay 0 \
-bf 0 \ #取消B帧,减小延迟
-f h264 \
"ch375://download" #声明传输协议
ffplay.exe接收并实时显示:
ffplay \
-fflags nobuffer \
-analyzeduration 0 \
-probesize 32 \
-flags low_delay \
-flags2 fast \
-fflags discardcorrupt \
"ch375://upload"
屏幕拓展
- 创建虚拟显示屏
首先需要一个虚拟显示驱动来创建副屏。
这里推荐借助parsec-vdd项目来完成。
Parsec VDD 可在 Windows 10+ 系统上启用虚拟显示,能够很方便的创建各种自定义尺寸的屏幕。
项目仓库:
https://github.com/nomi-san/parsec-vdd/tree/main

- ffmpeg.exe抓屏并传输
ffmpeg \
-probesize 32 \
-fflags flush_packets \
-fflags discardcorrupt \
-flags low_delay \
-analyzeduration 0 \
-f gdigrab \
-framerate 30 \
-offset_x 1920 \ #主屏幕尺寸为1920*1080,因此抓取x坐标偏移1920
-offset_y 0 \
-video_size 1920x1080 \ #创建的副屏尺寸为1920*1080
-i desktop \
-preset ultrafast \
-c:v libx264 \
-crf 28 \
-g 1 \
-tune zerolatency \
-pix_fmt yuv420p \
-max_delay 0 \
-bf 0 \
-f h264 \
"ch375://download"
ffplay.exe接收并实时显示命令不变,仍是:
ffplay \
-fflags nobuffer \
-analyzeduration 0 \
-probesize 32 \
-flags low_delay \
-flags2 fast \
-fflags discardcorrupt \
"ch375://upload"
总结
项目所使用的WCHKMFU相关库文件可联系官方技术支持索取:
技术邮箱:tech@wch.cn
本文利用CH9338芯片提供的USB透传通道完成屏幕数据的传输。修改的FFMPEG的源码、编译后的ffmpeg.exe、ffplay.exe,以及通过FFMPEG API调用CH9338抓屏推流及拉流DEMO均已上传至Github,详见:
https://github.com/ZhiyuanYuanNJ/CH9338_FFMPEG_Screen