2 * This file is part of DisOrder.
3 * Copyright (C) 2018 Mark Wooding
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 /** @file plugins/tracklength-gstreamer.c
19 * @brief Plugin to compute track lengths using GStreamer
21 #include "tracklength.h"
23 #include "speaker-protocol.h"
25 /* Ugh. It turns out that libxml tries to define a function called
26 * `attribute', and it's included by GStreamer for some unimaginable reason.
27 * So undefine it here. We'll want GCC attributes for special effects, but
28 * can take care of ourselves.
34 #include <gst/app/gstappsink.h>
35 #include <gst/audio/audio.h>
37 /* The only application we have for `attribute' is declaring function
38 * arguments as being unused, because we have a lot of callback functions
39 * which are meant to comply with an externally defined interface.
42 # define UNUSED __attribute__((unused))
45 #define END ((void *)0)
47 static int inited_gstreamer
= 0;
49 enum { ST_PAUSE
, ST_PAUSED
, ST_PLAY
, ST_EOS
, ST_BOTCHED
= -1 };
51 struct tracklength_state
{
59 static void decoder_pad_arrived(GstElement
*elt
, GstPad
*pad
, gpointer p
) {
60 /* The `decodebin' element dreamed up a pad. If it's for audio output,
61 * attach it to a dummy sink so that it doesn't become sad.
64 struct tracklength_state
*state
= p
;
70 #ifdef HAVE_GSTREAMER_0_10
71 caps
= gst_pad_get_caps(pad
); if(!caps
) goto end
;
73 caps
= gst_pad_get_current_caps(pad
); if(!caps
) goto end
;
75 for(i
= 0, n
= gst_caps_get_size(caps
); i
< n
; i
++) {
76 s
= gst_caps_get_structure(caps
, i
);
77 if(!strncmp(gst_structure_get_name(s
), "audio/", 6)) goto match
;
82 padname
= gst_pad_get_name(pad
);
84 disorder_error(0, "error checking `%s': "
85 "failed to get GStreamer pad name (out of memory?)",
89 if(!gst_element_link_pads(elt
, padname
, state
->sink
, "sink")) {
90 disorder_error(0, "error checking `%s': "
91 "failed to link GStreamer elements `%s' and `sink'",
92 state
->path
, GST_OBJECT_NAME(elt
));
98 state
->state
= ST_BOTCHED
;
99 g_main_loop_quit(state
->loop
);
101 if(caps
) gst_caps_unref(caps
);
102 if(padname
) g_free(padname
);
105 static void bus_message(GstBus UNUSED
*bus
, GstMessage
*msg
, gpointer p
) {
106 /* Our bus sent us a message. If it's interesting, maybe switch state, and
107 * wake the main loop up.
110 struct tracklength_state
*state
= p
;
113 switch(GST_MESSAGE_TYPE(msg
)) {
114 case GST_MESSAGE_ERROR
:
115 /* An error. Mark the operation as botched and wake up the main loop. */
116 disorder_error(0, "error checking `%s': %s", state
->path
,
117 gst_structure_get_string(gst_message_get_structure(msg
),
119 state
->state
= ST_BOTCHED
;
120 g_main_loop_quit(state
->loop
);
122 case GST_MESSAGE_STATE_CHANGED
:
123 /* A state change. If we've reached `PAUSED', then notify the main loop.
124 * This turns out to be a point at which the duration becomes available.
125 * If not, then the main loop will set us playing.
127 gst_message_parse_state_changed(msg
, 0, &newstate
, 0);
128 if(state
->state
== ST_PAUSE
&& newstate
== GST_STATE_PAUSED
&&
129 GST_MESSAGE_SRC(msg
) == GST_OBJECT(state
->pipeline
)) {
130 g_main_loop_quit(state
->loop
);
131 state
->state
= ST_PAUSED
;
134 case GST_MESSAGE_EOS
:
135 /* The end of the stream. Wake up the loop, and set the state to mark this
136 * as being the end of the line.
138 state
->state
= ST_EOS
;
139 g_main_loop_quit(state
->loop
);
141 case GST_MESSAGE_DURATION_CHANGED
:
142 /* Something thinks it knows a duration. Wake up the main loop just in
147 /* Something else happened. Whatevs. */
152 static int query_track_length(struct tracklength_state
*state
,
155 /* Interrogate the pipeline to find the track length. Return zero on
156 * success, or -1 on failure. This is annoying and nonportable.
160 #ifdef HAVE_GSTREAMER_0_10
161 GstFormat fmt
= GST_FORMAT_TIME
;
164 #ifdef HAVE_GSTREAMER_0_10
165 if(!gst_element_query_duration(state
->pipeline
, &fmt
, &t
) ||
166 fmt
!= GST_FORMAT_TIME
)
169 if(!gst_element_query_duration(state
->pipeline
, GST_FORMAT_TIME
, &t
))
172 *length_out
= (t
+ 500000000)/1000000000;
176 long disorder_tracklength(const char UNUSED
*track
, const char *path
) {
177 /* Discover the length of a track. */
179 struct tracklength_state state
;
180 GstElement
*source
, *decode
, *sink
;
185 /* Fill in the state structure. */
187 state
.state
= ST_PAUSE
;
191 /* Set up the GStreamer machinery. */
192 if(!inited_gstreamer
) gst_init(0, 0);
194 /* Create the necessary pipeline elements. */
195 source
= gst_element_factory_make("filesrc", "file");
196 decode
= gst_element_factory_make("decodebin", "decode");
197 sink
= state
.sink
= gst_element_factory_make("fakesink", "sink");
198 state
.pipeline
= gst_pipeline_new("pipe");
199 if(!source
|| !decode
|| !sink
) {
200 disorder_error(0, "failed to create GStreamer elements: "
201 "need base and good plugins");
204 g_object_set(source
, "location", path
, END
);
206 /* Add the elements to the pipeline. It will take over responsibility for
209 gst_bin_add_many(GST_BIN(state
.pipeline
), source
, decode
, sink
, END
);
211 /* Link the elements together as far as we can. Arrange to link the decoder
212 * onto our (dummy) sink when it's ready to produce output.
214 if(!gst_element_link(source
, decode
)) {
215 disorder_error(0, "error checking `%s': "
216 "failed to link GStreamer elements `file' and `decode'",
220 g_signal_connect(decode
, "pad-added",
221 G_CALLBACK(decoder_pad_arrived
), &state
);
223 /* Fetch the bus and listen for messages. */
224 bus
= gst_pipeline_get_bus(GST_PIPELINE(state
.pipeline
));
225 gst_bus_add_signal_watch(bus
);
226 g_signal_connect(bus
, "message", G_CALLBACK(bus_message
), &state
);
228 /* Turn the handle until we have an answer. The message handler will wake us
229 * up if: the pipeline reports that the duration has changed (suggesting that
230 * something might know what it is); we successfully reach the initial
231 * `paused' state; or we hit the end of the stream (by which point we ought
232 * to know where we are).
234 state
.loop
= g_main_loop_new(0, FALSE
);
235 gst_element_set_state(state
.pipeline
, GST_STATE_PAUSED
); running
= 1;
237 g_main_loop_run(state
.loop
);
238 if(!query_track_length(&state
, &length
)) goto end
;
239 switch(state
.state
) {
240 case ST_BOTCHED
: goto end
;
242 disorder_error(0, "error checking `%s': "
243 "failed to fetch duration from GStreamer pipeline",
247 gst_element_set_state(state
.pipeline
, GST_STATE_PLAYING
);
248 state
.state
= ST_PLAY
;
254 /* Well, it's too late to worry about anything now. */
255 if(running
) gst_element_set_state(state
.pipeline
, GST_STATE_NULL
);
257 gst_bus_remove_signal_watch(bus
);
258 gst_object_unref(bus
);
261 gst_object_unref(state
.pipeline
);
263 if(source
) gst_object_unref(source
);
264 if(decode
) gst_object_unref(decode
);
265 if(sink
) gst_object_unref(sink
);
267 if(state
.loop
) g_main_loop_unref(state
.loop
);
272 int main(int argc
, char *argv
[]) {
275 for(i
= 1; i
< argc
; i
++)
276 printf("%s: %ld\n", argv
[i
], disorder_tracklength(0, argv
[i
]));