QGC二次开发本地媒体浏览实战(一)Qt5+DirectShow 在 Android正常_Windows为什么出问题

文章目录

大家好,这篇记录一下我最近在 QGC 二次开发里做本地媒体浏览功能时踩到的一个坑。

这次需求并不复杂,就是在飞行界面增加一个媒体浏览入口,用来查看软件保存下来的照片和视频。其中:

  • 照片要支持预览
  • 照片要支持上一张、下一张切换
  • 视频要支持播放、暂停、停止
  • Android 和 Windows 都要能用

结果实际做下来发现,Android 这条线基本没什么问题,但 Windows 上就开始出状况了:

  • 图片能正常显示
  • 视频列表也能正常出来
  • 点击视频后却无法播放
  • 控制台一直报 DirectShowPlayerService::doRender 相关错误

所以这篇文章不讲虚的,直接把这次排查过程和关键代码记录下来。后面如果你也在做 QGC 二次开发,刚好要碰本地媒体浏览,这篇应该能帮你少绕点路。

效果图

整体效果图如下:

整体技术架构

先放一张这次媒体浏览功能的整体架构图,后面正文就是按这条链路一步一步排查下来的。
QGC 飞行界面
Media 入口按钮
媒体浏览页
目录层

AppSettings

photoSavePath / videoSavePath
列表层

FolderListModel

后缀分类 / 排序 / 筛选
预览层

图片预览 / 视频播放
Android
Windows
图片预览正常
视频播放正常
图片预览正常
视频列表正常
视频播放失败

DirectShowPlayerService::doRender
结论

Windows 问题不在媒体页 UI

而在 Qt5 + DirectShow 本地视频链路

一、先把功能入口接起来

本地媒体浏览页不是一个单独弹出来的窗口,而是飞行界面里的一层页面,所以第一步先把入口做好。

我这里的做法是,在顶部状态栏增加一个 Media 按钮,点击后切换媒体页显示状态。

关键代码如下:

qml 复制代码
Button {
    text: XGlobalProperty.mediaBrowserVisible ? qsTr("Back") : qsTr("Media")
    onClicked: {
        XGlobalProperty.mediaBrowserVisible = !XGlobalProperty.mediaBrowserVisible
        if (XGlobalProperty.mediaBrowserVisible) {
            XGlobalProperty.settingViewVisible = false
        }
    }
}

这里的思路很简单:

  • 没进入媒体页时,按钮显示 Media
  • 进入媒体页后,按钮变成 Back
  • 返回时直接把媒体页状态关掉

也就是说,媒体浏览功能本质上就是飞行界面中的一个页面状态切换。

二、媒体页挂载位置不能随便放

这一步是我一开始踩到的第一个结构性问题。

最开始我以为媒体页只要挂在主 QML 页面里就行,结果实际验证发现不对。因为飞行界面本身已经有:

  • 图传区域
  • 飞行叠加控件
  • 拍照录像控件
  • PIP 小窗

如果媒体页挂载层级不对,就会出现"媒体页打开了,但别的飞行控件还浮在上面"的情况。

最后正确的挂载方式,是让媒体页直接覆盖到飞行界面顶层。

关键代码类似这样:

qml 复制代码
XMediaBrowserView {
    id: xMediaBrowserView
    anchors.top: parent.top
    anchors.left: parent.left
    anchors.right: parent.right
    anchors.bottom: parent.bottom
    visible: XGlobalProperty.mediaBrowserVisible
    z: QGroundControl.zOrderTopMost + 10
}

这段代码看起来很简单,但很关键。因为它决定了媒体页不是一个普通子控件,而是一个真正压在飞行界面上层的全屏页面。

三、媒体目录必须跟 AppSettings 走

页面起来以后,下一步就是读照片和视频目录。

这种功能很容易一开始写成这样:

qml 复制代码
property string photoFolderPath: "C:/Users/xxx/Documents/QGroundControl/Photo"
property string videoFolderPath: "C:/Users/xxx/Documents/QGroundControl/Video"

开发阶段这么写似乎能跑,但这种方式很不稳。因为真正的保存路径应该跟软件设置保持一致,而不是开发者自己猜。

在 QGC 二次开发里,更合理的方式是直接读取应用设置中的照片和视频保存目录。

关键代码如下:

qml 复制代码
readonly property string photoFolderPath: QGroundControl.settingsManager.appSettings.photoSavePath
readonly property string videoFolderPath: QGroundControl.settingsManager.appSettings.videoSavePath

这里我还踩了一个小坑,就是最开始把它们当成 Fact.value 去读了,写成类似下面这样:

qml 复制代码
QGroundControl.settingsManager.appSettings.photoSavePath.value
QGroundControl.settingsManager.appSettings.videoSavePath.value

结果路径直接就拿错了。

后面确认以后才发现,这两个值本身就是直接给 QML 使用的字符串属性,不需要再套 .value

四、Android 和 Windows 的 file URL 不能硬共用一套逻辑

目录路径拿对以后,下一步就是生成目录 URL 给 FolderListModelImageMediaPlayer 使用。

这一步一开始我也想偷懒,想着统一转成 file:// 就行。但实际做下来发现,Android 和 Windows 这里还是得分开处理。

我这里最后用的是一个很简单的转换函数:

qml 复制代码
function toFolderUrl(path) {
    var cleaned = cleanedPath(path)
    if (cleaned === "") {
        return ""
    }

    if (cleaned.charAt(0) === "/") {
        return "file://" + cleaned
    }

    return "file:///" + cleaned
}

为什么要这样分?

因为两边原始路径长得不一样。

Android

Android 这边通常是:

text 复制代码
/storage/emulated/0/...

如果这里 file:// 前缀拼错,结果通常不是"路径报错",而是更烦人的那种情况:

  • 明明目录里有文件
  • 但列表扫不到
  • 图片不显示
  • 视频也播不了

Windows

Windows 这边通常是:

text 复制代码
C:/Users/xxx/Documents/...

这时就应该是 file:///C:/... 这种形式。

这一步看起来是小事,但实际就是这种小地方最容易把 Android 和 Windows 的行为拉开。

五、先把目录扫描做稳,再谈播放

路径处理对了以后,接着就是扫描目录。

我这里没有一开始就把照片和视频分成很多套扫描规则,而是先把目录里的文件全部扫出来,再由代码自己判断类型。

关键代码如下:

qml 复制代码
FolderListModel {
    id: photoFolderModel
    folder: root.photoFolderUrl
    nameFilters: [ "*" ]
    showDirs: false
    showDotAndDotDot: false
    sortField: FolderListModel.Unsorted
}

再配合一个后缀判断函数:

qml 复制代码
function mediaTypeFromName(fileName) {
    var lower = (fileName || "").toLowerCase()
    if (lower.match(/\.(jpg|jpeg|png|bmp|gif)$/)) {
        return "photo"
    }
    if (lower.match(/\.(mp4|mov|avi|mkv)$/)) {
        return "video"
    }
    return "unknown"
}

这样做的好处是比较直接:

  1. 目录先完整扫出来
  2. 再用代码自己控制哪些当照片、哪些当视频
  3. 后面调试时也更容易看清到底是哪一步出了问题

如果一开始就把过滤条件全压到 FolderListModel 上,出了问题有时候不太好定位。

六、FolderListModel.get(...) 返回值这个坑很容易忽略

目录能扫出来,不代表数据就已经可以放心往后用了。

我这次还有一个很典型的坑,就是从 FolderListModel 取值的时候,直接把结果当普通字符串用了。

比如最开始很容易写出这种代码:

qml 复制代码
var fileName = folderModel.get(i, "fileName")
var fileUrl = folderModel.get(i, "fileURL")

表面看起来没问题,但运行时就会碰到这种报错:

text 复制代码
Unable to assign QJSValue to QString
Unable to assign QJSValue to QUrl

原因就在于,FolderListModel.get(...) 返回出来的值很多时候不是你以为的纯字符串,而是 QJSValue

所以更稳一点的写法应该是显式转换一下,比如:

qml 复制代码
var nativeUrl = folderModel.get(i, "fileURL")
var fileUrl = nativeUrl ? nativeUrl.toString() : ""

这一步其实很细,但很真实。因为你会发现:

  • 文件数没问题
  • 列表似乎也差不多出来了
  • 但运行时一直有奇怪类型错误

如果不注意这里,排查起来很浪费时间。

七、Android 到这里基本就已经通了

把目录、URL、列表模型和类型转换这些问题收掉以后,Android 这条线基本就通了。

最后 Android 上的效果大致就是:

  • 能正常读取照片和视频目录
  • 能显示媒体列表
  • 照片能预览
  • 照片支持上一张、下一张
  • 视频点击后自动播放
  • 支持暂停、停止和拖动进度

也就是说,从整体设计上看,这套媒体浏览页面本身是成立的。页面结构没有问题,列表逻辑没有问题,基础交互也没有问题。

这一点非常重要,因为这意味着:如果后面 Windows 还有问题,那就别再反复怀疑整套界面设计了。

八、Windows 最终问题落到了 Qt5 + DirectShow

前面的基础问题都排干净以后,Windows 上剩下的现象就比较集中:

  • 图片可以看
  • 视频列表可以显示
  • 视频文件路径也没问题
  • 点击视频后,播放器报错

当时最典型的报错就是:

text 复制代码
DirectShowPlayerService::doRender: Unresolved error code 0x80040266

而视频播放部分的代码其实很普通,就是 Qt5 常见的写法:

qml 复制代码
MediaPlayer {
    id: mediaPlayer
    source: root.fileUrl
    autoPlay: false

    onError: {
        console.log("[MediaBrowser] video play error:", error, errorString, root.fileUrl)
    }
}

VideoOutput {
    anchors.fill: parent
    source: mediaPlayer
    fillMode: VideoOutput.PreserveAspectFit
}

也正因为这段代码没什么特别,所以问题反而更容易收束。

排查到这里,我基本就确认了:

  • 不是页面层级问题
  • 不是目录路径问题
  • 不是媒体列表问题
  • 不是图片预览逻辑问题

而是 Qt5 在 Windows 下这套本地多媒体链路,本质上还是走到了 DirectShow 这条路径上。而这条路径的老问题就是:不是所有本地 MP4 都能稳定播。

换句话说,同样一套 QML 播放逻辑:

  • Android 上可以
  • Windows 上不一定

这时候如果还继续在 QML 层面来回试,意义已经不大了。

九、最后总结

这次在 QGC 二次开发里做本地媒体浏览,整体结论比较清楚。

先说 Android,这条线基本没什么大问题。只要把照片和视频保存路径读取对、文件 URL 处理对、FolderListModel 取值时注意类型转换,图片预览和视频播放都能正常工作。

Windows 这边就不一样了。前面的目录、列表、图片预览其实都没问题,真正卡住的是本地视频播放。最后问题收束到 Qt5 在 Windows 下这套 MediaPlayer + VideoOutput 的本地多媒体链路,也就是 DirectShow 这一层不够稳。现象就是图片能看、视频列表能出,但视频不一定能播,哪怕文件本身是 MP4。

这次有几个点值得单独记一下:

  1. 照片和视频目录不要自己猜,直接从应用设置里读取。
  2. photoSavePathvideoSavePath 不是 Fact.value,这一点很容易写错。
  3. Android 和 Windows 的 file:// 路径不能偷懒用一套逻辑。
  4. FolderListModel.get(...) 取出来的值最好先做显式转换,否则很容易遇到 QJSValue 的类型问题。
  5. 如果 Windows 一直报 DirectShowPlayerService::doRender,那基本就不是页面逻辑的问题了,而是本地视频后端本身的兼容性边界。

所以这篇文章的最终结论就是:

  • Android 端,这套本地媒体浏览方案可以继续用 Qt5 自带播放器。
  • Windows 端,如果只是浏览图片或者外部打开视频,到这里已经够了。
  • 但如果项目要求"Windows 也要在软件内部稳定播放本地视频",那就不能继续停在 DirectShow 这条线上了。

下一篇我继续写,记录一下 Windows 下如何用一个 FFmpeg 最小系统,把本地视频播放真正接起来。

相关推荐
肖恭伟2 小时前
Cursor(VSCode) + clangd 无法跳转 Qt 类/变量
ide·vscode·qt
2501_915918412 小时前
iOS App 拿不到数据怎么办?数据解密导出到分析结构方法
android·macos·ios·小程序·uni-app·cocoa·iphone
csdn_zhangchunfeng2 小时前
Qt之智能指针使用建议
开发语言·qt
2501_916008892 小时前
iOS App 抓包看不到内容,从有请求没数据一步步排查
android·ios·小程序·https·uni-app·iphone·webview
幸福在路上wellbeing2 小时前
Kotlin 核心学习大纲(Android 开发)
android·学习·kotlin
谪星·阿凯3 小时前
文件包含与下载读取漏洞:实战进阶与场景突破
android·网络安全
冬奇Lab12 小时前
AudioRecord音频录制流程深度解析
android·音视频开发·源码阅读
alexhilton15 小时前
Jetpack Compose中的富文本输入
android·kotlin·android jetpack
兄弟加油,别颓废了。15 小时前
ctf.show_web4
android