/* * subtitle editor * * http://kitone.free.fr/subtitleeditor/ * * Copyright @ 2005-2006, kitone * * Contact: kitone at free dot fr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * See gpl.txt for more information regarding the GNU General Public License. * * * \file * \brief * \author kitone (kitone at free dot fr) */ #include "GstLaunch.h" #include #include "debug.h" namespace Gst { /* * retourne le temps en string par rapport au temps nsecs (gstreamer) */ Glib::ustring time_to_string (gint64 time) { gchar *str = g_strdup_printf ("%u:%02u:%02u", (guint) (time / (GST_SECOND * 60 * 60)), (guint) ((time / (GST_SECOND * 60)) % 60), (guint) ((time / GST_SECOND) % 60)); Glib::ustring res(str); g_free(str); return res; } } gboolean __static_bus_message(GstBus *bus, GstMessage *message, gpointer data) { return ((GstLaunch*)data)->bus_message(bus, message); } /* bool pool_for_state_change_full(GstElement *element, GstState state, GError **error, gint64 timeout) { GstBus *bus = NULL; GstMessageType events; bus = gst_element_get_bus(element); events = (GstMessageType)(GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS); while(true) { GstMessage *message = NULL; GstElement *src = NULL; message = gst_bus_poll(bus, events, timeout); if(!message) goto timed_out; src = (GstElement*)GST_MESSAGE_SRC(message); switch(GST_MESSAGE_TYPE(message)) { case GST_MESSAGE_STATE_CHANGED: { GstState sold, snew, spending; if(src == element) { gst_message_parse_state_changed(message, &sold, &snew, &spending); if(snew == state) { gst_message_unref(message); goto success; } } }break; case GST_MESSAGE_ERROR: { gchar *debug = NULL; GError *gsterror = NULL; gst_message_parse_error(message, &gsterror, &debug); if(error) { // *error = } else { g_warning("Error: %s (%s)", gsterror->message, debug); } gst_message_unref(message); g_error_free(gsterror); g_free(debug); goto error; }break; case GST_MESSAGE_EOS: { gst_message_unref(message); goto error; }break; default: gst_message_unref(message); break; } } success: return true; timed_out: return true; error: return false; } */ /* static void event_loop (GstElement * pipe) { GstBus *bus; GstMessage *message = NULL; bus = gst_element_get_bus (GST_ELEMENT (pipe)); while (TRUE) { message = gst_bus_poll (bus, (GstMessageType)(GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS), GST_SECOND/4); //g_assert (message != NULL); if(!message) return; switch (message->type) { case GST_MESSAGE_EOS: gst_message_unref (message); return; case GST_MESSAGE_STATE_CHANGED: gst_message_unref (message); return; break; case GST_MESSAGE_WARNING: case GST_MESSAGE_ERROR:{ GError *gerror; gchar *debug; gst_message_parse_error (message, &gerror, &debug); gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug); gst_message_unref (message); g_error_free (gerror); g_free (debug); return; } default: gst_message_unref (message); break; } } } */ /* * thx totem */ void GstLaunch::static_parse_stream_info(GObject *obj, GParamSpec *pspec, GstLaunch *launch) { /* GstMessage *msg = NULL; launch->parse_stream_info(); msg = gst_message_new_application(GST_OBJECT(launch->m_pipeline), gst_structure_new("notify-streaminfo", NULL)); gst_element_post_message(launch->m_pipeline, msg); */ } /* * */ void GstLaunch::static_caps_set(GObject *obj, GParamSpec *pspec, GstLaunch *launch) { /* int video_fps_n=0, video_fps_d=0; GstPad *pad = GST_PAD(obj); GstStructure *s = NULL; GstCaps *caps = NULL; if(!(caps = gst_pad_get_negotiated_caps(pad))) return; // get video decoder caps s = gst_caps_get_structure(caps, 0); if(s) { if(!(gst_structure_get_fraction(s, "framerate", &video_fps_n, &video_fps_d) && //if(!(gst_structure_get_int(s, "framerate", &launch->m_video_fps) && gst_structure_get_int(s, "width", &launch->m_video_info.width) && gst_structure_get_int(s, "height", &launch->m_video_info.height))) return; //video_par = gst_structure_get_value(s, "pixel-aspect-ratio"); } if(video_fps_d > 0) launch->m_video_info.framerate = (video_fps_n + video_fps_d/2) / video_fps_d; //std::cout << "FPS:" << launch->m_video_fps << std::endl; gst_caps_unref(caps); launch->info_update(); */ } /* * */ void GstLaunch::parse_stream_info() { /* GList *streaminfo = NULL; GstPad *videopad = NULL; g_object_get(m_pipeline, "stream-info", &streaminfo, NULL); streaminfo = g_list_copy(streaminfo); g_list_foreach(streaminfo, (GFunc)g_object_ref, NULL); for( ; streaminfo != NULL; streaminfo = streaminfo->next) { GObject *info = G_OBJECT(streaminfo->data); gint type; GParamSpec *pspec = NULL; GEnumValue *val = NULL; if(!info) continue; g_object_get(info, "type", &type, NULL); pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(info), "type"); val = g_enum_get_value(G_PARAM_SPEC_ENUM(pspec)->enum_class, type); if(!g_strcasecmp(val->value_nick, "audio")) { m_video_info.has_audio = true; } else if(!g_strcasecmp(val->value_nick, "video")) { m_video_info.has_video = true; if(!videopad) g_object_get(info, "object", &videopad, NULL); } } if(videopad) { GstCaps *caps = NULL; if((caps = gst_pad_get_negotiated_caps(videopad))) { static_caps_set(G_OBJECT(videopad), NULL, this); gst_caps_unref(caps); } g_signal_connect(videopad, "notify::caps", G_CALLBACK(static_caps_set), this); } g_list_foreach(streaminfo, (GFunc)g_object_unref, NULL); g_list_free(streaminfo); */ } /* * constructeur * time permet de regler le temps en msecs * pour l'envoi d'information (timeout * utiliser pendant la lecture */ GstLaunch::GstLaunch(guint time) :m_pipeline(NULL), m_src(NULL), m_bus(NULL), m_timeout_t(time) { se_debug_message(SE_DEBUG_GSTREAMER, "time=%d", time); } /* * destructeur * disconnect signal_timeout et init le pipeline (GST_STATE_NULL) */ GstLaunch::~GstLaunch() { se_debug(SE_DEBUG_GSTREAMER); if(m_connection) m_connection.disconnect(); if(m_pipeline) gst_element_set_state(GST_ELEMENT(m_pipeline), GST_STATE_NULL); } /* * s'utilise comme gst-launch * l'element principale doit s'appeler "src" * exemple : playbin name=src ... */ bool GstLaunch::parse(const Glib::ustring &pipe) throw (GstLaunch::Error) { se_debug_message(SE_DEBUG_GSTREAMER, pipe.c_str()); GError *error = NULL; m_pipeline = gst_parse_launch(pipe.c_str(), &error); if(error || !m_pipeline) { se_debug_message(SE_DEBUG_GSTREAMER, "error : %s", error->message); g_error_free(error); throw PARSE_ERROR; } m_src = gst_bin_get_by_name(GST_BIN(m_pipeline), "src"); m_bus = gst_pipeline_get_bus(GST_PIPELINE(m_pipeline)); gst_bus_add_watch(GST_BUS(m_bus), __static_bus_message, this); //g_signal_connect(m_pipeline, "notify::stream-info", // G_CALLBACK(static_parse_stream_info), this); return true; } /* * gestion des messages gstreamer : * GST_MESSAGE_ERROR, GST_MESSAGE_WARNING, * GST_MESSAGE_EOS, GST_MESSAGE_STATE_CHANGED * * cette fonction est important * il faut l'appeler si on fait deriver cette fonction */ bool GstLaunch::bus_message(GstBus *bus, GstMessage *message) throw (GstLaunch::Error) { se_debug(SE_DEBUG_GSTREAMER); if(message->type == GST_MESSAGE_ERROR) { GError *error = NULL; gchar* debug = NULL; gst_message_parse_error(message, &error, &debug); GST_DEBUG("Error message: %s [%s]", GST_STR_NULL(error->message), GST_STR_NULL(debug)); se_debug_message(SE_DEBUG_GSTREAMER, "GST_MESSAGE_ERROR : %s [%s]", GST_STR_NULL(error->message), GST_STR_NULL(debug)); // signal error emit... if(m_pipeline) gst_element_set_state(GST_ELEMENT(m_pipeline), GST_STATE_NULL); g_error_free(error); g_free(debug); on_error(); //throw FAILED; } else if(message->type == GST_MESSAGE_WARNING) { GError *error = NULL; gchar* debug = NULL; gst_message_parse_warning(message, &error, &debug); GST_DEBUG("Warning message: %s [%s]", GST_STR_NULL(error->message), GST_STR_NULL(debug)); se_debug_message(SE_DEBUG_GSTREAMER, "GST_MESSAGE_WARNING : %s [%s]", GST_STR_NULL(error->message), GST_STR_NULL(debug)); g_warning ("%s [%s]", GST_STR_NULL (error->message), GST_STR_NULL (debug)); g_error_free(error); g_free(debug); } else if(message->type == GST_MESSAGE_EOS) { if(m_connection) m_connection.disconnect(); on_eos(); } else if(message->type == GST_MESSAGE_STATE_CHANGED) { GstState old_state, new_state; gst_message_parse_state_changed(message, &old_state, &new_state, NULL); if(old_state != new_state) { if(new_state <= GST_STATE_PAUSED) { if(m_connection) m_connection.disconnect(); } else if(new_state > GST_STATE_PAUSED) { if(m_timeout_t > 0) { if(!m_connection) { m_connection = Glib::signal_timeout().connect( sigc::mem_fun(*this, &GstLaunch::on_timeout), m_timeout_t); } } } if(old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED) parse_stream_info(); } } return true; } /* * le media joue t'il ? */ bool GstLaunch::is_playing() { se_debug(SE_DEBUG_GSTREAMER); if(!m_pipeline) return false; GstState state; gst_element_get_state(GST_ELEMENT(m_pipeline), &state, NULL, GST_CLOCK_TIME_NONE); return (state == GST_STATE_PLAYING); // return GST_STATE(m_pipeline) == GST_STATE_PLAYING; } /* * init pipeline a GST_STATE_NULL * (clear) */ bool GstLaunch::null() { se_debug(SE_DEBUG_GSTREAMER); if(gst_element_set_state(GST_ELEMENT(m_pipeline), GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE) { se_debug_message(SE_DEBUG_GSTREAMER, "GST_STATE_CHANGE_FAILURE"); return false; } return true; } /* * init le pipeline a GST_STATE_READY */ bool GstLaunch::ready() { se_debug(SE_DEBUG_GSTREAMER); if(gst_element_set_state(GST_ELEMENT(m_pipeline), GST_STATE_READY) == GST_STATE_CHANGE_FAILURE) { se_debug_message(SE_DEBUG_GSTREAMER, "GST_STATE_CHANGE_FAILURE"); return false; } return true; /* gst_element_set_state(GST_ELEMENT(m_pipeline), GST_STATE_READY); //return pool_for_state_change_full(m_pipeline, GST_STATE_READY, NULL, GST_SECOND/4); event_loop(m_pipeline); return true; */ } /* * init le pipeline a GST_STATE_PLAYING */ bool GstLaunch::play() { se_debug(SE_DEBUG_GSTREAMER); if(gst_element_set_state(GST_ELEMENT(m_pipeline), GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { se_debug_message(SE_DEBUG_GSTREAMER, "GST_STATE_CHANGE_FAILURE"); return false; } return true; /* gst_element_set_state(GST_ELEMENT(m_pipeline), GST_STATE_PLAYING); //return pool_for_state_change_full(m_pipeline, GST_STATE_PLAYING, NULL, GST_SECOND/4); event_loop(m_pipeline); return true; */ } /* * init le pipeline a GST_STATE_PAUSE */ bool GstLaunch::pause() { se_debug(SE_DEBUG_GSTREAMER); if(gst_element_set_state(GST_ELEMENT(m_pipeline), GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) { se_debug_message(SE_DEBUG_GSTREAMER, "GST_STATE_CHANGE_FAILURE"); return false; } return true; /* gst_element_set_state(GST_ELEMENT(m_pipeline), GST_STATE_PAUSED); // return pool_for_state_change_full(m_pipeline, GST_STATE_PAUSED, NULL, GST_SECOND/4); event_loop(m_pipeline); return true; */ } /* * retourne la longueur du media (GST_FORMAT_TIME) */ gint64 GstLaunch::get_duration() { se_debug(SE_DEBUG_GSTREAMER); g_return_val_if_fail(GST_IS_ELEMENT(m_pipeline), 0); gint64 len = 0; GstFormat fmt = GST_FORMAT_TIME; if(gst_element_query_duration(GST_ELEMENT(m_pipeline), &fmt, &len)) return len; return 0; } /* * retourne la position (lecture) dans le media */ gint64 GstLaunch::get_position() { se_debug(SE_DEBUG_GSTREAMER); g_return_val_if_fail(GST_IS_ELEMENT(m_pipeline), 0); gint64 pos = 0; GstFormat fmt = GST_FORMAT_TIME; if(gst_element_query_position(GST_ELEMENT(m_pipeline), &fmt, &pos)) return pos; return 0; } /* * retourne la position en % */ gint64 GstLaunch::get_percent() { se_debug(SE_DEBUG_GSTREAMER); g_return_val_if_fail(GST_IS_ELEMENT(m_pipeline), 0); gint64 pos = 0; GstFormat fmt = GST_FORMAT_PERCENT; if(gst_element_query_position(GST_ELEMENT(m_pipeline), &fmt, &pos)) return pos; return 0; } /* * ce deplacer a pos */ bool GstLaunch::seek(gint64 pos) { if(pos < 0) pos = 0; se_debug_message(SE_DEBUG_GSTREAMER, "%d", pos); CLAMP(pos, 0, get_duration()); /* bool res = gst_element_seek(m_pipeline, 1.0, GST_FORMAT_TIME, (GstSeekFlags)(GST_SEEK_FLAG_FLUSH), GST_SEEK_TYPE_SET, pos, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); return res; */ bool res = gst_element_seek(m_pipeline, 1.0, GST_FORMAT_TIME, (GstSeekFlags)(GST_SEEK_FLAG_FLUSH), GST_SEEK_TYPE_SET, pos, GST_SEEK_TYPE_SET, get_duration()); /* if(res) { gst_pipeline_set_new_stream_time(GST_PIPELINE(m_pipeline), 0); gst_element_get_state(GST_ELEMENT(m_pipeline), NULL,NULL, 40*GST_MSECOND); } else std::cerr << "GstLaunch::seek > seek failed" << std::endl; */ return res; } /* * ce deplacer a start et lire jusqu'a end * ne declanche pas play */ bool GstLaunch::seek(gint64 start, gint64 end) { if(start < 0) start = 0; if(end < 0) end = 0; se_debug_message(SE_DEBUG_GSTREAMER, "%d %d", start, end); if(start > end) { gint64 tmp=start; start=end; end=tmp; } /* bool res = gst_element_seek(m_pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, start, GST_SEEK_TYPE_SET, end); return res; */ bool res = gst_element_seek(m_pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, start, GST_SEEK_TYPE_SET, end); /* if(res) { gst_pipeline_set_new_stream_time(GST_PIPELINE(m_pipeline), 0); gst_element_get_state(GST_ELEMENT(m_pipeline), NULL,NULL, 40*GST_MSECOND); } else std::cerr << "GstLaunch::seek > seek failed" << std::endl; */ return res; } /* * fonction callback pour Glib::timeout() */ bool GstLaunch::on_timeout() { se_debug(SE_DEBUG_GSTREAMER); m_signal_timeout();//get_position()); return is_playing(); } /* * fonction de rappel * seulement pendant la lecture du media * configure la frequence dans le constructeur "timeout" */ sigc::signal& GstLaunch::get_signal_timeout() { se_debug(SE_DEBUG_GSTREAMER); return m_signal_timeout; } /* * emit si on arrive a la fin du fichier */ void GstLaunch::on_eos() { se_debug(SE_DEBUG_GSTREAMER); } /* * emit en cas d'erreur */ void GstLaunch::on_error() { se_debug(SE_DEBUG_GSTREAMER); } const VideoInfo& GstLaunch::get_info() { return m_video_info; } void GstLaunch::info_update() { se_debug(SE_DEBUG_GSTREAMER); }