如何通过gtk_media_file_new_for_input_stream实现GTK4视频播放?
问题场景与代码
我正尝试用Python开发一款带嵌入式媒体播放器的GTK4应用,用于流媒体播放,当前代码如下:
def async_run(func, *args): thread = Thread(target=func, args=args) thread.start() class VideoStream: def __init__(self): self.bytes_len =0 self.memory_stream = Gio.MemoryInputStream() self.media_file = Gtk.MediaFile.new_for_input_stream(self.memory_stream) def write(self, input_bytes:bytes): self.bytes_len += len(input_bytes) gbytes = GLib.Bytes(input_bytes) self.memory_stream.add_bytes(gbytes) def get_gtk_memory_stream(self): return self.media_file def start() -> Gtk.Video: async_run(_impl_start_stream) video_stream.get_gtk_memory_stream().play() return Gtk.Video.new_for_media_stream(video_stream.get_gtk_memory_stream())
_impl_start_stream负责加载视频数据并多次调用VideoStream.write,通过async_run在另一个线程执行避免阻塞UI更新。但调用start()并将生成的部件添加到UI后,播放器显示带叉的界面,播放按钮无响应。
疑问点
- 是否是文件类型或编解码问题?我预期它仅支持部分文件类型,但不确定具体支持哪些格式,当前尝试加载.mp4,但文档未明确说明媒体流支持的格式;
- 异步加载是否存在问题?若有必要,我可以将整个流加载到文件再播放,但这会大幅增加视频启动时的延迟;
- 视频加载时是否需要对播放器进行操作?目前我没有从
_impl_start_stream线程强制刷新UI或设置播放器属性,但猜测可能需要相关操作。
我的GTK4版本为4.6.6(通过pkg-config --modversion gtk4查询)。
[编辑]重装系统后,程序现在崩溃并抛出以下错误:
ERROR:../modules/media/gtkgstmediafile.c:299:gtk_gst_media_file_open: code should not be reached
Bail out! ERROR:../modules/media/gtkgstmediafile.c:299:gtk_gst_media_file_open: code should not be reached
改为从文件加载时无错误,但播放时长显示为0。
问题分析与解决
1. 格式与编解码支持问题
GTK4的媒体播放依赖GStreamer,支持的格式完全由系统安装的GStreamer插件决定。.mp4通常需要gstreamer1.0-plugins-good和gstreamer1.0-plugins-bad(针对H.264/AAC编码的mp4)。可以通过终端运行gst-inspect-1.0 mp4或gst-inspect-1.0 h264检查是否有对应插件,缺失的话安装对应插件包即可。
2. 异步加载的核心问题
你的代码存在两个关键问题:
- GIO流线程安全问题:
Gio.MemoryInputStream并非线程安全,不能直接在非GTK主线程调用add_bytes,必须通过GLib的主线程调度器执行; - 过早调用play():启动线程后立刻调用
play(),此时内存流中还没有足够的视频数据(甚至可能为空),GStreamer无法识别媒体格式,导致播放失败或崩溃。
修复方案:
- 使用
GLib.idle_add()将write操作调度到主线程执行; - 等待加载足够的头部数据(比如前几KB)后再调用
play(),或者监听Gtk.MediaFile的loaded信号,在信号触发后启动播放。
修改后的VideoStream.write示例:
def write(self, input_bytes:bytes): def _add_bytes(): self.bytes_len += len(input_bytes) gbytes = GLib.Bytes(input_bytes) self.memory_stream.add_bytes(gbytes) return False # 只执行一次 GLib.idle_add(_add_bytes)
3. 播放器必要操作与崩溃修复
你遇到的code should not be reached崩溃,是因为Gtk.MediaFile初始化时输入流为空且无可用媒体数据,触发了GStreamer内部的断言失败。解决关键:
- 确保创建
Gtk.MediaFile前,内存流中已有至少媒体文件的头部数据;或者 - 先创建空的
Gtk.MediaFile,写入足够数据后调用media_file.reload()重新解析媒体。
另外,从文件加载时长显示为0,是因为GStreamer需要读取文件索引信息,确保文件完全加载或调用media_file.reload()让GStreamer重新解析媒体元数据。
优化后的流程建议
- 在后台线程加载视频头部数据(比如前100KB);
- 通过
GLib.idle_add()将头部数据写入内存流; - 调用
media_file.reload()解析媒体格式; - 监听
loaded信号,触发后调用play(); - 后续视频数据持续通过
GLib.idle_add()写入内存流。
内容的提问来源于stack exchange,提问作者pi_squared

