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]));