// // Copyright (C) 2008-2009 Jordi Mas i Hernandez, jmas@softcatala.org // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #include #include #include #include #include #include #include #include "mistelix.h" typedef struct { gchar filename [1024]; unsigned int weight; unsigned int height; unsigned int frames_sec; unsigned int total_frames; unsigned int type; } ThreadParams; typedef enum { None = 0, InitParams = 1, /* Length (4 bytes) + pixels */ NewImage = 2, /* Length (4 bytes) seconds */ FixedImage = 3, /* Length (4 bytes) + nframes + pixels */ End = 4 } Command; void mistelix_socket_send (unsigned char* data, unsigned int bytes); void mistelix_socket_connect (); gpointer mistelix_launch_gstreamer (gpointer data); const gchar* address = "localhost"; const int port = 2048; static int mis_socket; ThreadParams params; char audio[1024]; GThread* thread; int started; // It is assumed that there is only one client using the library at the time // There are no handles for session, just the active session void mistelix_slideshow_createstream (const gchar* filename, unsigned int type, unsigned int weight, unsigned int height, unsigned int frames_sec, unsigned int total_frames) { #ifdef _DEBUG printf ("*** mistelix_slideshow_createstream: %s, %u, %u, %u, %u\n", filename, type, weight, height, frames_sec); #endif thread = NULL; started = 0; *audio = '\x0'; strcpy ((char*) params.filename,filename); params.weight = weight; params.height = height; params.frames_sec = frames_sec; params.total_frames = total_frames; params.type = type; } void mistelix_slideshow_add_image (unsigned char* bytes, unsigned int len) { unsigned char header[6]; unsigned char* pos = (unsigned char*) &len; int i; #ifdef _DEBUG printf ("*** mistelix_slideshow_add_image %x\n", len); #endif mistelix_check_started (); // Command header[0] = 0xff; header[1] = NewImage; // Len for (i = 0; i < sizeof (unsigned int); i++) { header[2 + i] = *pos; pos++; } mistelix_socket_send (header, 6); mistelix_socket_send (bytes, len); } void mistelix_slideshow_add_imagefixed (unsigned char* bytes, unsigned int len, unsigned int frames) { unsigned char header[10]; // 2 bytes header, 4 length buffer, 4 number of frames unsigned char* pos = (unsigned char*) &len; int i; #ifdef _DEBUG printf ("*** mistelix_slideshow_add_image_fixed %x %u\n", len, frames); #endif mistelix_check_started (); // Command header[0] = 0xff; header[1] = FixedImage; // Len for (i = 0; i < sizeof (unsigned int); i++) { header[2 + i] = *pos; pos++; } // Frames pos = (unsigned char*) &frames; for (i = 0; i < sizeof (unsigned int); i++) { header[6 + i] = *pos; pos++; } mistelix_socket_send (header, 10); mistelix_socket_send (bytes, len); } void mistelix_slideshow_close () { // Wait until the pipe line completes g_thread_join (thread); } void mistelix_slideshow_add_audio (const gchar* filename) { strcpy (audio, filename); /* Private functions */ } /* 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); } /* 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 () { if (started != 0) return; started = 1; thread = g_thread_create (mistelix_launch_gstreamer, (gpointer) ¶ms, TRUE, NULL); // TODO: To be replaced by socket timeout sleep (2); mistelix_socket_connect (); } #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_plugins_count (); char *plugins[ncodecs]; mistelix_get_plugins (plugins); for (i = 0; i < ncodecs; i++ ) { if (strcmp (name, plugins[i]) == 0) { found = TRUE; break; } } for (i = 0; i < ncodecs; i++) free (plugins [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, *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 = FALSE; char media [2048]; mistelix_check_init (); 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; } } } 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 { 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); } } 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); /* 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; } void mistelix_socket_connect () { struct sockaddr_in serveraddr; int yes = 1; #ifdef _DEBUG printf ("*** mistelix_socket_connect %s %u\n", address, port); #endif if ((mis_socket = socket (AF_INET, SOCK_STREAM, 0)) == -1) return; if (setsockopt (mis_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { close (mis_socket); return; } serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = INADDR_ANY; serveraddr.sin_port = htons (port); memset (&(serveraddr.sin_zero), '\0', 8); if (connect (mis_socket,(struct sockaddr *)&serveraddr,sizeof (serveraddr)) < 0) { printf ("*** mistelix_socket_connect error. It may be caused because not all the assumed pipe elements are present\n"); return; } printf ("*** mistelix_socket_connect %d\n", mis_socket); } void mistelix_socket_send (unsigned char* data, unsigned int bytes) { write (mis_socket, data, bytes); }