ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GStreamer入门:从playbin开始构建你的第一个音视频应用

GStreamer入门:从playbin开始构建你的第一个音视频应用 1. 为什么GStreamer的第一个应用要从playbin开始1.1 playbin这个名字背后的一段老历史很多人第一次接触GStreamer都是被Your first application这个题目吸引过来的。但真到动手写代码的时候反而会困惑GStreamer不是号称多媒体框架吗怎么第一个应用只有二十来行代码就用了一个不知道哪来的playbin先别急着怀疑这其实是GStreamer设计上的一种刻意安排。GStreamer把整个媒体处理过程拆成一个个element元素这些element像乐高积木一样串联成pipeline管道。你本可以手动创建源、解码器、视频转换器、音频转换器、渲染器然后一个一个连接起来。但GStreamer官方教程里第一个应用不是这么干的它直接用playbin这个大颗粒元素搞定了一切。我用一句话说明playbin是什么它是一个自动化的bin元素内部帮你把文件读取 → 解封装 → 音视频解码 → 同步 → 渲染输出这一整套流程都封装好了。你只需要给它一个URI告诉它去播这个地址它就能自省媒体的格式自动挑合适的插件组起一条流水线。这就像你去餐厅吃饭第一个应用教你的不是从种小麦、揉面团开始做面条而是教你先学会点单。等你知道这顿饭是怎么上来的再去后厨学每道工序也不迟。1.2 教程先跑通再谈原理的价值踩过C语言坑或者做过嵌入式开发的人应该都懂如果一门框架的新手教程上来就让你管理几十个element的状态、自己处理所有pad的negotiation协商多半学两天就放弃了。GStreamer的Your first application真正的价值是快速建立起正反馈写一个小程序编译看到视频窗口弹出、画面播放、声音出来这个过程越短越好。你只有先知道一个能跑的完整程序长什么样之后在调试自定义pipeline时才有一个可以对比的基准。而且这个第一个应用里涉及的核心概念——GstElement、GstBus、GstState、gst_parse_launch——恰恰是整个GStreamer开发使用频率最高的四个东西。后面所有复杂应用不管你是做视频剪辑工具、直播推流、还是边缘设备上的RTSP拉流底层都绕不开这几个基础概念。所以这篇博文我打算带你把Your first application从代码到编译再到调试完整过一遍顺便把我在实际项目里踩过的坑一并拿出来讲特别是那些官方教程里不会写、但你在真实环境中几乎一定会碰到的细节。2. 开发环境准备装好GStreamer只是第一步2.1 不同平台的安装差异不管你是Ubuntu、CentOS还是macOS安装GStreamer开发包这件事本身不算难难的是你得搞明白到底要装哪些包。如果只是跑通第一个应用理论上只需要gstreamer1.0和libgstreamer1.0-dev这两个核心包。但注意如果你的系统里只有核心库播放常见的MP4、WebM媒体时大概率会报missing element错误。因为playbin只是框架真正处理H.264、AAC、MP3这些格式的是外挂的解码插件。以Ubuntu/Debian为例一个比较稳妥的安装组合是sudo apt-get install libgstreamer1.0-dev gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly libgstreamer-plugins-base1.0-dev我建议在跑第一个应用之前就把这些base、good、bad、ugly插件包都装上。很多人以为bad和ugly名字不好听就没装结果后面播放H.264视频或者MP3时直接炸。名字唬人实际意思是质量不一定多好但能用以及可能涉及专利但不影响你本地开发测试它们包含了解码常见格式必需的插件。macOS用户可以直接用Homebrewbrew install gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly装完后先别急着写代码打开终端验证一下gst-inspect-1.0 --version如果这个命令能跑出版本号说明核心库已经就位。再执行gst-inspect-1.0 playbin能输出playbin的详细信息说明plugin系统正常工作。这一步很多教程都不会强调但它能提前拦截掉百分之八十的我代码明明没写错为什么跑不起来的尴尬问题。2.2 pkg-config是连接代码和库的桥梁写GStreamer程序时头文件路径和链接库路径通常不用你手动指定pkg-config会帮你搞定。所以环境准备的第二个重点是学会读pkg-config的输出。pkg-config --cflags --libs gstreamer-1.0以我的Ubuntu 22.04环境为例输出大概是-I/usr/include/gstreamer-1.0 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -pthread -lgstreamer-1.0 -lgobject-2.0 -lglib-2.0注意两个细节第一GStreamer依赖GLib所以cflags里会带上glib相关的头文件路径这在编译时是不能少的第二链接参数里除了-lgstreamer-1.0之外还有-lgobject-2.0和-lglib-2.0因为GStreamer的对象模型是建立在GObject之上的。如果你在自己的机器上执行上述命令后输出为空或者报Package gstreamer-1.0 not found不要急着怀疑代码先回头检查开发包是否真的装了。我遇到过太多次这种情况——库装的是运行时版本runtime缺少带头文件和.pc文件的-dev包pkg-config自然找不到。2.3 版本问题1.x和0.10是天壤之别环境准备里必须提一嘴版本。GStreamer 0.10是早已停止维护的旧版本而1.0是2012年发布的接口重构版本两者API差异很大。我见过有人拿着十年前博客的0.10代码改了gst_thread之类的类名去问为什么链接失败其实不是他写错了而是那套API在1.x里整个被删掉了。现在所有新项目都基于GStreamer 1.x目前稳定版本大概是1.22/1.24你只要确保gst-inspect-1.0 --version显示的是1.x开头就没问题。如果系统默认给装了0.10建议直接用包管理器卸载干净再装1.x避免两个版本的库在系统里互相干扰。提示查看pkg-config也能帮忙排除版本混乱。pkg-config --modversion gstreamer-1.0直接输出版本号一目了然。3. 第一个应用一个能播片的20行C程序3.1 完整代码与逐行拆解环境就绪后新建一个basic-tutorial-1.c。下面是GStreamer官方Your first application示例的完整代码我稍微加了一些注释和边界处理#include gst/gst.h int main(int argc, char *argv[]) { GstElement *pipeline; GstBus *bus; GstMessage *msg; GError *error NULL; gchar *debug_info NULL; /* 1. 初始化GStreamer同时会解析命令行里的GStreamer专用参数 */ gst_init(argc, argv); /* 2. 用gst_parse_launch把一个URI塞给playbin */ pipeline gst_parse_launch( playbin urihttps://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm, error); if (!pipeline) { g_printerr(Parse error: %s\n, error-message); g_clear_error(error); return 1; } /* 3. 让pipeline进入Playing状态这会触发真正的播放流程 */ gst_element_set_state(pipeline, GST_STATE_PLAYING); /* 4. 从bus上取消息直到收到ERROR或EOSEnd of Stream */ bus gst_element_get_bus(pipeline); msg gst_bus_timed_pop_filtered( bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS ); /* 5. 处理消息 */ if (msg ! NULL) { GstMessageType type GST_MESSAGE_TYPE(msg); if (type GST_MESSAGE_ERROR) { gst_message_parse_error(msg, error, debug_info); g_printerr(Error received from element %s: %s\n, GST_OBJECT_NAME(msg-src), error-message); g_printerr(Debugging information: %s\n, debug_info ? debug_info : none); g_clear_error(error); g_free(debug_info); } else if (type GST_MESSAGE_EOS) { g_print(End of stream reached.\n); } } /* 6. 善后释放消息、bus、pipeline */ gst_message_unref(msg); gst_object_unref(bus); gst_element_set_state(pipeline, GST_STATE_NULL); gst_object_unref(pipeline); return 0; }别被这一堆代码吓到整个程序的骨架可以理解为三步创建流水线 → 跑起来 → 等消息。下面逐段解释关键部分。gst_init(argc, argv)做的事比较多包括注册所有插件、初始化GLib类型系统、解析命令行参数。在这个示例里你体会不到它的重要性但等你以后在命令行工具里需要传GStreamer的参数比如--gst-debug-level3时就明白为什么一定要把argc和argv传进去了。gst_parse_launch是GStreamer提供给开发者的快速管道语法。你给它一段字符串它内部帮你解析、创建元素、连接pad。这里的playbin uri...就是告诉playbin把媒体源设置为后面的HTTPS地址。这个函数的实现会把错误信息详细填到GError结构体里所以一定要检查返回值不能假设它每次都成功。gst_element_set_state(pipeline, GST_STATE_PLAYING)是触发状态机切换的入口。GStreamer里的element有四个主要状态GST_STATE_NULL、GST_STATE_READY、GST_STATE_PAUSED、GST_STATE_PLAYING。从NULL到PLAYING不是瞬间完成的中间会经过READY和PAUSED。对于playbin这种复杂元素它内部还会协调所有子元素完成媒体信息的探测和动态管线搭建。gst_element_get_bus拿到的是pipeline的消息总线。我在实际项目中几乎所有的错误处理、流结束检测、标签信息读取都是靠监听这条bus上的消息实现的。这段示例代码用的是阻塞式等待gst_bus_timed_pop_filtered会卡住当前线程直到出现ERROR或EOS消息才返回。这个设计对第一个应用来说没问题因为它唯一的任务就是播放直到结束或出错。但如果你要做有界面的播放器这种阻塞方式就不合适了需要用非阻塞的bus watch这个我们在后面会谈。3.2 编译运行找到官方示例的正确用法编译命令很简单关键是别手写头文件路径gcc basic-tutorial-1.c -o basic-tutorial-1 $(pkg-config --cflags --libs gstreamer-1.0)如果你的系统里GStreamer是1.22编译完成后直接运行./basic-tutorial-1正常情况是弹出一个窗口播放一段大约52秒的Sintel开源电影预告片有画面有声音播完后程序打印End of stream reached.然后退出。很多人在这里会犯一个想当然的错误以为窗口必须先用某个库创建。实际上playbin内部自动创建了视频渲染sink在桌面上通常是autovideosink它会自己弹一个窗口显示视频画面。所以第一个应用里你不需要手动创建任何UI窗口。如果运行后系统提示找不到域名、连接超时也不要慌因为默认示例用了远程HTTPS地址。国内网络环境下访问放出来的资源速度不稳定我通常会把URI换成自己局域网里的流媒体地址或者直接用本地文件./basic-tutorial-1 # 如果想省事可以直接修改代码里的uri为本地文件 playbin urifile:///home/user/Videos/test.mp4file://协议GStreamer原生支持注意路径必须是绝对路径本地文件名建议不要带空格否则URI解析阶段容易出各种奇怪问题。3.3 跑通了然后呢你需要看到的输出如果你严格按照上面的操作跑通了恭喜你已经完成了GStreamer开发的第一个闭环。但我建议你别急着关掉终端再做两件事。第一件事是运行程序时在命令行加上GStreamer调试参数./basic-tutorial-1 --gst-debug-level3你会看到终端里刷出大量调试日志。这些日志看起来像黑话但它们其实是理解pipeline内部行为的最好入口。比如你会看到playbin内部在考虑用什么source插件、什么demuxer、什么decoder最终选择了哪个。这些日志在下一节排查问题时会发挥巨大作用。第二件事是用gst-launch-1.0跑一个类似效果的命令gst-launch-1.0 playbin urihttps://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webmgst-launch-1.0是GStreamer自带的一个命令行播放工具它本质上就是gst_parse_launch的一个封装。如果你的程序跑不通先用这个命令验证一下环境和插件是否正常可以帮你快速缩小问题范围。4. 踩坑实录编译报错、播放无声音/视频的排查链路4.1 undefined reference系列——链接错误的完整排查我在带新人入门GStreamer时遇到最频繁的编译错误就是undefined reference to gst_init这类。这种错误九成九不是代码问题而是编译命令里的库链接参数缺了。先说几个我实际看到的场景对比错误信息根本原因解决方式undefined reference to gst_init-lgstreamer-1.0缺失用pkg-config自动添加fatal error: gst/gst.h: No such file or directory头文件路径缺失安装libgstreamer1.0-devrpath: /usr/local/lib相关警告自定义安装路径没进ldconfig执行sudo ldconfig很多人在Ubuntu上习惯自己编译安装GStreamer这样gst-inspect-1.0都能跑但gcc编译时就是找不到头文件。这通常是因为手动编译安装时头文件被放到了/usr/local/include/gstreamer-1.0而pkg-config没去那边搜索。这个时候你可以在~/.bashrc里加上export PKG_CONFIG_PATH/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH export LD_LIBRARY_PATH/usr/local/lib:$LD_LIBRARY_PATH但我的建议是新手阶段优先用系统包管理器安装等你在项目里确实有自定义编译GStreamer的需求时再改环境变量也不迟。系统装好后pkg-config路径默认配置好的少操很多心。还有一点容易忽略编译命令里-o后面的输出名不要和.c源文件名重名。我见过有人执行gcc basic-tutorial-1.c -o basic-tutorial-1.c直接把源文件冲掉当场傻眼。4.2 视频黑屏或提示Missing Elements的问题定位第一个应用如果编译成功、运行也不报错但屏幕上没有画面或者直接提示类似There is no element xxx那问题多半出在插件缺失上。这里有一个快速定位招式就是上gst-inspect-1.0去检查具体插件是否在这个系统里存在。比如你怀疑H.264解码器没装可以执行gst-inspect-1.0 | grep -i h264如果输出里能看到类似avdec_h264或者vaapidecodebin等条目说明有H.264解码能力。如果是空的那就要补插件包。播放MPEG-TS流缺mpegtsdemux播放RTSP缺rtspsrc每个问题都有对应的插件包经验多了基本一眼定位。另一种情况是编译运行都正常播放本地文件也有声音但是视频窗口全黑。这种情况我碰到的多半是视频渲染器选择了不兼容的格式。你可以强制指定使用某个视频sink来验证比如在parse_launch语法里做如下替换playbin urifile:///home/user/Videos/test.mp4 video-sinkximagesink如果换成ximagesink就能显示画面说明默认的autovideosink在你的显示环境下协商出了问题。在纯命令行或者没有X11环境的服务器上这种问题尤其常见。注意playbin的属性可以直接在gst_parse_launch的URI语法里设置这是很多初学者不知道的。常用的还有audio-sinkalsasink、video-sinkwaylandsink等这比在代码里调g_object_set更直观适合快速做实验。4.3 GST_DEBUG是你在GStreamer世界里的探针如果说前两个问题还属于环境范畴那么你在GStreamer开发中真正要长期依赖的调试工具就是GST_DEBUG环境变量。GST_DEBUG可以细粒度控制不同级别的日志输出。最常用的几个级别是0不输出错误之外的日志1错误ERROR2警告WARNING3信息INFO4调试DEBUG5全量日志LOG比如你在排错时可以这样跑GST_DEBUG3 ./basic-tutorial-1如果你想关注特定类别GStreamer支持类别:级别的语法。例如想只看playbin相关的调试信息其他一律屏蔽GST_DEBUGplaybin:4,.*:2 ./basic-tutorial-1这个语法中的.*是通配符表示除此之外的类别使用默认级别2。别小看这个技巧当你的pipeline里有20个element时全量日志会刷屏刷到你找不到重点按类别过滤能大幅度提高排查效率。我还经常用GST_DEBUGGST_REFCOUNTING:5来排查对象引用计数泄漏。在长时间运行的服务型应用里GStreamer的对象泄漏是绕不开的坎通过refcount日志可以看到谁多占了一次引用、谁没有释放。5. 从playbin走向真实项目如何拆解和扩展第一个应用5.1 从playbin换成自定义pipeline跑通playbin之后你迟早会遇到它搞不定的场景。比如你想对视频做特效叠加、想转码成特定格式、想自己控制RTSP推流这时候playbin这种全自动黑盒就不够用了你需要亲手组装pipeline。从playbin过渡到自定义pipeline我建议先从filesrc开始练手而不是一步到位去搞rtspsrc或者拼接复杂的传输协议。下面是一个最简单的文件播放pipelinepipeline gst_parse_launch( filesrc location/home/user/Videos/test.mp4 ! qtdemux namedemux demux.video_0 ! queue ! decodebin ! videoconvert ! autovideosink demux.audio_0 ! queue ! decodebin ! audioconvert ! autoaudiosink, error);这段语法拆解开来如下filesrc从本地文件读取字节流qtdemux解析MP4/MOV容器格式把音视频分流到不同padqueue在流之间增加缓冲解决音视频互相阻塞的问题decodebin自动选择解码器相当于一个小号的playbinvideoconvert/audioconvert做像素格式和采样格式转换保证sink能接受数据autovideosink/autoaudiosink自动选择可用的视频/音频输出设备这个pipeline的功能其实和playbin类似但你能看到每个环节、能单独替换某一个element这才是你走向真实项目的基础。5.2 加入事件处理和状态管理第一个应用里用了gst_bus_timed_pop_filtered做阻塞式等待这在真实项目里几乎不会用。真实场景中你可能需要保持视频一直播放同时UI线程还要响应用户操作这时候就要在bus上安装一个watch回调。static gboolean bus_callback(GstBus *bus, GstMessage *msg, gpointer data) { GstElement *pipeline GST_ELEMENT(data); switch (GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_ERROR: // 这里通常需要通知UI层弹错误提示 break; case GST_MESSAGE_EOS: // 播完后的收尾动作 break; case GST_MESSAGE_STATE_CHANGED: // 状态变化事件注意过滤只关心pipeline自身的状态变化 if (GST_ELEMENT(msg-src) pipeline) { // 可以在这里记录播放状态 } break; default: break; } return TRUE; }然后把gst_bus_timed_pop_filtered换成bus gst_element_get_bus(pipeline); gst_bus_add_watch(bus, bus_callback, pipeline); gst_object_unref(bus);这样bus消息会通过GLib的main loop分发你的进程可以继续做其他事情。注意返回TRUE表示保留watch返回FALSE会移除watch。状态管理方面也要养成习惯。很多人会直接gst_element_set_state(pipeline, GST_STATE_NULL)了事但更稳妥的做法是先暂停再空置gst_element_set_state(pipeline, GST_STATE_PAUSED); /* 或者做更多清理工作 */ gst_element_set_state(pipeline, GST_STATE_NULL);对于包含视频渲染sink的应用来说直接从PLAYING切到NULL偶尔会有窗口来不及关闭的问题先暂停可以给sink一个相对平缓的过渡时间。5.3 学习路线建议从Your first application到你自己的第一个正式项目我的建议路径是这样的先扎实理解playbin花时间看gst-inspect-1.0 playbin输出里那些属性uri、video-sink、audio-sink、flags都是干什么用的。玩熟gst-launch-1.0的命令行语法这是在不用写代码的情况下最快学会pipeline组装的方式。碰到一个需求先用命令行验证确认可行后再写C代码能省下大量编译调试时间。从本地文件播放到网络流播放把filesrc换成rtspsrc、httpsrc理解这些source element接收数据的方式差异。读懂日志和bin结构学会用gst-launch-1.0 --gst-debug-no-color -v配合GST_DEBUG来观察每个element的pad连接情况。最后做一个综合小项目比如一个支持暂停、拖动进度、音量控制的播放器或者一个把USB摄像头画面推到RTMP服务器的推流器。我个人在实际操作中的建议是不要一上来就抱着《GStreamer Application Development Manual》从第1章啃到第18章那是工具书不是小说。最好的方式是像这个教程的标题一样先搭起你的first application然后带着我想往里面加什么功能的问题反过来查手册。碰到一个概念就搞清楚一个概念这样积累起来的速度反而最快。最后再分享一个我常用的调试小技巧给gst_parse_launch的字符串末尾加上! fakesink可以把整条pipeline跑起来但丢弃所有数据。在排查到底是上游数据没出来还是下游渲染失败这种问题时这个手段简直百试百灵。你把一条pipeline的各个节点插上taps去观察数据流动远比对着日志猜高效得多。
返回列表