当前位置:首页 > 体育 > 正文

用Golang手搓一个Office 365画中画视频工具?这事儿我干过

  • 体育
  • 2026-06-29 03:45:03
  • 252
摘要: 正在Office 365的Teams会议里听老板画大饼,突然想偷偷看个教学视频或者追一集剧?别问我怎么知道的,反正我试过切换窗口...

正在Office 365的Teams会议里听老板画大饼,突然想偷偷看个教学视频或者追一集剧?别问我怎么知道的,反正我试过切换窗口,结果被逮个正着,后来我想——能不能用Golang写个小工具,把画中画视频直接嵌入到Office 365界面上?就像电视里那种小窗播放。

说干就干,我知道Golang不是做UI的常规选择,但它够快、够直接,而且跨平台。我用的是Golang调用Windows的Win32 API,直接操作窗口句柄,核心思路就三步:

  1. 定位Office 365窗口(比如Teams、Outlook或Edge里打开的网页)
  2. 创建一个透明覆盖层,在上面开一个“洞”来显示视频
  3. 用Golang的goroutine实时抓取视频流,塞进那个洞里

听起来简单?踩坑踩得我差点把键盘吃了。

第一步:抓窗口句柄——我差点被PID搞疯

在Golang里枚举窗口,要用syscall调用EnumWindows,我写了个函数:

func findOfficeWindows() []uintptr {
    var hwnds []uintptr
    syscall.NewCallback(func(hwnd syscall.Handle, lParam uintptr) uintptr {
        var title [256]uint16
        syscall.GetWindowText(hwnd, &title, 256)
        text := syscall.UTF16ToString(title[:])
        if strings.Contains(text, "Teams") || strings.Contains(text, "Outlook") {
            hwnds = append(hwnds, uintptr(hwnd))
        }
        return 1
    })
}

你以为这样就完了?Office 365网页版在Edge里打开时,窗口标题是动态的,有时候是“Microsoft Teams - 会议中”,有时候就变成“新选项卡”,我加了个模糊匹配,但依然会误抓,最后我丧心病狂地比对窗口类名,比如Chrome_WidgetWin_1——这才稳了。

第二步:画中画覆盖层——透明窗口其实是个黑洞

画中画的核心是创建一个始终置顶的半透明窗口,Golang里你得用CreateWindowEx,设置WS_EX_LAYEREDWS_EX_TRANSPARENT标志,代码大概长这样:

hwndOverlay := CreateWindowEx(
    WS_EX_LAYERED|WS_EX_TRANSPARENT|WS_EX_TOOLWINDOW,
    className,
    nil,
    WS_POPUP,
    x, y, width, height,
    parentHwnd,
    nil,
    instance,
    nil,
)

注意坑:透明窗口默认不接收鼠标事件,但如果你想让用户能拖动画中画,得用SetWindowLong设置WS_EX_LAYERED不带WS_EX_TRANSPARENT,然后自己处理点击穿透,我搞了个开关:按住Ctrl键拖动,松手后鼠标事件穿透——有点像游戏里的小地图。

这个覆盖层不能是纯色,得显示视频,所以我要从视频源抓帧,然后BitBlt把每一帧画到窗口DC上,Golang的image包可以处理解码,但每秒30帧的渲染,用纯Go效率不够,我换成调用ffmpeg的共享库,把解码后的像素数据通过CGo传进来。

第三步:视频流同步——goroutine + 通道

视频解码和渲染得分开跑,我开了两个goroutine:

  • 解码协程:从本地文件或网络流读取,用ffmpeg解码成RGBA,丢进chan *image.RGBA
  • 渲染协程:从通道取帧,调用UpdateLayeredWindow刷新覆盖层

核心逻辑:

go func() {
    for frame := range frameChan {
        renderOverlay(hwndOverlay, frame)
        time.Sleep(33 * time.Millisecond) // 约30fps
    }
}()

但这么做有个问题:如果解码比渲染快,通道会膨胀爆内存,我加了个固定容量通道(比如10),满了就丢帧,副作用是视频会偶尔卡顿,但比起崩掉,卡一卡我能忍。

真实使用效果:又香又烂

我拿这个工具试了试——在Teams会议界面里,右下角浮着一个200x150的小窗,播放着《瑞克和莫蒂》,确实爽,但问题一堆:

  1. Office 365窗口一更新布局,画中画位置就偏了,比如Teams更新后左侧导航栏变窄,我之前硬编码的偏移量全废。
  2. 高DPI屏幕下模糊——因为我用的DC缩放没处理好。
  3. Edge的硬件加速会让覆盖层闪黑屏——不得不关掉Edge的GPU渲染。

最扯的是有一次Teams自动更新,窗口类名变了,我的程序找不到句柄,直接崩溃。

对了,为什么非要用Golang?

其实用Electron或者C#写这个更容易,但我选Golang是因为我懒得分发运行时——编译成一个.exe扔给朋友就能用,而且Golang的并发模型太适合这种多任务场景了。你甚至可以用go test边运行边调试画中画位置,这是写测试代码时意外的收获。

现在这个工具我还在用,但加了配置文件,可以手动指定Office 365窗口标题、画中画大小和偏移量,配置文件是JSON,每次Office更新我就改改偏移量,不完美,但够用。

哦对了,还有个小技巧:如果你用Windows 10的系统,可以用DwmSetWindowAttribute给画中画窗口加圆角——这样看起来更原生,我在Github上看到有人用dwmapi.h实现了,抄了一段过来,但偶尔会丢失圆角效果,我怀疑和DWM的版本有关,但懒得深究了。

反正嘛,工具就是边用边修,你要是也想搞一个,—先别追求完美,能跑就行,毕竟画中画的核心目的不是技术多牛,而是让你在Office 365里偷偷看视频的时候,别被抓到。

现在我去更新偏移量了……Teams又更新了。

用Golang手搓一个Office 365画中画视频工具?这事儿我干过