惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
O
OpenAI News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 三生石上(FineUI控件)
Webroot Blog
Webroot Blog
GbyAI
GbyAI
S
SegmentFault 最新的问题
Cyberwarzone
Cyberwarzone
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
J
Java Code Geeks
Google DeepMind News
Google DeepMind News
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
博客园 - 【当耐特】
S
Secure Thoughts
酷 壳 – CoolShell
酷 壳 – CoolShell
AWS News Blog
AWS News Blog
Engineering at Meta
Engineering at Meta
S
Security Affairs
H
Help Net Security
Microsoft Security Blog
Microsoft Security Blog
D
DataBreaches.Net
云风的 BLOG
云风的 BLOG
Hugging Face - Blog
Hugging Face - Blog
Google DeepMind News
Google DeepMind News
Spread Privacy
Spread Privacy
T
Threatpost
Forbes - Security
Forbes - Security
C
Cisco Blogs
Scott Helme
Scott Helme
Attack and Defense Labs
Attack and Defense Labs
Simon Willison's Weblog
Simon Willison's Weblog
腾讯CDC
The Last Watchdog
The Last Watchdog
Cloudbric
Cloudbric
Last Week in AI
Last Week in AI
Recorded Future
Recorded Future
小众软件
小众软件
V
Vulnerabilities – Threatpost
美团技术团队
人人都是产品经理
人人都是产品经理
有赞技术团队
有赞技术团队
Apple Machine Learning Research
Apple Machine Learning Research
Hacker News - Newest:
Hacker News - Newest: "LLM"
I
Intezer
月光博客
月光博客
C
Cyber Attacks, Cyber Crime and Cyber Security
博客园 - 司徒正美
C
Cybersecurity and Infrastructure Security Agency CISA
Martin Fowler
Martin Fowler
博客园 - 聂微东

阁子

小工具(三) 相机小述 四元数与旋转矩阵 小工具(二) 私有办公服务搭建 小工具(一) 图床搭建 Git基本用法 Telegram接管聊天消息 Hashcat密码破解 Docker博客环境封装及自动化部署 小聊乐理 LXD搭设服务器 Time Machine 不满就折腾小记 GStreamer笔记五: Media Formats and Pad Capabilities GSreamer笔记四: GUI Toolkit Integration GStreamer笔记二: Dynamic Pipeline GStreamer笔记一: GStreamer Concepts
GStreamer笔记三: Time Management
本文作者: dfine · 2017-12-03 · via 阁子

发表于 | 分类于 TX1入坑 | 阅读次数

主要是关于如何使用GStreamer中的时间相关工具,如查询Pipeline的位置或者持续时间等信息,以及寻找或者跳转到Stream中的不同位置——时间点的方法。

关于GstQuery

GstQuery是GStreamer中用于查询element和pad信息的一种机制。此篇所用例中首先需要询问是否支持寻找(seek),因为有一些源,如live stream,并不支持跳转。本例在确定支持跳转后,一旦电影播放10s后,就是用seek函数跳转到一个不同的时间点。
在之前的例子中,一旦建立其了Pipeline并开始运行,主函数所做的事仅仅是坐等接收来自总线(bus)的ERROR或者EOS信息。这里将会修改这个函数来周期性的唤醒并查询Pipeline的位置,所以可以将其输出在屏幕上。有点类似于一个媒体播放器定期更新用户接口。
最终,在stream持续时间改变后就会重新查询和更新。

Seeking示例

一个有关seeking时间点的示例如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

#include <gst/gst.h>

/* Structure to contain all our information, so we can pass it around */

typedef struct _CustomData {

GstElement *playbin; /* Our one and only element */

gboolean playing; /* Are we in the PLAYING state? */

gboolean terminate; /* Should we terminate execution? */

gboolean seek_enabled; /* Is seeking enabled for this media? */

gboolean seek_done; /* Have we performed the seek already? */

gint64 duration; /* How long does this media last, in nanoseconds */

} CustomData;

/* Forward definition of the message processing function */

static void handle_message (CustomData *data, GstMessage *msg);

int main(int argc, char *argv[]) {

CustomData data;

GstBus *bus;

GstMessage *msg;

GstStateChangeReturn ret;

data.playing = FALSE;

data.terminate = FALSE;

data.seek_enabled = FALSE;

data.seek_done = FALSE;

data.duration = GST_CLOCK_TIME_NONE;

/* Initialize GStreamer */

gst_init (&argc, &argv);

/* Create the elements */

data.playbin = gst_element_factory_make ("playbin", "playbin");

if (!data.playbin) {

g_printerr ("Not all elements could be created.\n");

return -1;

}

/* Set the URI to play */

g_object_set (data.playbin, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL);

/* Start playing */

ret = gst_element_set_state (data.playbin, GST_STATE_PLAYING);

if (ret == GST_STATE_CHANGE_FAILURE) {

g_printerr ("Unable to set the pipeline to the playing state.\n");

gst_object_unref (data.playbin);

return -1;

}

/* Listen to the bus */

bus = gst_element_get_bus (data.playbin);

do {

msg = gst_bus_timed_pop_filtered (bus, 100 * GST_MSECOND,

GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_DURATION);

/* Parse message */

if (msg != NULL) {

handle_message (&data, msg);

} else {

/* We got no message, this means the timeout expired */

if (data.playing) {

gint64 current = -1;

/* Query the current position of the stream */

if (!gst_element_query_position (data.playbin, GST_FORMAT_TIME, &current)) {

g_printerr ("Could not query current position.\n");

}

/* If we didn't know it yet, query the stream duration */

if (!GST_CLOCK_TIME_IS_VALID (data.duration)) {

if (!gst_element_query_duration (data.playbin, GST_FORMAT_TIME, &data.duration)) {

g_printerr ("Could not query current duration.\n");

}

}

/* Print current position and total duration */

g_print ("Position %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT "\r",

GST_TIME_ARGS (current), GST_TIME_ARGS (data.duration));

/* If seeking is enabled, we have not done it yet, and the time is right, seek */

if (data.seek_enabled && !data.seek_done && current > 10 * GST_SECOND) {

g_print ("\nReached 10s, performing seek...\n");

gst_element_seek_simple (data.playbin, GST_FORMAT_TIME,

GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 30 * GST_SECOND);

data.seek_done = TRUE;

}

}

}

} while (!data.terminate);

/* Free resources */

gst_object_unref (bus);

gst_element_set_state (data.playbin, GST_STATE_NULL);

gst_object_unref (data.playbin);

return 0;

}

static void handle_message (CustomData *data, GstMessage *msg) {

GError *err;

gchar *debug_info;

switch (GST_MESSAGE_TYPE (msg)) {

case GST_MESSAGE_ERROR:

gst_message_parse_error (msg, &err, &debug_info);

g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message);

g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");

g_clear_error (&err);

g_free (debug_info);

data->terminate = TRUE;

break;

case GST_MESSAGE_EOS:

g_print ("End-Of-Stream reached.\n");

data->terminate = TRUE;

break;

case GST_MESSAGE_DURATION:

/* The duration has changed, mark the current one as invalid */

data->duration = GST_CLOCK_TIME_NONE;

break;

case GST_MESSAGE_STATE_CHANGED: {

GstState old_state, new_state, pending_state;

gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);

if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) {

g_print ("Pipeline state changed from %s to %s:\n",

gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));

/* Remember whether we are in the PLAYING state or not */

data->playing = (new_state == GST_STATE_PLAYING);

if (data->playing) {

/* We just moved to PLAYING. Check if seeking is possible */

GstQuery *query;

gint64 start, end;

query = gst_query_new_seeking (GST_FORMAT_TIME);

if (gst_element_query (data->playbin, query)) {

gst_query_parse_seeking (query, NULL, &data->seek_enabled, &start, &end);

if (data->seek_enabled) {

g_print ("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n",

GST_TIME_ARGS (start), GST_TIME_ARGS (end));

} else {

g_print ("Seeking is DISABLED for this stream.\n");

}

}

else {

g_printerr ("Seeking query failed.");

}

gst_query_unref (query);

}

}

} break;

default:

/* We should not reach here */

g_printerr ("Unexpected message received.\n");

break;

}

gst_message_unref (msg);

}

该段代码会打开并显示一个伴有音频的一个电影,由于媒体来自于网络,所以窗口可能需要一会才能显示出来,具体取决于网速。并在电影十秒钟后跳转到一个新的位置。

代码分析

首先建立一个可以传递给其他函数的含有所有信息的结构体:

1

2

3

4

5

6

7

8

9

10

11

12

/* Structure to contain all our information, so we can pass it around */

typedef struct _CustomData {

GstElement *playbin; /* Our one and only element */

gboolean playing; /* Are we in the PLAYING state? */

gboolean terminate; /* Should we terminate execution? */

gboolean seek_enabled; /* Is seeking enabled for this media? */

gboolean seek_done; /* Have we performed the seek already? */

gint64 duration; /* How long does this media last, in nanoseconds */

} CustomData;

/* Forward definition of the message processing function */

static void handle_message (CustomData *data, GstMessage *msg);

这里由于信息处理代码会变得越来越大,因此将其移到了handle_message函数中。
然后建立了一个包含单个元素(playbin)的Pipeline,然而playbin本身就是一个Pipeline,而且这种情况下,他是Pipeline中唯一的element,所以直接使用playbin。
这里跳过一些细节:该clip的URI通过URI属性给playbin,并将Pipeline设置为播放状态。

1

2

msg = gst_bus_timed_pop_filtered (bus, 100 * GST_MSECOND,

GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_DURATION);

之前没有给gst_bus_timed_pop_filtered函数提供超时参数,因此它在收到消息前不会返回。这里使用100ms的超时,所以如果在0.1s内没有收到任何消息,函数将返回NULL,并通过这个方法来更新UI。
需要注意的是,所有的超时时间必须指定为GstClockTime,所以都是以纳秒为单位的,表示不同时间单位的数字应该乘以宏如GST_SECOND或GST_MSECOND。也能使代码更具可读性。
如果收到消息,则通过handle_message函数处理它。否则刷新用户接口(UI)。

用户接口刷新

1

2

/* We got no message, this means the timeout expired */

if (data.playing) {

如果Pipeline处于PLAYING状态,则刷新屏幕。在非PLAYING状态下我们不想做任何事,因为大多数查询都会失败。
这里的刷新率大约是每秒10次,对于我们的UI来说已经足够。同时将在屏幕上打印出当前媒体的位置以便了解管道查询。这涉及到几个步骤,之后再说,但是位置和持续时间是比较常见的查询,所以GStreamer提供了更容易的现成的备选方案:

1

2

3

4

/* Query the current position of the stream */

if (!gst_element_query_position (data.pipeline, GST_FORMAT_TIME, &current)) {

g_printerr ("Could not query current position.\n");

}

其中,gst_element_query_position函数隐藏了查询对象的管理并直接提供结果。

1

2

3

4

5

6

/* If we didn't know it yet, query the stream duration */

if (!GST_CLOCK_TIME_IS_VALID (data.duration)) {

if (!gst_element_query_duration (data.pipeline, GST_FORMAT_TIME, &data.duration)) {

g_printerr ("Could not query current duration.\n");

}

}

其中,gst_element_query_duration用于函数查询流的长度。

1

2

3

/* Print current position and total duration */

g_print ("Position %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT "\r",

GST_TIME_ARGS (current), GST_TIME_ARGS (data.duration));

这里使用GST_TIME_FORMATGST_TIME_ARGS宏来提供对GStreamer时间的对用户友好的表示。

1

2

3

4

5

6

7

/* If seeking is enabled, we have not done it yet, and the time is right, seek */

if (data.seek_enabled && !data.seek_done && current > 10 * GST_SECOND) {

g_print ("\nReached 10s, performing seek...\n");

gst_element_seek_simple (data.pipeline, GST_FORMAT_TIME,

GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 30 * GST_SECOND);

data.seek_done = TRUE;

}

现在在管道上调用gst_element_seek_simple函数进行查找,这种方法的好处是隐藏了许多复杂的问题。

参数回顾

GST_FORMAT_TIME: 表示以时间单位指定目标位置,其他的查找格式使用不同的单位。
然后是一些GstSeekFlags,其中常见的一些如下:

  • GST_SEEK_FLAG_FLUSH: 会在seek之前丢弃当前Pipeline中的所有数据。当Pipeline被重新填充且新的数据开始出现时,可能会暂停一下,但是极大增加了应用程序的响应能力。因为如果没有这个标志,旧数据可能会一直显示,直到新的数据出现在Pipeline末端。
  • GST_SEEK_FLAG_KEY_UNIT: 对于大多数编码视频流,寻找到任意位置是不可能的,因为仅限于某些称为关键帧的帧。使用这个标识时,seek操作实际上会移动到最近的关键帧并开始产生数据。不使用这个标志的话,Pipeline将会在内部移动到最近的关键帧(没有其他选择),但是直到到达要求的位置才会显示数据。后面一种方法更精确,但是可能需要更长的时间。
  • GST_SEEK_FLAG_ACCURATE: 查找精度标识。在一些媒体clips没有提供足够的索引信息时,查找任意位置是耗时的。在这些情况下,GStreamer通常会估计要寻找的位置,而且完成的比较好。如果这个精度对于你的情况来说不够好(看到的不是要求的精确时间),则提供该标识。但是值得注意的是,计算寻找位置可能花费更长时间(在一些文件中很长)。
    最后提供了查找的位置。因为要求了GST_FORMAT_TIME,所以值需要用纳秒表示。为了简单起见,用秒表示时间并乘以GST_SECOND。

信息处理

handle_message函数通过管道总线(Pipeline’s bus)处理接收到的所有信息。ERROR和EOS处理之前已经说过了,所以直接跳到感兴趣的部分:

1

2

3

4

case GST_MESSAGE_DURATION:

/* The duration has changed, mark the current one as invalid */

data->duration = GST_CLOCK_TIME_NONE;

break;

该消息在流的持续时间变化时会发送给总线。这里简单地将当前持续时间标记为无效,所以稍后会被重新查询。

1

2

3

4

5

6

7

8

9

case GST_MESSAGE_STATE_CHANGED: {

GstState old_state, new_state, pending_state;

gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);

if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) {

g_print ("Pipeline state changed from %s to %s:\n",

gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));

/* Remember whether we are in the PLAYING state or not */

data->playing = (new_state == GST_STATE_PLAYING);

在PAUSED和PLAYING状态下,搜索和查询操作通常只会得到一个有效的回复,因为所有元素都有机会接收信息并进行自我配置。这里使用playing变量来跟踪管道是否处于PLAYING状态。如果刚刚进入了PLAYING状态,则执行第一次查询。然后询问Pipeline是否允许在此流上进行搜索:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

if (data->playing) {

/* We just moved to PLAYING. Check if seeking is possible */

GstQuery *query;

gint64 start, end;

query = gst_query_new_seeking (GST_FORMAT_TIME);

if (gst_element_query (data->pipeline, query)) {

gst_query_parse_seeking (query, NULL, &data->seek_enabled, &start, &end);

if (data->seek_enabled) {

g_print ("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n",

GST_TIME_ARGS (start), GST_TIME_ARGS (end));

} else {

g_print ("Seeking is DISABLED for this stream.\n");

}

}

else {

g_printerr ("Seeking query failed.");

}

gst_query_unref (query);

}

这里gst_query_new_seeking函数使用GST_FORMAT_TIME格式创建了一个新的“seeking”类型的查询对象。这表明我们有兴趣通过指定想要移动的新时间来寻找。也可以使用GST_FORMAT_BYTE格式,然后在源文件中查找特定的字节位置,不过通常不太实用。
然后gst_element_query函数将查询对象传递给Pipeline,并将结果存储在同一个查询中,因此可以通过gst_query_parse_seeking函数方便的检索。它提取出一个表示是否允许查询的布尔值和可查找的范围。
最后在完成查询后释放查询对象。
通过这些过程基本上可以建立一个媒体播放器,根据当前流的位置定期更新一个滑块,并允许通过滑块进行搜索或跳转。