3、第一个Pipeline:创建简单pipeline、理解pipeline状态机、运行第一个视频播放pipeline

好,咱们直接动手。这一章我带你搭第一个GStreamer pipeline。别怕,就几行命令的事。但背后的状态机逻辑,你得吃透。我在项目里见过太多人卡在这一步,pipeline起不来,一脸懵。

3.1 什么是Pipeline?说白了就是“管道”

GStreamer里的pipeline,你可以想象成一根水管。数据(视频流、音频流)从一头进去,经过各种“水龙头”(element),最后从另一头出来。每个element干一件事:读取文件、解码、转换格式、渲染画面。

这根水管怎么连?用命令或者代码。咱们先从命令行开始,最直观。

3.2 创建你的第一个Pipeline

打开终端,输入下面这行命令。我保证,你马上能看到视频在屏幕上播放。

gst-launch-1.0 filesrc location=test.mp4 ! qtdemux ! h264parse ! avdec_h264 ! videoconvert ! autovideosink

嗯,这里要注意:你得先有一个叫 test.mp4 的文件。没有的话,随便找个视频改个名也行。

这行命令干了什么?我拆开给你看:

  • filesrc:从文件系统读取数据。像个水龙头,拧开就有水流出来。
  • qtdemux:解复用器。MP4文件里视频和音频是混在一起的,它负责把视频轨道单独拎出来。
  • h264parse:解析H.264码流。把原始数据整理成解码器能吃的格式。
  • avdec_h264:解码器。把压缩的视频数据解码成原始图像帧。
  • videoconvert:格式转换。解码出来的图像格式可能跟显示设备不匹配,它负责转成能显示的格式。
  • autovideosink:自动视频输出。自动选择最合适的窗口来显示画面。

每个element之间用 ! 连接,数据就这么一级一级传下去。

我的小习惯:刚开始学的时候,我总喜欢用 gst-inspect-1.0 查看每个element的详细参数。比如 gst-inspect-1.0 videoconvert,能看到它支持哪些输入输出格式。这招排查问题特别好使。

3.3 Pipeline状态机:NULL、READY、PAUSED、PLAYING

Pipeline不是一启动就直接播放的。它要经历几个状态。我当年第一次接触时觉得这玩意儿多余,后来发现,不理解状态机,你连“为什么视频卡住了”都查不出来。

GStreamer pipeline有四个状态:

状态 含义 实际表现
NULL 初始状态,啥也没干 pipeline刚创建,还没分配任何资源
READY 资源已分配,但没开始处理数据 打开了文件、分配了内存,但还没开始解码
PAUSED 数据流已准备好,但暂停播放 解码器已经就绪,第一帧数据已经到达,但画面不更新
PLAYING 正在播放 画面动起来了,声音也出来了

状态切换是单向的:NULL → READY → PAUSED → PLAYING。反过来也一样,停止播放时:PLAYING → PAUSED → READY → NULL

为什么会这样设计?你想想看,如果直接从NULL跳到PLAYING,万一文件打不开怎么办?资源没分配好怎么办?状态机给了你一个“逐步确认”的机会。每个状态切换时,如果某个element报错,pipeline就会停下来,告诉你哪里出了问题。

避坑指南:我曾经在项目里遇到一个bug:视频播放到一半突然卡住,但没报错。排查了半天,发现是某个element在PAUSED状态下没准备好,直接切到PLAYING就崩了。后来我养成了一个习惯——每次状态切换后,都检查一下返回值。代码里用 gst_element_set_state() 时,一定要看它返回的是 GST_STATE_CHANGE_SUCCESS 还是 GST_STATE_CHANGE_FAILURE

3.4 运行第一个视频播放Pipeline

刚才的命令已经能播放视频了。但我想让你看看状态切换的过程。用下面这个命令,加个 -v 参数(verbose模式):

gst-launch-1.0 -v filesrc location=test.mp4 ! qtdemux ! h264parse ! avdec_h264 ! videoconvert ! autovideosink

你会看到终端里刷出一堆日志。其中就有状态切换的信息:

Setting pipeline to PAUSED ...
Pipeline is PREROLLING ...
Pipeline is PREROLLED ...
Setting pipeline to PLAYING ...
New clock: GstSystemClock

看到了吗?pipeline先进入PAUSED状态,进行“预卷”(preroll)。预卷的意思是:让所有element都准备好第一帧数据。等一切就绪,再切到PLAYING。这样能保证播放时不会出现“黑屏几秒”的情况。

我个人习惯用 GST_DEBUG 环境变量来调日志级别。比如想看更详细的状态切换:

GST_DEBUG=GST_STATE:5 gst-launch-1.0 filesrc location=test.mp4 ! qtdemux ! h264parse ! avdec_h264 ! videoconvert ! autovideosink

这样会打印出每个element的状态变化,调试时特别有用。

3.5 一个更完整的例子:带音频播放

刚才那个只有视频,没声音。咱们加个音频分支。用 t. 来分叉数据流:

gst-launch-1.0 filesrc location=test.mp4 ! qtdemux name=demux \
  demux.video_0 ! queue ! h264parse ! avdec_h264 ! videoconvert ! autovideosink \
  demux.audio_0 ! queue ! aacparse ! avdec_aac ! audioconvert ! autoaudiosink

这里多了几个新面孔:

  • queue:缓冲队列。视频和音频处理速度不一样,queue能防止互相拖累。
  • aacparse:解析AAC音频码流。
  • avdec_aac:AAC音频解码器。
  • audioconvert:音频格式转换。
  • autoaudiosink:自动音频输出。

注意那个 demux. 的写法。qtdemux解复用后,会生成 video_0audio_0 两个输出口(pad)。我们用 demux.video_0demux.audio_0 分别连接视频和音频处理链。

注意:如果视频文件里没有音频轨道,或者音频编码不是AAC,上面这个命令会报错。你可以用 gst-discoverer-1.0 test.mp4 先看看文件里到底有什么流。这个工具会列出所有轨道信息,包括编码格式、分辨率、码率等。

3.6 小结

这一章我们干了三件事:

  1. gst-launch-1.0 搭了第一个视频播放pipeline。
  2. 理解了四个状态:NULL、READY、PAUSED、PLAYING。说白了就是“创建→准备→就绪→开跑”。
  3. 跑了一个带音频的完整例子。

下一章,咱们会深入element的内部结构,看看pad、cap、buffer这些概念到底是怎么回事。嗯,到时候你会觉得,pipeline就像搭积木一样简单。