[mistelix] Audio support for Vorbis generated videos



commit 7da871b5954737e5ed481d909148653707d0157b
Author: Jordi Mas <jmas softcatala org>
Date:   Tue May 5 20:27:56 2009 +0200

    Audio support for Vorbis generated videos
---
 libmistelix/Makefile.am             |    1 +
 libmistelix/mistelix.c              |  372 +++++++++++++++++++++++++++++++----
 libmistelix/mistelix.h              |    2 +
 src/core/Dependencies.cs            |   14 +-
 src/dialogs/AudioSelectionDialog.cs |    6 +
 src/mistelix.glade                  |    1 +
 src/widgets/BrowseFile.cs           |   20 ++-
 7 files changed, 375 insertions(+), 41 deletions(-)

diff --git a/libmistelix/Makefile.am b/libmistelix/Makefile.am
index ee4b820..55825eb 100644
--- a/libmistelix/Makefile.am
+++ b/libmistelix/Makefile.am
@@ -13,6 +13,7 @@ libmistelix_la_SOURCES =		\
 noinst_HEADERS =  \
 	mistelix.h
 
+libmistelix_la_CFLAGS = $(LIBMISTELIX_CFLAGS) -Wall -Wno-unused -Wno-format
 libmistelix_la_LDFLAGS = -export-dynamic -module -avoid-version
 libmistelix_la_LIBADD = $(LIBMISTELIX_LIBS)
 
diff --git a/libmistelix/mistelix.c b/libmistelix/mistelix.c
index e45625c..9b974bf 100644
--- a/libmistelix/mistelix.c
+++ b/libmistelix/mistelix.c
@@ -23,13 +23,20 @@
 
 #include <stdio.h>
 #include <netdb.h>
+#include <unistd.h>
 #include <sys/types.h>
 #include <netinet/in.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
+#include <gst/gst.h>
+#include <sys/wait.h>
 
 #include "mistelix.h"
 
+#define SEEK_TIMEOUT 5 * GST_MSECOND
+
+/* Enable this define for debugging */
+
 //#define _DEBUG 1
 
 typedef struct 
@@ -240,7 +247,7 @@ void mistelix_launchtool (const char* app, const char* args, const char* in_file
 
 	// Parent process returns once the child has completed
 	if (pid > 0) {
-		wait ();
+		wait (NULL);
 		return;
 	}
 
@@ -517,10 +524,10 @@ cb_typefound (GstElement *typefind,
 	g_free (type);
 }
 
-//
-// Do not want to start the thread until is necessary
-// to allow for example to add an audio channel before launching it
-//
+/*
+	Do not want to start the thread until is necessary
+	to allow for example to add an audio channel before launching it
+*/
 void
 mistelix_check_started ()
 {
@@ -535,52 +542,338 @@ mistelix_check_started ()
 	mistelix_socket_connect ();
 }
 
+/*
+	Sends a seek event to a pad
+*/
+void
+send_seek_event (GstElement* pipeline, GstPad* pad, gboolean flush)
+{
+	gboolean res = FALSE;
+	GstEvent *event;
+	GstSeekFlags flags;
+
+	flags = GST_SEEK_FLAG_SEGMENT;
+
+	if (flush)
+		flags |= GST_SEEK_FLAG_FLUSH;
+
+	/* Seek from the begining */
+	event = gst_event_new_seek (1, GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, -1);
+	res = gst_pad_send_event (pad, event);
+	if (res)
+		gst_element_get_state (GST_ELEMENT (pipeline), NULL, NULL, SEEK_TIMEOUT);
+	else 
+		printf ("send_seek_event: error sending seek event\n");
+}
+
+/*
+	Monitor the EOS event for the sink pad
+	and resend it to the pipeline to finish it
+*/
+static gboolean
+gst_handle_sink_event (GstPad * pad, GstEvent * event)
+{
+	GstElement* element = (GstElement *) gst_object_get_parent (GST_OBJECT (pad));
+
+#ifdef _DEBUG
+	printf ("--->* gst_handle_sink_event %s %s -> %s (%u)\n", 
+		 gst_element_get_name (element),
+		 gst_element_get_name (pad),
+		 gst_event_type_get_name (event->type),
+		 event->type);
+#endif
+	if (event->type == GST_EVENT_EOS) {
+		GstElement* pipe = (GstElement *) gst_object_get_parent (GST_OBJECT (element));
+		GstBus *bus = gst_element_get_bus (pipe);
+		gst_bus_post (bus, gst_message_new_eos (GST_OBJECT (pipe)));
+#ifdef _DEBUG
+		printf ("Posted EOS\n");
+#endif
+	}
+	return gst_pad_event_default (pad, event);
+}
+
+#ifdef _DEBUG
+
+/*
+	Lists all the elements in a pipeline
+*/
+void
+listelements (GstBin* bin)
+{
+	gpointer point, point_pad;
+	GstIterator *it, *it_pads;
+
+	it = gst_bin_iterate_elements (bin);
+
+	printf ("----\n");
+	while (gst_iterator_next (it, &point) == GST_ITERATOR_OK) {
+		GstElement* element = GST_ELEMENT (point);
+
+		it_pads = gst_element_iterate_pads (element);
+		printf ("element -> %s\n", gst_element_get_name (element));
+
+		while (gst_iterator_next (it_pads, &point_pad) == GST_ITERATOR_OK) {
+			GstPad * pad = GST_PAD (point_pad);
+			printf ("pad-> %s\n", gst_element_get_name (pad));
+		}
+	}
+	printf ("----\n");
+}
+#endif
+
+gboolean
+mistelix_is_codec (const char* name)
+{
+	int ncodecs, i;
+	gboolean found = FALSE;
+
+	ncodecs = mistelix_get_codecs_count ();
+
+	char *codecs[ncodecs];
+	mistelix_get_codecs (codecs);
 
-// Method to launch gstreamer thread. Method signature as required by g_thread_create
+	for (i = 0; i < ncodecs; i++ )
+	{
+		if (strcmp (name, codecs[i]) == 0) {
+			found = TRUE;
+			break;
+		}
+	}
+
+	for (i = 0; i < ncodecs; i++)
+		free (codecs [i]);
+
+#ifdef _DEBUG
+	printf ("Codec %s, found %d\n", name, found);
+#endif
+	return found;
+}
+
+/*
+	Get the element name within the pipeline
+	Caller is responsible for freezing the allocated memory
+*/
+char *
+mistelix_get_element_name_from_pipeline (GstBin* pipe, char* generic_name)
+{
+	char* result = NULL;
+	gpointer point;
+	GstIterator* it;
+	int len;
+
+	len = strlen (generic_name);
+	it = gst_bin_iterate_elements (pipe);
+
+	while (gst_iterator_next (it, &point) == GST_ITERATOR_OK) {
+		GstElement* element = GST_ELEMENT (point);
+		char* name;
+
+		name = gst_element_get_name (element);
+
+		if (strncmp (name, generic_name, len) == 0) {
+			result = malloc (strlen (name) + 1);
+			strcpy (result, name);
+			break;
+		}
+	}
+#ifdef _DEBUG	
+	printf ("Seached %s, found %s\n", generic_name, result);
+#endif
+	return result;
+}
+
+/* Method to launch gstreamer thread. Method signature as required by g_thread_create */
 gpointer 
 mistelix_launch_gstreamer  (gpointer data)
 {
 	char desc [1024];
-	GstElement *pipe;
-	GstElement* elem;
+	GstElement* pipe, *element;
 	ThreadParams* params = (ThreadParams *) data;
+	gboolean has_audio;
+	GstBus *bus;
+	GstMessage *message;
+	GstStateChangeReturn ret;
+	GstPad* seekable_pad;
+	gpointer point_pad;
+	GstIterator *it_pads;
+	char* element_name;
+	int sink = 0;
+	gboolean vorbis;
+	char media [2048];
 
 	mistelix_check_init ();
 
-	// Also mpeg2enc available
-	//  theoraenc ! oggmux  for ogg
-	if (params->type == 0)  {// Theora
-		sprintf (desc, 
-			"mistelixvideosrc num-buffers=%u ! video/x-raw-yuv,format=(fourcc)I420,width=%u,height=%u,framerate=(fraction)%u/1 !theoraenc ! oggmux !filesink location=%s", 
-				params->total_frames, params->weight, params->height, params->frames_sec, params->filename);
+	has_audio = (*audio != '\x0');
+
+	if (has_audio) {
+		mistelix_detect_media (audio, media);
+
+		if (strcmp (media, "application/ogg") == 0) {
+			if (mistelix_is_codec ("vorbisdec"))
+				vorbis = TRUE;
+			else 
+				has_audio = FALSE;
+		} else {
+			if (strcmp (media, "application/x-id3") == 0) {
+				if (mistelix_is_codec ("flump3dec"))
+					vorbis = FALSE;
+				else 
+					has_audio = FALSE;
+			} else {
+				printf ("mistelix: unsupported audio format: %s\n", media);
+				has_audio = FALSE;
+			}
+		}
 	}
-	else{ // DVD
-		if (*audio == '\x0') {
-			//
-			// ffenc_mpeg2video
-			//    We use 'bitrate'element to set the quality of the generated MPEG2 video. 
-			//    Every additional 10000 bits/s represent approximately 10% additional time 
-			//    to generate the video
-			//
-			// ffmux_dvd
-			//    We use maxdelay and preload to generate MPEG2 compatible streams with DVD
-			//
-			sprintf (desc, 
-				"mistelixvideosrc num-buffers=%u ! video/x-raw-yuv,format=(fourcc)I420,width=%u,height=%u,framerate=(fraction)%u/1 ! ffenc_mpeg2video bitrate=500000 ! ffmux_dvd preload=500000 maxdelay=699999 !filesink location=%s", 
-					params->total_frames, params->weight, params->height, params->frames_sec, params->filename);
+
+	if (params->type == 0)  {/* Theora output format */
+		if (has_audio) {
+			if (vorbis) { /* Source audio in Vorbis */
+				sprintf (desc, 
+					"mistelixvideosrc num-buffers=%u ! video/x-raw-yuv,format=(fourcc)I420,width=%u,height=%u,framerate=(fraction)%u/1 !"
+					"theoraenc ! mux. filesrc location=%s ! oggdemux ! vorbisdec ! audioconvert ! vorbisenc ! "
+					"oggmux name=mux ! filesink location=%s",
+					params->total_frames, params->weight, params->height, params->frames_sec, audio, params->filename);
+			} else { /* Source audio in MP3 */
+				sprintf (desc, 
+					"mistelixvideosrc num-buffers=%u ! video/x-raw-yuv,format=(fourcc)I420,width=%u,height=%u,framerate=(fraction)%u/1 !"
+					"theoraenc ! mux. filesrc location=%s ! flump3dec ! audioconvert ! vorbisenc ! "
+					"oggmux name=mux ! filesink location=%s",
+					params->total_frames, params->weight, params->height, params->frames_sec, audio, params->filename);
+			}
 		} else {
-			// ffenc_ac3 does not seem to be recognised by ffmpeg when preparing the final MPEG
 			sprintf (desc, 
-				"mistelixvideosrc num-buffers=%u ! video/x-raw-yuv,format=(fourcc)I420,width=%u,height=%u,framerate=(fraction)%u/1 ! ffenc_mpeg2video bitrate=500000 ! queue ! mux. filesrc location=%s ! mad ! audioconvert ! ffenc_mp2 ! ffmux_dvd name=mux preload=500000 maxdelay=699999 ! filesink location=%s", 
-					params->total_frames, params->weight, params->height, params->frames_sec, audio, params->filename);
+				"mistelixvideosrc num-buffers=%u ! video/x-raw-yuv,format=(fourcc)I420,width=%u,height=%u,framerate=(fraction)%u/1 !"
+				"theoraenc ! oggmux !filesink location=%s", 
+				params->total_frames, params->weight, params->height, params->frames_sec, params->filename);
 		}
 	}	
+	else { /* DVD output format */
+
+		/*
+		 * ffenc_mpeg2video
+		 *    We use 'bitrate'element to set the quality of the generated MPEG2 video. 
+		 *    Every additional 10000 bits/s represent approximately 10% additional time 
+		 *    to generate the video
+		 *
+		 * ffmux_dvd
+		 *    We use maxdelay and preload to generate MPEG2 compatible streams with DVD
+		*/
+		
+		/* No audio support for now */
+		sprintf (desc, 
+			"mistelixvideosrc num-buffers=%u ! video/x-raw-yuv,format=(fourcc)I420,width=%u,height=%u,framerate=(fraction)%u/1 !"
+			"ffenc_mpeg2video bitrate=500000 ! ffmux_dvd preload=500000 maxdelay=699999 !filesink location=%s", 
+			params->total_frames, params->weight, params->height, params->frames_sec, params->filename);
+	}	
 #ifdef _DEBUG
 	printf ("mistelix_launch_gstreamer: %s\n", desc);
 #endif
 	pipe = gst_parse_launch (desc, NULL);
-	run_pipeline (pipe);
 
+	/* Launch pipeline */
+
+#ifdef _DEBUG
+	listelements (GST_BIN (pipe));
+	printf ("*** run_pipeline start\n");
+#endif
+
+	g_assert (pipe);
+	bus = gst_element_get_bus (pipe);
+	g_assert (bus);
+
+	gst_element_set_state (pipe, GST_STATE_PLAYING);
+
+	/* Wait for status change */
+	gst_element_get_state (pipe, NULL, NULL, SEEK_TIMEOUT);
+
+	if (has_audio) {
+
+		/* Find the pad of the audio decoder to send the audio seek events */
+		if (vorbis)
+			element_name = mistelix_get_element_name_from_pipeline (GST_BIN (pipe), "vorbisdec");
+		else
+			element_name = mistelix_get_element_name_from_pipeline (GST_BIN (pipe), "flump3dec");
+
+		g_assert (element_name);
+		element = gst_bin_get_by_name (GST_BIN (pipe), element_name);
+		free (element_name);
+
+		g_assert (element);
+		seekable_pad = gst_element_get_pad (element, "src");
+		g_assert (seekable_pad);
+
+		/* Find the muxer's sink's and set callback function  */
+		element = gst_bin_get_by_name (GST_BIN (pipe), "mux");
+		g_assert (element);
+
+		it_pads = gst_element_iterate_pads (element);
+		while (gst_iterator_next (it_pads, &point_pad) == GST_ITERATOR_OK) {
+			GstPad * pad = GST_PAD (point_pad);
+	
+			if (strncmp (gst_element_get_name (pad), "src", 3) == 0)
+				continue;
+
+			sink++;
+			if (sink < 2)
+				continue;
+
+			/* The second sink is the video sink */
+	#ifdef _DEBUG
+			printf ("Setting handler for %s\n", gst_element_get_name (pad));
+	#endif
+			gst_pad_set_event_function (pad, gst_handle_sink_event);
+			break;
+		}
+
+		send_seek_event (pipe, seekable_pad, FALSE);
+	}
+	/* We get a GST_MESSAGE_EOS when the pipe is finished */
+	while (1) {
+		GstMessageType revent;
+
+		message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 2);
+
+		if (message) {
+			revent = GST_MESSAGE_TYPE (message);
+#ifdef _DEBUG
+			printf ("*** run_pipeline message: %s (%x)\n", gst_message_type_get_name (revent), revent);
+#endif
+			gst_message_unref (message);
+		} else
+			revent = GST_MESSAGE_UNKNOWN;
+
+		if (revent == GST_MESSAGE_SEGMENT_DONE) {
+			/* Send audio segment again */
+			send_seek_event (pipe, seekable_pad, TRUE);
+			continue;
+		}
+
+		if (revent == GST_MESSAGE_ERROR) {
+#ifdef _DEBUG
+			printf ("*** run_pipeline exiting reason GST_MESSAGE_ERROR\n");
+#endif
+			break;
+		}
+
+		if (revent == GST_MESSAGE_EOS) {
+#ifdef _DEBUG
+			printf ("*** run_pipeline exiting reason: GST_MESSAGE_EOS\n");
+#endif
+			break;
+		}
+	}
+	
+	/* Need to explicitly set elements to the NULL state before dropping the final reference */
+	gst_element_set_state (pipe, GST_STATE_NULL);
+	gst_element_get_state (pipe, NULL, NULL, SEEK_TIMEOUT);
+	gst_object_unref (pipe);
+	gst_object_unref (bus);
+
+#ifdef _DEBUG
+	printf ("*** run_pipeline end\n");
+#endif
 	return NULL;
 }
 
@@ -615,8 +908,6 @@ mistelix_socket_connect ()
 	printf ("*** mistelix_socket_connect %d\n", mis_socket);
 }
 
-
-
 void 
 mistelix_socket_send (unsigned char* data, unsigned int bytes)
 {
@@ -624,6 +915,9 @@ mistelix_socket_send (unsigned char* data, unsigned int bytes)
 }
 
 
+/*
+	Runs standard pipline that ends by an EOS event
+*/	
 void
 run_pipeline (GstElement * pipe)
 {
@@ -640,8 +934,11 @@ run_pipeline (GstElement * pipe)
 	g_assert (bus);
 
 	gst_element_set_state (pipe, GST_STATE_PLAYING);
-	
-	// We get a GST_MESSAGE_EOS when the pipe is finished
+
+	/* Wait for status change */
+	gst_element_get_state (pipe, NULL, NULL, GST_CLOCK_TIME_NONE);
+
+	/* We get a GST_MESSAGE_EOS when the pipe is finished */
 	while (1) {
 		message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 2);
 
@@ -651,7 +948,8 @@ run_pipeline (GstElement * pipe)
 			printf ("*** run_pipeline message: %s (%x)\n", gst_message_type_get_name (revent), revent);
 #endif
 			gst_message_unref (message);
-		}
+		} else
+			revent = GST_MESSAGE_UNKNOWN;
 
 		if (revent == GST_MESSAGE_ERROR) {
 #ifdef _DEBUG
diff --git a/libmistelix/mistelix.h b/libmistelix/mistelix.h
index d37415f..b3bca0d 100644
--- a/libmistelix/mistelix.h
+++ b/libmistelix/mistelix.h
@@ -47,6 +47,8 @@ void mistelix_thumbnail_video (const char* filein, const char* fileout, int widt
 
 // Private (not exposed)
 
+char * mistelix_get_element_name_from_pipeline (GstBin* pipe, char* generic_name);
+
 void mistelix_detect_media (const char* file, char* media);
 
 static void cb_typefound (GstElement *typefind, guint probability, GstCaps *caps, gpointer data);
diff --git a/src/core/Dependencies.cs b/src/core/Dependencies.cs
index 88f54dc..7b83e3c 100644
--- a/src/core/Dependencies.cs
+++ b/src/core/Dependencies.cs
@@ -39,7 +39,9 @@ namespace Mistelix.Core
 			FFMUX_DVD,
 			MISTELIXVIDEOSRC,
 			THEORAENC,
-			OGGMUX
+			OGGMUX,
+			VORBISENC,
+			FLUMP3DEC,
 		};
 
 		public struct Dependency
@@ -126,7 +128,7 @@ namespace Mistelix.Core
 
 			status_checked = true;
 			List <string> codecs = MistelixLib.GetCodecs ();
-			string [] used =  {"ffenc_mpeg2video", "ffmux_dvd", "mistelixvideosrc", "theoraenc", "oggmux"};
+			string [] used =  {"ffenc_mpeg2video", "ffmux_dvd", "mistelixvideosrc", "theoraenc", "oggmux", "vorbisdec", "flump3dec"};
 			bool [] founds = new bool [used.Length];
 
 			// TODO: Better a hash table
@@ -158,7 +160,13 @@ namespace Mistelix.Core
 			DependencyList.Add (new Dependency (used [(int) Codecs.OGGMUX], founds [(int) Codecs.OGGMUX],
 				Catalog.GetString ("Missing ogg muxer. You need to install GStreamer Base Plugins.")));
 
-			// Check for application
+			DependencyList.Add (new Dependency (used [(int) Codecs.VORBISENC], founds [(int) Codecs.VORBISENC],
+				Catalog.GetString ("Missing Vorbis audio encoder. You need to install GStreamer Base Plugins.")));
+	
+			DependencyList.Add (new Dependency (used [(int) Codecs.FLUMP3DEC], founds [(int) Codecs.FLUMP3DEC],
+				Catalog.GetString ("Missing MP3 audio decoder. You need to install  Fluendo MP3 decoder GStreamer plugin.")));
+
+			// Check for applications
 			bool dvdauthor, spumux;
 	
 			dvdauthor = CheckApp ("dvdauthor");
diff --git a/src/dialogs/AudioSelectionDialog.cs b/src/dialogs/AudioSelectionDialog.cs
index 67c4878..717237c 100644
--- a/src/dialogs/AudioSelectionDialog.cs
+++ b/src/dialogs/AudioSelectionDialog.cs
@@ -46,10 +46,16 @@ namespace Mistelix.Dialogs
 		public AudioSelectionDialog () : base ("audioselection")
 		{			
 			audiofile_browser = new BrowseFile (audiofile_hbox, null, true);
+			audiofile_browser.DefaultDirectory = Mistelix.Preferences.GetStringValue (Preferences.AudioDirectoryKey);
+
 			clean_button = new Gtk.Button (Catalog.GetString ("No audio"));
 			audiofile_hbox.Add (clean_button);
 			clean_button.Clicked += new EventHandler (OnButtonClean);
 
+			Gtk.Box.BoxChild box = (Gtk.Box.BoxChild) audiofile_hbox [clean_button];
+			box.Expand = false;
+			box.Fill = false;
+
 			clean_button.ShowAll ();
 		}
 
diff --git a/src/mistelix.glade b/src/mistelix.glade
index b672c29..18b21ba 100644
--- a/src/mistelix.glade
+++ b/src/mistelix.glade
@@ -4450,6 +4450,7 @@
 </widget>
 
 <widget class="GtkDialog" id="audioselection">
+  <property name="width_request">500</property>
   <property name="visible">True</property>
   <property name="title" translatable="yes">Select audio</property>
   <property name="type">GTK_WINDOW_TOPLEVEL</property>
diff --git a/src/widgets/BrowseFile.cs b/src/widgets/BrowseFile.cs
index 5896ece..abd8380 100644
--- a/src/widgets/BrowseFile.cs
+++ b/src/widgets/BrowseFile.cs
@@ -35,6 +35,7 @@ namespace Mistelix.Widgets
 		Button browse;
 		bool browse_file;
 		Gtk.FileFilter filter;
+		string default_dir;
 
 		public virtual event EventHandler FileSelectedChanged;
 
@@ -67,6 +68,15 @@ namespace Mistelix.Widgets
 			}
 		}
 
+		public string DefaultDirectory {
+			set {
+				if (browse_file == false)
+					throw new InvalidOperationException ("Default directory can only be set when browsing files");
+				
+				default_dir = value;
+			}
+		}
+
 		public Gtk.FileFilter Filter {
 			set { filter = value; }
 		}
@@ -77,7 +87,15 @@ namespace Mistelix.Widgets
 				Catalog.GetString ("Open Location") , null,
 				browse_file ? FileChooserAction.Open : FileChooserAction.SelectFolder);
 
-			chooser_dialog.SetCurrentFolder (filename.Text);
+
+			if (browse_file) {
+				if (default_dir != null)
+					chooser_dialog.SetCurrentFolder (default_dir);
+			}
+			else {
+				chooser_dialog.SetCurrentFolder (filename.Text);
+			}
+
 			chooser_dialog.AddButton (Stock.Cancel, ResponseType.Cancel);
 			chooser_dialog.AddButton (Stock.Open, ResponseType.Ok);
 			chooser_dialog.DefaultResponse = ResponseType.Ok;



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]