Skip to content

Commit a4b94bb

Browse files
committed
add support for feeding gstreamer pipelines into folk image frames
1 parent ac95fc6 commit a4b94bb

File tree

2 files changed

+234
-0
lines changed

2 files changed

+234
-0
lines changed

test/gstreamer.tcl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
loadVirtualPrograms [list "virtual-programs/gstreamer.folk" "virtual-programs/images.folk"]
2+
Step
3+
4+
# namespace eval Pipeline $::makePipeline
5+
# set pl [Pipeline::create "videotestsrc"]
6+
# Pipeline::play $pl
7+
# set img [Pipeline::frame $pl]
8+
# Pipeline::freeImage $img
9+
# Pipeline::destroy $pl
10+
11+
When the gstreamer pipeline "videotestsrc" frame is /frame/ at /ts/ {
12+
Wish the web server handles route "/gst-image/$" with handler [list apply {{im} {
13+
set filename "/tmp/web-image-frame.png"
14+
image saveAsPng $im $filename
15+
set fsize [file size $filename]
16+
set fd [open $filename r]
17+
fconfigure $fd -encoding binary -translation binary
18+
set body [read $fd $fsize]
19+
close $fd
20+
dict create statusAndHeaders "HTTP/1.1 200 OK\nConnection: close\nContent-Type: image/png\nContent-Length: $fsize\n\n" body $body
21+
}} $frame]
22+
}
23+
24+
forever { Step }

virtual-programs/gstreamer.folk

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
set makePipeline {
2+
rename [c create] cc
3+
4+
cc cflags {*}[exec pkg-config --cflags --libs gstreamer-1.0]
5+
cc include <gst/gst.h>
6+
cc include <assert.h>
7+
8+
proc defineGObjectType {cc type cast} {
9+
set cc [uplevel {namespace current}]::$cc
10+
$cc argtype $type* [format {
11+
%s* $argname;
12+
GObject* _$argname;
13+
sscanf(Tcl_GetString($obj), "(%s) 0x%%p", &_$argname);
14+
$argname = %s(_$argname);
15+
} $type $type $cast]
16+
17+
# Tcl_ObjPrintf doesn't work with %lld/%llx for some reason,
18+
# so we do it by hand.
19+
$cc rtype $type* [format {
20+
$robj = Tcl_ObjPrintf("(%s) 0x%%" PRIxPTR, (uintptr_t) G_OBJECT($rvalue));
21+
} $type]
22+
}
23+
24+
defineImageType cc
25+
defineGObjectType cc GstElement GST_ELEMENT
26+
defineGObjectType cc GstBus GST_BUS
27+
28+
cc struct pipeline_t {
29+
GstElement* pipeline;
30+
GstElement* sink;
31+
GstBus* bus;
32+
}
33+
34+
cc struct frame_t {
35+
bool valid;
36+
uint64_t timestamp;
37+
image_t image;
38+
}
39+
40+
cc code {
41+
void log_messages(GstBus* bus) {
42+
GstMessage* msg;
43+
GError *err = NULL;
44+
gchar *dbg_info = NULL;
45+
while ((msg = gst_bus_pop_filtered(bus, GST_MESSAGE_ERROR | GST_MESSAGE_WARNING))) {
46+
switch (GST_MESSAGE_TYPE (msg)) {
47+
case GST_MESSAGE_ERROR: {
48+
gst_message_parse_error(msg, &err, &dbg_info);
49+
g_printerr("ERROR from element %s: %s\n", GST_OBJECT_NAME(msg->src), err->message);
50+
g_printerr("Debugging info: %s\n", (dbg_info) ? dbg_info : "none");
51+
g_error_free(err);
52+
g_free(dbg_info);
53+
break;
54+
}
55+
case GST_MESSAGE_WARNING: {
56+
gst_message_parse_warning(msg, &err, &dbg_info);
57+
g_printerr("WARNING from element %s: %s\n", GST_OBJECT_NAME(msg->src), err->message);
58+
g_printerr("Debugging info: %s\n", (dbg_info) ? dbg_info : "none");
59+
g_error_free(err);
60+
g_free(dbg_info);
61+
break;
62+
}
63+
default:
64+
break;
65+
}
66+
}
67+
}
68+
}
69+
70+
cc proc destroy {pipeline_t p} void {
71+
gst_object_unref(p.bus);
72+
gst_object_unref(p.sink);
73+
gst_element_set_state(p.pipeline, GST_STATE_NULL);
74+
gst_object_unref(p.pipeline);
75+
}
76+
77+
cc proc create {char* srcdec} pipeline_t {
78+
GError* err = NULL;
79+
gst_init(NULL, NULL);
80+
81+
char buf[512];
82+
snprintf(buf, sizeof(buf), "%s ! videoconvert ! appsink caps=video/x-raw,format=RGBA name=output drop=true max-buffers=1", srcdec);
83+
GstElement* pipeline = gst_parse_launch(buf, &err);
84+
if (err) {
85+
g_printerr("ERROR launching gst pipeline: %s\n", err->message);
86+
FOLK_ERROR("Error launching pipeline");
87+
}
88+
89+
pipeline_t p;
90+
p.pipeline = pipeline;
91+
p.bus = gst_element_get_bus(p.pipeline);
92+
p.sink = gst_bin_get_by_name(GST_BIN(p.pipeline), "output");
93+
log_messages(p.bus);
94+
95+
return p;
96+
}
97+
98+
cc proc play {pipeline_t p} void {
99+
GstState state;
100+
gst_element_set_state(p.pipeline, GST_STATE_PLAYING);
101+
gst_element_get_state(p.pipeline, &state, NULL, GST_CLOCK_TIME_NONE);
102+
log_messages(p.bus);
103+
104+
if (state != GST_STATE_PLAYING) {
105+
g_printerr("ERROR launching gst pipeline: pipeline failed to start\n");
106+
destroy(p);
107+
FOLK_ERROR("Error starting pipeline playback");
108+
}
109+
}
110+
111+
if {[namespace exists ::Heap]} {
112+
cc import ::Heap::cc folkHeapAlloc as folkHeapAlloc
113+
cc import ::Heap::cc folkHeapFree as folkHeapFree
114+
} else {
115+
cc code {
116+
#define folkHeapAlloc malloc
117+
#define folkHeapFree free
118+
}
119+
}
120+
cc proc frame {pipeline_t p} frame_t {
121+
frame_t frame;
122+
123+
GstSample* sample;
124+
g_signal_emit_by_name(p.sink, "pull-sample", &sample);
125+
FOLK_CHECK(sample, "pipeline playback stopped");
126+
127+
GstCaps* caps = gst_sample_get_caps(sample);
128+
// gst_println("caps are %" GST_PTR_FORMAT, caps);
129+
130+
GstStructure* s = gst_caps_get_structure(caps, 0);
131+
FOLK_ENSURE(gst_structure_get_int(s, "width", (gint*)&frame.image.width));
132+
FOLK_ENSURE(gst_structure_get_int(s, "height", (gint*)&frame.image.height));
133+
const gchar* format = gst_structure_get_string(s, "format");
134+
if (g_str_equal(format, "RGB")) {
135+
frame.image.components = 3;
136+
} else if (g_str_equal(format, "RGBA")) {
137+
frame.image.components = 4;
138+
} else {
139+
g_printerr("frame: invalid cap format '%s'\n", format);
140+
FOLK_ERROR("invalid cap format");
141+
}
142+
frame.image.bytesPerRow = frame.image.width * frame.image.components;
143+
144+
GstMapInfo map;
145+
GstBuffer* buffer = gst_sample_get_buffer(sample);
146+
gst_buffer_map(buffer, &map, GST_MAP_READ);
147+
148+
frame.image.data = folkHeapAlloc(map.size);
149+
memmove(frame.image.data, map.data, map.size);
150+
frame.timestamp = (uint64_t) GST_BUFFER_DTS(buffer);
151+
152+
gst_buffer_unmap(buffer, &map);
153+
gst_sample_unref(sample);
154+
155+
return frame;
156+
}
157+
158+
cc proc freeImage {image_t image} void {
159+
folkHeapFree(image.data);
160+
}
161+
162+
cc compile
163+
}
164+
165+
set ::pipelineIndex 0
166+
When when the gstreamer pipeline /pl/ frame is /frame/ at /ts/ /lambda/ with environment /e/ {
167+
Start process "gstreamer-[incr ::pipelineIndex]" {
168+
Wish $::thisProcess shares statements like \
169+
[list /someone/ claims the gstreamer pipeline /...anything/]
170+
171+
namespace eval Pipeline $makePipeline
172+
173+
try {
174+
set pipe [Pipeline::create $pl]
175+
Commit { Claim the gstreamer pipeline $pl is starting }
176+
Pipeline::play $pipe
177+
Commit { Claim the gstreamer pipeline $pl is playing with time 0 }
178+
} on error e {
179+
Commit {
180+
Claim the gstreamer pipeline $pl is stopped
181+
Claim the gstreamer pipeline $pl has error $e
182+
}
183+
}
184+
185+
set ::oldFrames [list]
186+
When the gstreamer pipeline $pl is playing with time /t/ &\
187+
$::thisProcess has step count /c/ {
188+
try {
189+
set frame [Pipeline::frame $pipe]
190+
dict with frame {
191+
Commit {
192+
Claim the gstreamer pipeline $pl is playing with time $timestamp
193+
Claim the gstreamer pipeline $pl frame is $image at [clock milliseconds]
194+
}
195+
196+
lappend ::oldFrames $image
197+
if {[llength $::oldFrames] >= 10} {
198+
set ::oldFrames [lassign $::oldFrames oldestFrame]
199+
Pipeline::freeImage $oldestFrame
200+
}
201+
}
202+
} on error e {
203+
Commit {
204+
Claim the gstreamer pipeline $pl is stopped
205+
Claim the gstreamer pipeline $pl has error $e
206+
}
207+
}
208+
}
209+
}
210+
}

0 commit comments

Comments
 (0)