├── Python ├── __init__.py └── simple-media-player.py ├── C ├── Makefile ├── simple-recording.c ├── tee-recording-and-display.c └── dynamic-recording.c ├── .gitignore ├── README.md └── dynamic_rtsp.cpp /Python/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /C/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=$(shell pkg-config --cflags gstreamer-1.0 glib-2.0 gobject-2.0) 2 | LDLIBS=$(shell pkg-config --libs gstreamer-1.0 glib-2.0 gobject-2.0) 3 | CFLAGS+=-g -Wall 4 | 5 | all: simple-recording tee-recording-and-display dynamic-recording 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | modules.order 49 | Module.symvers 50 | Mkfile.old 51 | dkms.conf 52 | .idea/ -------------------------------------------------------------------------------- /Python/simple-media-player.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import gi 4 | 5 | gi.require_version('Gst', '1.0') 6 | gi.require_version('Gtk', '3.0') 7 | from gi.repository import Gst, Gtk, GObject 8 | 9 | 10 | class MediaPlayer(Gtk.Window): 11 | def __init__(self): 12 | window = Gtk.Window(Gtk.WindowType.TOPLEVEL) 13 | window.set_default_size(300, -1) 14 | window.connect("delete-event", Gtk.main_quit) 15 | 16 | self.box = Gtk.VBox() 17 | window.add(self.box) 18 | 19 | self.entry = Gtk.Entry() 20 | self.entry.set_tooltip_text("Relative path to audio/video file") 21 | self.box.pack_start(self.entry, False, True, 0) 22 | 23 | self.button = Gtk.Button("Start") 24 | self.button.connect("clicked", self.start_stop) 25 | self.box.add(self.button) 26 | window.show_all() 27 | 28 | self.player = Gst.ElementFactory.make("playbin", "player") 29 | bus = self.player.get_bus() 30 | bus.add_signal_watch() 31 | bus.connect("message", self.on_message) 32 | 33 | def start_stop(self, w): 34 | if self.button.get_label() == "Start": 35 | path = self.entry.get_text().strip() 36 | 37 | if os.path.isfile(path): 38 | path = os.path.realpath(path) 39 | self.button.set_label("Stop") 40 | self.player.set_property("uri", "file://" + path) 41 | self.player.set_state(Gst.State.PLAYING) 42 | 43 | else: 44 | self.player.set_state(Gst.State.NULL) 45 | self.button.set_label("Start") 46 | 47 | def on_message(self, bus, message): 48 | t = message.type 49 | 50 | if t == Gst.MessageType.EOS: 51 | self.player.set_state(Gst.State.NULL) 52 | self.button.set_label("Start") 53 | elif t == Gst.MessageType.ERROR: 54 | self.player.set_state(Gst.State.NULL) 55 | err, debug = message.parse_error() 56 | print("Error: %s" % err, debug) 57 | self.button.set_label("Start") 58 | 59 | 60 | Gst.init(None) 61 | MediaPlayer() 62 | GObject.threads_init() 63 | Gtk.main() 64 | -------------------------------------------------------------------------------- /C/simple-recording.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // gst-launch-1.0 v4l2src ! x264enc ! mp4mux ! filesink location=/home/rish/Desktop/okay.264 -e 8 | static GMainLoop *loop; 9 | static GstElement *pipeline, *src, *encoder, *muxer, *sink; 10 | static GstBus *bus; 11 | 12 | static gboolean 13 | message_cb (GstBus * bus, GstMessage * message, gpointer user_data) 14 | { 15 | switch (GST_MESSAGE_TYPE (message)) { 16 | case GST_MESSAGE_ERROR:{ 17 | GError *err = NULL; 18 | gchar *name, *debug = NULL; 19 | 20 | name = gst_object_get_path_string (message->src); 21 | gst_message_parse_error (message, &err, &debug); 22 | 23 | g_printerr ("ERROR: from element %s: %s\n", name, err->message); 24 | if (debug != NULL) 25 | g_printerr ("Additional debug info:\n%s\n", debug); 26 | 27 | g_error_free (err); 28 | g_free (debug); 29 | g_free (name); 30 | 31 | g_main_loop_quit (loop); 32 | break; 33 | } 34 | case GST_MESSAGE_WARNING:{ 35 | GError *err = NULL; 36 | gchar *name, *debug = NULL; 37 | 38 | name = gst_object_get_path_string (message->src); 39 | gst_message_parse_warning (message, &err, &debug); 40 | 41 | g_printerr ("ERROR: from element %s: %s\n", name, err->message); 42 | if (debug != NULL) 43 | g_printerr ("Additional debug info:\n%s\n", debug); 44 | 45 | g_error_free (err); 46 | g_free (debug); 47 | g_free (name); 48 | break; 49 | } 50 | case GST_MESSAGE_EOS:{ 51 | g_print ("Got EOS\n"); 52 | g_main_loop_quit (loop); 53 | gst_element_set_state (pipeline, GST_STATE_NULL); 54 | g_main_loop_unref (loop); 55 | gst_object_unref (pipeline); 56 | g_print("Saved video file\n"); 57 | exit(0); 58 | break; 59 | } 60 | default: 61 | break; 62 | } 63 | 64 | return TRUE; 65 | } 66 | 67 | void sigintHandler(int unused) { 68 | g_print("Sending EoS\n"); 69 | gst_element_send_event(pipeline, gst_event_new_eos()); 70 | } 71 | 72 | int main(int argc, char *argv[]) 73 | { 74 | if (argc != 2) { 75 | g_printerr("Enter commandline argument file-path to save recorded video to.\nExample : ./a.out /home/xyz/Desktop/recorded.mp4\n"); 76 | return -1; 77 | } 78 | 79 | 80 | signal(SIGINT, sigintHandler); 81 | gst_init (&argc, &argv); 82 | 83 | pipeline = gst_pipeline_new(NULL); 84 | src = gst_element_factory_make("v4l2src", NULL); 85 | encoder = gst_element_factory_make("x264enc", NULL); 86 | muxer = gst_element_factory_make("mp4mux", NULL); 87 | sink = gst_element_factory_make("filesink", NULL); 88 | 89 | if (!pipeline || !src || !encoder || !muxer || !sink) { 90 | g_error("Failed to create elements"); 91 | return -1; 92 | } 93 | 94 | g_object_set(sink, "location", argv[1], NULL); 95 | gst_bin_add_many(GST_BIN(pipeline), src, encoder, muxer, sink, NULL); 96 | if (!gst_element_link_many(src, encoder, muxer, sink, NULL)){ 97 | g_error("Failed to link elements"); 98 | return -2; 99 | } 100 | 101 | loop = g_main_loop_new(NULL, FALSE); 102 | 103 | bus = gst_pipeline_get_bus(GST_PIPELINE (pipeline)); 104 | gst_bus_add_signal_watch(bus); 105 | g_signal_connect(G_OBJECT(bus), "message", G_CALLBACK(message_cb), NULL); 106 | gst_object_unref(GST_OBJECT(bus)); 107 | 108 | gst_element_set_state(pipeline, GST_STATE_PLAYING); 109 | 110 | g_print("Starting loop\n"); 111 | g_main_loop_run(loop); 112 | 113 | return 0; 114 | } 115 | -------------------------------------------------------------------------------- /C/tee-recording-and-display.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // v4l2src ! tee name=t t. ! x264enc ! mp4mux ! filesink location=/home/rish/Desktop/okay.264 t. ! videoconvert ! autovideosink 8 | 9 | static GMainLoop *loop; 10 | static GstElement *pipeline, *src, *tee, *encoder, *muxer, *filesink, *videoconvert, *videosink, *queue_record, *queue_display; 11 | static GstBus *bus; 12 | 13 | static gboolean 14 | message_cb (GstBus * bus, GstMessage * message, gpointer user_data) 15 | { 16 | GError *err = NULL; 17 | gchar *name, *debug = NULL; 18 | 19 | switch (GST_MESSAGE_TYPE (message)) { 20 | case GST_MESSAGE_ERROR: 21 | name = gst_object_get_path_string (message->src); 22 | gst_message_parse_error (message, &err, &debug); 23 | 24 | g_printerr ("ERROR: from element %s: %s\n", name, err->message); 25 | if (debug != NULL) 26 | g_printerr ("Additional debug info:\n%s\n", debug); 27 | 28 | g_error_free (err); 29 | g_free (debug); 30 | g_free (name); 31 | 32 | g_main_loop_quit (loop); 33 | break; 34 | case GST_MESSAGE_WARNING: 35 | name = gst_object_get_path_string (message->src); 36 | gst_message_parse_warning (message, &err, &debug); 37 | 38 | g_printerr ("ERROR: from element %s: %s\n", name, err->message); 39 | if (debug != NULL) 40 | g_printerr ("Additional debug info:\n%s\n", debug); 41 | 42 | g_error_free (err); 43 | g_free (debug); 44 | g_free (name); 45 | break; 46 | case GST_MESSAGE_EOS: 47 | g_print ("Got EOS\n"); 48 | g_main_loop_quit (loop); 49 | gst_element_set_state (pipeline, GST_STATE_NULL); 50 | g_main_loop_unref (loop); 51 | gst_object_unref (pipeline); 52 | g_print("Saved video file\n"); 53 | exit(0); 54 | break; 55 | 56 | default: 57 | break; 58 | } 59 | 60 | return TRUE; 61 | } 62 | 63 | void sigintHandler(int unused) 64 | { 65 | g_print("Sending EoS!\n"); 66 | gst_element_send_event(pipeline, gst_event_new_eos()); 67 | } 68 | 69 | int main(int argc, char *argv[]) 70 | { 71 | 72 | if (argc != 2) { 73 | g_printerr("Enter commandline argument file-path to save recorded video to.\nExample : ./a.out /home/xyz/Desktop/recorded.mp4\n"); 74 | return -1; 75 | } 76 | 77 | signal(SIGINT, sigintHandler); 78 | gst_init (&argc, &argv); 79 | 80 | pipeline = gst_pipeline_new(NULL); 81 | src = gst_element_factory_make("v4l2src", NULL); 82 | tee = gst_element_factory_make("tee", "tee"); 83 | encoder = gst_element_factory_make("x264enc", NULL); 84 | muxer = gst_element_factory_make("mp4mux", NULL); 85 | filesink = gst_element_factory_make("filesink", NULL); 86 | videoconvert = gst_element_factory_make("videoconvert", NULL); 87 | videosink = gst_element_factory_make("autovideosink", NULL); 88 | queue_display = gst_element_factory_make("queue", "queue_display"); 89 | queue_record = gst_element_factory_make("queue", "queue_record"); 90 | 91 | if (!pipeline || !src || !tee || !encoder || !muxer || !filesink || !videoconvert || !videosink || !queue_record || !queue_display) { 92 | g_error("Failed to create elements"); 93 | return -1; 94 | } 95 | 96 | g_object_set(filesink, "location", argv[1], NULL); 97 | g_object_set(encoder, "tune", 4, NULL); /* important, the encoder usually takes 1-3 seconds to process this. Queue buffer is generally upto 1 second. Hence, set tune=zerolatency (0x4) */ 98 | 99 | gst_bin_add_many(GST_BIN(pipeline), src, tee, queue_record, encoder, muxer, filesink, queue_display, videoconvert, videosink, NULL); 100 | if (!gst_element_link_many(src, tee, NULL) 101 | || !gst_element_link_many(tee, queue_record, encoder, muxer, filesink, NULL) 102 | || !gst_element_link_many(tee, queue_display, videoconvert, videosink, NULL)) { 103 | g_error("Failed to link elements"); 104 | return -2; 105 | } 106 | 107 | loop = g_main_loop_new(NULL, FALSE); 108 | 109 | bus = gst_pipeline_get_bus(GST_PIPELINE (pipeline)); 110 | gst_bus_add_signal_watch(bus); 111 | g_signal_connect(G_OBJECT(bus), "message", G_CALLBACK(message_cb), NULL); 112 | gst_object_unref(GST_OBJECT(bus)); 113 | 114 | gst_element_set_state(pipeline, GST_STATE_PLAYING); 115 | 116 | g_print("Starting loop\n"); 117 | g_main_loop_run(loop); 118 | 119 | return 0; 120 | } 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## GStreamer Cookbook 2 | 3 | The GStreamer API is difficult to work with. Remember, data in GStreamer flows through pipelines quite analogous to the way water flows through pipes. 4 | 5 | This repository is a collection of C snippets and commandline pipelines using the GStreamer 1.0 API to perform video operations. 6 | 7 | ## Capturing & Recording 8 | - The webcam (v4l2src) as the input stream 9 | - Autovideosink is used to display. In these examples, autovideosink is preceded by videoconvert. 10 | - Recording is H.264 encoded using the x264enc element. 11 | - Mux the recording with mp4mux to make the output an mp4 file. 12 | 13 | #### Let's display a video 14 | `gst-launch-1.0 v4l2src ! videoconvert ! autovideosink` 15 | 16 | The most basic pipeline to display video from webcam to a window on the screen. 17 | 18 | 19 | #### Now let's record a video 20 | `gst-launch-1.0 v4l2src ! x264enc ! mp4mux ! filesink location=/home/xyz/Desktop/recorded.mp4 -e` 21 | 22 | A basic pipeline to record video from webcam to a file on specified location. The `-e` tage instructs GStreamer to flush EoS(End of Stream) before closing the recorded stream. This allows proper closing of the saved file. 23 | 24 | [Implementation of recording a video](/C/simple-recording.c). 25 | 26 | #### Display Recorded Video 27 | `gst-launch-1.0 filesrc location=/home/xyz/Desktop/recorded.mp4 ! decodebin ! videoconvert ! autovideosink` 28 | 29 | A pipeline to display the file saved using the above pipeline. This uses the element `decodebin` which is a super-powerful all-knowing decoder. It decodes any stream of a legal format to the format required by the next element in the pipeline, automagically. 30 | 31 | #### Display + Record Video Together 32 | 33 | `gst-launch-1.0 v4l2src ! tee name=t t. ! queue ! x264enc ! mp4mux ! filesink location=/home/rish/Desktop/okay.264 t. ! queue ! videoconvert ! autovideosink` 34 | 35 | A pipeline to display and record the incoming webcam stream. This uses another powerful element called `tee`. The `tee` can be thought of as a T-joint, splitting the source into two or more sub-pipes. Remember, you must put a `queue` element after each branch to provide separate threads to each branch. 36 | 37 | [Implementation of recording + displaying a video using tee](/C/tee-recording-and-display.c). 38 | 39 | #### Start/Stop Recording at will 40 | 41 | Here comes one of the more difficult parts of GStreamer. Dynamic pipelines. These are useful in cases where you would like to alwasy display the stream, but record at will (say on the click of a button). You cannot use a command line pipeline for this. 42 | 43 | [Implementation of dynamic pipelines in C](/C/dynamic-recording.c). 44 | 45 | Use Ctrl+C to start and stop streaming. Every time you start a new recording, a fresh file is created with a new name. 46 | 47 | ## Streaming 48 | 49 | Streaming can be of various formats and from various sources. 50 | 51 | #### Raw Streaming 52 | 53 | For streaming a raw unencoded for example: 54 | 55 | *Source:* 56 | `gst-launch-1.0 -v videotestsrc ! rtpvrawpay ! udpsink host="127.0.0.1" port=5000` 57 | 58 | *Client:* 59 | `gst-launch-1.0 -v udpsrc port=5000 caps = "application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)RAW, width=(string)640, height=(string)480, payload=(int)96, sampling=(string)RGBA, depth=(string)8, a-framerate=30/1" ! rtpvrawdepay ! videoconvert ! queue ! xvimagesink sync=false` 60 | 61 | *Note:* Setting caps in the client is absolutely necessary. It is also advisable to set them in the source. 62 | 63 | #### H264 Encoded Streaming 64 | 65 | *Source:* 66 | `gst-launch-1.0 -v videotestsrc ! rtpvrawpay ! udpsink host="127.0.0.1" port=5000` 67 | 68 | *Client:* 69 | `gst-launch-1.0 -v udpsrc port=5000 caps = "application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)H264, width=(string)640, height=(string)480, payload=(int)96, a-framerate=30/1" ! rtph264depay ! decodebin ! videoconvert ! queue ! xvimagesink sync=false` 70 | 71 | 72 | ## Installing GStreamer 1.0 packages for Ubuntu 73 | 74 | - Open `terminal` and copy-paste the following lines to install all the required packages: 75 | ``` 76 | sudo apt install python-gi python3-gi \ 77 | gstreamer1.0-tools \ 78 | gir1.2-gstreamer-1.0 \ 79 | gir1.2-gst-plugins-base-1.0 \ 80 | gstreamer1.0-plugins-good \ 81 | gstreamer1.0-plugins-ugly \ 82 | gstreamer1.0-plugins-bad \ 83 | gstreamer1.0-libav 84 | ``` 85 | 86 | 87 | #### Other useful commands 88 | 89 | ###### Displaying MJPEG stream 90 | I needed this for the action camera which only gave out mjpeg. 91 | 92 | ``` 93 | gst-launch-1.0 -v v4l2src device=/dev/video0 ! image/jpeg,width=640,height=480,framerate=30/1 ! jpegparse ! jpegdec ! videoconvert ! xvimagesink sync=False 94 | ``` 95 | 96 | -------------------------------------------------------------------------------- /C/dynamic-recording.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /* 10 | 11 | GST_DEBUG=3 \ 12 | gst-launch-1.0 videotestsrc pattern=ball background-color=0x80808080 \ 13 | num-buffers=100 "video/x-raw,framerate=5/1" ! \ 14 | tee name=t \ 15 | t. ! queue ! x264enc tune=zerolatency ! matroskamux ! filesink location=264.mkv \ 16 | t. ! autovideosink 17 | 18 | See also: 19 | https://gstreamer.freedesktop.org/documentation/tutorials/basic/dynamic-pipelines.html?gi-language=c 20 | https://coaxion.net/blog/2014/01/gstreamer-dynamic-pipelines/ 21 | https://github.com/sdroege/gst-snippets/blob/217ae015aaddfe3f7aa66ffc936ce93401fca04e/dynamic-filter.c 22 | https://gstreamer.freedesktop.org/documentation/x264/index.html?gi-language=c#x264enc-page 23 | */ 24 | 25 | static GMainLoop *loop; 26 | static GstElement *pipeline, *src, *tee, *encoder, *muxer, *filesink, *videoconvert, *videosink, *queue_record, *queue_display; 27 | static GstBus *bus; 28 | static GstPad *teepad; 29 | static gboolean recording = FALSE; 30 | static gint counter = 0; 31 | static char *file_path; 32 | 33 | static gboolean 34 | message_cb (GstBus * bus, GstMessage * message, gpointer user_data) 35 | { 36 | GError *err = NULL; 37 | gchar *name, *debug = NULL; 38 | 39 | switch (GST_MESSAGE_TYPE (message)) { 40 | case GST_MESSAGE_ERROR: 41 | 42 | name = gst_object_get_path_string (message->src); 43 | gst_message_parse_error (message, &err, &debug); 44 | 45 | g_printerr ("ERROR: from element %s: %s\n", name, err->message); 46 | if (debug != NULL) 47 | g_printerr ("Additional debug info:\n%s\n", debug); 48 | 49 | g_error_free (err); 50 | g_free (debug); 51 | g_free (name); 52 | 53 | g_main_loop_quit (loop); 54 | break; 55 | case GST_MESSAGE_WARNING: 56 | name = gst_object_get_path_string (message->src); 57 | gst_message_parse_warning (message, &err, &debug); 58 | 59 | g_printerr ("ERROR: from element %s: %s\n", name, err->message); 60 | if (debug != NULL) 61 | g_printerr ("Additional debug info:\n%s\n", debug); 62 | 63 | g_error_free (err); 64 | g_free (debug); 65 | g_free (name); 66 | break; 67 | case GST_MESSAGE_EOS: 68 | g_print ("Got EOS\n"); 69 | g_main_loop_quit (loop); 70 | gst_element_set_state (pipeline, GST_STATE_NULL); 71 | g_main_loop_unref (loop); 72 | gst_object_unref (pipeline); 73 | exit(0); 74 | break; 75 | default: 76 | break; 77 | } 78 | 79 | return TRUE; 80 | } 81 | 82 | static GstPadProbeReturn unlink_cb(GstPad *pad, GstPadProbeInfo *info, gpointer user_data) { 83 | g_print("Unlinking..."); 84 | GstPad *sinkpad; 85 | sinkpad = gst_element_get_static_pad (queue_record, "sink"); 86 | gst_pad_unlink (teepad, sinkpad); 87 | gst_object_unref (sinkpad); 88 | 89 | gst_element_send_event(encoder, gst_event_new_eos()); 90 | 91 | sleep(1); 92 | gst_bin_remove(GST_BIN (pipeline), queue_record); 93 | gst_bin_remove(GST_BIN (pipeline), encoder); 94 | gst_bin_remove(GST_BIN (pipeline), muxer); 95 | gst_bin_remove(GST_BIN (pipeline), filesink); 96 | 97 | gst_element_set_state(queue_record, GST_STATE_NULL); 98 | gst_element_set_state(encoder, GST_STATE_NULL); 99 | gst_element_set_state(muxer, GST_STATE_NULL); 100 | gst_element_set_state(filesink, GST_STATE_NULL); 101 | 102 | gst_object_unref(queue_record); 103 | gst_object_unref(encoder); 104 | gst_object_unref(muxer); 105 | gst_object_unref(filesink); 106 | 107 | gst_element_release_request_pad (tee, teepad); 108 | gst_object_unref (teepad); 109 | 110 | g_print("Unlinked\n"); 111 | 112 | return GST_PAD_PROBE_REMOVE; 113 | } 114 | 115 | void stopRecording() { 116 | g_print("stopRecording\n"); 117 | gst_pad_add_probe(teepad, GST_PAD_PROBE_TYPE_IDLE, unlink_cb, NULL, (GDestroyNotify) g_free); 118 | recording = FALSE; 119 | } 120 | 121 | void startRecording() { 122 | g_print("startRecording\n"); 123 | GstPad *sinkpad; 124 | GstPadTemplate *templ; 125 | 126 | templ = gst_element_class_get_pad_template(GST_ELEMENT_GET_CLASS(tee), "src_%u"); 127 | teepad = gst_element_request_pad(tee, templ, NULL, NULL); 128 | queue_record = gst_element_factory_make("queue", "queue_record"); 129 | encoder = gst_element_factory_make("x264enc", NULL); 130 | muxer = gst_element_factory_make("mp4mux", NULL); 131 | filesink = gst_element_factory_make("filesink", NULL); 132 | char *file_name = (char*) malloc(255 * sizeof(char)); 133 | sprintf(file_name, "%s%d.mp4", file_path, counter++); 134 | g_print("Recording to file %s", file_name); 135 | g_object_set(filesink, "location", file_name, NULL); 136 | g_object_set(encoder, "tune", 4, NULL); // zerolatency 137 | free(file_name); 138 | 139 | gst_bin_add_many(GST_BIN(pipeline), gst_object_ref(queue_record), gst_object_ref(encoder), gst_object_ref(muxer), gst_object_ref(filesink), NULL); 140 | gst_element_link_many(queue_record, encoder, muxer, filesink, NULL); 141 | 142 | gst_element_sync_state_with_parent(queue_record); 143 | gst_element_sync_state_with_parent(encoder); 144 | gst_element_sync_state_with_parent(muxer); 145 | gst_element_sync_state_with_parent(filesink); 146 | 147 | sinkpad = gst_element_get_static_pad(queue_record, "sink"); 148 | gst_pad_link(teepad, sinkpad); 149 | gst_object_unref(sinkpad); 150 | 151 | recording = TRUE; 152 | } 153 | 154 | void sigintHandler(int unused) { 155 | g_print("You ctrl-c!\n"); 156 | if (recording) 157 | stopRecording(); 158 | else 159 | startRecording(); 160 | } 161 | 162 | int main(int argc, char *argv[]) 163 | { 164 | 165 | if (argc != 2) { 166 | g_printerr("Enter commandline argument folder path to save recorded video to.\nExample : ./a.out /home/xyz/Desktop/\n"); 167 | return -1; 168 | } 169 | 170 | if (strstr(argv[1],".mp4") != NULL) { 171 | g_printerr("Please specify folder path only.\nExample : ./a.out /home/xyz/Desktop/\n"); 172 | return -1; 173 | } 174 | 175 | file_path = (char*) malloc(255 * sizeof(char)); 176 | file_path = argv[1]; 177 | 178 | signal(SIGINT, sigintHandler); 179 | gst_init (&argc, &argv); 180 | 181 | pipeline = gst_pipeline_new(NULL); 182 | src = gst_element_factory_make("videotestsrc", NULL); 183 | g_object_set(src, "pattern", 18, NULL); // GST_VIDEO_TEST_SRC_BALL 184 | g_object_set(src, "background-color", 0x80808080, NULL); 185 | g_object_set(src, "do-timestamp", TRUE, NULL); 186 | 187 | tee = gst_element_factory_make("tee", NULL); 188 | queue_display = gst_element_factory_make("queue", "queue_display"); 189 | videoconvert = gst_element_factory_make("videoconvert", NULL); 190 | videosink = gst_element_factory_make("autovideosink", NULL); 191 | 192 | if (!pipeline || !src || !tee || !videoconvert || !videosink || !queue_display) { 193 | g_error("Failed to create elements"); 194 | return -1; 195 | } 196 | 197 | gst_bin_add_many(GST_BIN(pipeline), src, tee, queue_display, videoconvert, videosink, NULL); 198 | if (!gst_element_link_many(src, tee, NULL) 199 | || !gst_element_link_many(tee, queue_display, videoconvert, videosink, NULL)) { 200 | g_error("Failed to link elements"); 201 | return -2; 202 | } 203 | 204 | startRecording(); 205 | loop = g_main_loop_new(NULL, FALSE); 206 | 207 | bus = gst_pipeline_get_bus(GST_PIPELINE (pipeline)); 208 | gst_bus_add_signal_watch(bus); 209 | g_signal_connect(G_OBJECT(bus), "message", G_CALLBACK(message_cb), NULL); 210 | gst_object_unref(GST_OBJECT(bus)); 211 | 212 | gst_element_set_state(pipeline, GST_STATE_PLAYING); 213 | 214 | g_print("Starting loop\n"); 215 | g_main_loop_run(loop); 216 | 217 | return 0; 218 | } 219 | -------------------------------------------------------------------------------- /dynamic_rtsp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | GMainLoop *loop; 16 | GstElement *filebin; 17 | GstElement *pipeline, *file_queue, *matroskamux, *filesink; //// matroska mux working 18 | GstBus *bus; 19 | GstPad *mux_sink_pad=NULL; 20 | gulong probeID; 21 | 22 | bool end = false; 23 | 24 | bool record_vid = false; 25 | 26 | void RemoveOldProbe() 27 | { 28 | GstPad *file_queue_src; 29 | file_queue_src = gst_element_get_static_pad(file_queue, "src"); 30 | gst_pad_remove_probe(file_queue_src, probeID); 31 | gst_object_unref(file_queue_src); 32 | 33 | } 34 | 35 | void CleanExitfile() 36 | { 37 | gst_element_set_state(filebin, GST_STATE_NULL); 38 | gst_bin_remove(GST_BIN(pipeline), filebin); 39 | 40 | } 41 | 42 | void CreateNextfile() 43 | { 44 | //Create next mux Element 45 | matroskamux = gst_element_factory_make("matroskamux", "matroskamux"); 46 | 47 | //Create next File Sink or fake sink 48 | if(record_vid) 49 | { 50 | static std::string fileLocPattern = "/home/rakesh/Desktop/recording%s.mkv"; 51 | 52 | char present_time[20]; 53 | time_t rawtime; 54 | struct tm * timeinfo; 55 | time (&rawtime); 56 | timeinfo = localtime(&rawtime); 57 | 58 | strftime(present_time,sizeof(present_time),"%d-%m-%Y_%H-%M-%S",timeinfo); 59 | 60 | char buff[80]; 61 | memset(buff, 0, sizeof(buff)); 62 | sprintf(buff, fileLocPattern.c_str(), present_time); 63 | 64 | filesink = gst_element_factory_make("filesink", "filesink"); 65 | g_object_set(G_OBJECT(filesink), 66 | "async", false, 67 | "location", buff, 68 | NULL); 69 | printf("next_FILEsink attached\n"); 70 | } 71 | else 72 | { 73 | filesink = gst_element_factory_make("fakesink", "fakesink"); 74 | g_object_set(G_OBJECT(filesink), 75 | "async", false, 76 | NULL); 77 | printf("next_FAKEsink attached\n"); 78 | } 79 | 80 | //Create next filebin 81 | filebin = gst_bin_new("nextfilebin"); 82 | g_object_set(G_OBJECT(filebin), 83 | "message-forward", true, 84 | NULL); 85 | 86 | gst_bin_add_many(GST_BIN(filebin), matroskamux, filesink, NULL); 87 | 88 | gst_element_link_many(matroskamux, filesink, NULL); 89 | 90 | //Add nextfilebin to pipeline 91 | gst_bin_add(GST_BIN(pipeline), filebin); 92 | 93 | //get request pad from mux 94 | mux_sink_pad = gst_element_get_request_pad(matroskamux, "video_%u"); 95 | 96 | //link ghostpad of mux_sink to filebin 97 | GstPad *ghostpad = gst_ghost_pad_new("sink", mux_sink_pad); 98 | gst_element_add_pad(filebin, ghostpad); 99 | 100 | gst_object_unref(GST_OBJECT(mux_sink_pad)); 101 | 102 | //Get src pad from encoder 103 | GstPad *file_queue_src = gst_element_get_static_pad(file_queue, "src"); 104 | 105 | //Link encoder to filebin 106 | if (gst_pad_link(file_queue_src, ghostpad) != GST_PAD_LINK_OK ) 107 | { 108 | printf("file_queue_src cannot be linked to next mux_sink_pad.\n"); 109 | } 110 | gst_object_unref(file_queue_src); 111 | 112 | gst_element_sync_state_with_parent(filebin); 113 | 114 | } 115 | 116 | 117 | static GstPadProbeReturn pad_probe_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) 118 | { 119 | GstPad *binsinkpad = gst_element_get_static_pad(filebin, "sink"); 120 | gst_pad_unlink(pad, binsinkpad); 121 | gst_pad_send_event(binsinkpad, gst_event_new_eos()); 122 | gst_object_unref(binsinkpad); 123 | 124 | return GST_PAD_PROBE_OK; 125 | } 126 | 127 | static gboolean switch_record(gpointer user_data) 128 | { 129 | GstPad *file_queue_src; 130 | file_queue_src = gst_element_get_static_pad(file_queue, "src"); 131 | 132 | probeID = gst_pad_add_probe (file_queue_src, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, 133 | pad_probe_cb, user_data, NULL); 134 | gst_object_unref(file_queue_src); 135 | 136 | return TRUE; 137 | } 138 | 139 | 140 | void ProperExit() 141 | { 142 | end = true; 143 | record_vid = false; 144 | switch_record(loop); 145 | //gst_element_send_event (pipeline, gst_event_new_eos()); 146 | printf("Recording has %s\n",record_vid ? "Started" : "Stopped"); 147 | printf("Clean Exited RECORD Gstpipeline\n"); 148 | 149 | } 150 | 151 | void SwitchRecord() 152 | { 153 | record_vid = !record_vid; 154 | switch_record(loop); 155 | printf("Recording has %s\n", record_vid ? "Started" : "Stopped"); 156 | } 157 | 158 | 159 | static gboolean bus_cb (GstBus *bus, GstMessage *msg, gpointer user_data) 160 | { 161 | GMainLoop *mloop = (GMainLoop *)user_data; 162 | switch (GST_MESSAGE_TYPE (msg)) 163 | { 164 | case GST_MESSAGE_ERROR: 165 | { 166 | GError *err = NULL; 167 | gchar *dbg; 168 | 169 | gst_message_parse_error (msg, &err, &dbg); 170 | gst_object_default_error (msg->src, err, dbg); 171 | g_error_free (err); 172 | g_free (dbg); 173 | 174 | g_main_loop_quit (mloop); 175 | break; 176 | } 177 | case GST_EVENT_EOS: 178 | { 179 | printf("EOS message received\n"); 180 | //printf("Stopping GSTpipeline\n"); 181 | //g_main_loop_quit (loop); 182 | break; 183 | } 184 | 185 | case GST_MESSAGE_ELEMENT: 186 | { 187 | const GstStructure *struc = gst_message_get_structure (msg); 188 | 189 | if (gst_structure_has_name (struc, "GstBinForwarded")) 190 | { 191 | GstMessage *forward_msg = NULL; 192 | 193 | gst_structure_get (struc, "message", GST_TYPE_MESSAGE, &forward_msg, NULL); 194 | if (GST_MESSAGE_TYPE (forward_msg) == GST_MESSAGE_EOS) 195 | { 196 | g_print ("EOS received from %s\n", 197 | GST_OBJECT_NAME (GST_MESSAGE_SRC (forward_msg))); 198 | 199 | CleanExitfile(); 200 | CreateNextfile(); 201 | if(!end) 202 | RemoveOldProbe(); 203 | 204 | } 205 | gst_message_unref (forward_msg); 206 | } 207 | 208 | } 209 | break; 210 | 211 | default: 212 | break; 213 | } 214 | return TRUE; 215 | } 216 | 217 | ////////////////// dynamic RTSP pipeline 218 | 219 | static void on_pad_added (GstElement *element, GstPad *pad, gpointer data) 220 | { 221 | GstPad *sinkpad; 222 | GstElement *decoder = (GstElement *) data; 223 | /* We can now link this pad with the rtsp-decoder sink pad */ 224 | g_print ("Dynamic pad created, linking source/demuxer\n"); 225 | sinkpad = gst_element_get_static_pad (decoder, "sink"); 226 | gst_pad_link (pad, sinkpad); 227 | gst_object_unref (sinkpad); 228 | } 229 | 230 | static void cb_new_rtspsrc_pad(GstElement *element,GstPad*pad,gpointer data) 231 | { 232 | gchar *name; 233 | GstCaps * p_caps; 234 | gchar * description; 235 | GstElement *p_rtph264depay; 236 | 237 | name = gst_pad_get_name(pad); 238 | g_print("A new pad %s was created\n", name); 239 | 240 | // here, you would setup a new pad link for the newly created pad 241 | // sooo, now find that rtph264depay is needed and link them? 242 | p_caps = gst_pad_get_pad_template_caps (pad); 243 | 244 | description = gst_caps_to_string(p_caps); 245 | //printf("%s\n",p_caps,", ",description,"\n"); 246 | g_free(description); 247 | 248 | p_rtph264depay = GST_ELEMENT(data); 249 | 250 | // try to link the pads then ... 251 | if(!gst_element_link_pads(element, name, p_rtph264depay, "sink")) 252 | { 253 | printf("Failed to link elements 3\n"); 254 | printf("trying again now.\n"); 255 | } 256 | else 257 | printf("successfull in linking elements 3\n"); 258 | 259 | g_free(name); 260 | } 261 | 262 | 263 | void signal_handler(int signal) { 264 | 265 | if(signal == 2) 266 | SwitchRecord(); 267 | else 268 | { 269 | 270 | ProperExit(); 271 | usleep(100000); 272 | exit(0); 273 | } 274 | } 275 | 276 | //////////////////////// 277 | 278 | int RecordInit_Run() 279 | { 280 | GstElement *rtspsrc, *capsfilter, *rtph264depay, *h264parse, *tee, *stream_queue, *stream_decoder, *videoconvert, *ximagesink; 281 | 282 | guint unused; 283 | gst_init (NULL, NULL); 284 | loop = g_main_loop_new (NULL, FALSE); 285 | 286 | pipeline = gst_pipeline_new ("pipeline"); 287 | 288 | rtspsrc = gst_element_factory_make("rtspsrc", "rtspsrc"); 289 | g_object_set(G_OBJECT(rtspsrc), 290 | "location", "rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov", 291 | NULL); 292 | 293 | /// CAPSFILTER PART 294 | capsfilter = gst_element_factory_make("capsfilter", "caps"); 295 | GstCaps *rtspcaps; 296 | rtspcaps = gst_caps_from_string("application/x-rtp, media=(string)video, payload=(int)96, clock-rate=(int)90000, encoding-name=(string)H264"); 297 | 298 | g_object_set(G_OBJECT(capsfilter), "caps", rtspcaps, 299 | NULL); 300 | 301 | //////////// 302 | tee = gst_element_factory_make("tee", "tee"); 303 | rtph264depay = gst_element_factory_make("rtph264depay","rtph264depay"); 304 | h264parse = gst_element_factory_make("h264parse","h264parse"); 305 | stream_decoder = gst_element_factory_make("avdec_h264", "h264dec"); 306 | matroskamux = gst_element_factory_make("matroskamux", "matroskamuxer"); 307 | 308 | videoconvert = gst_element_factory_make("videoconvert", "videoconvert"); 309 | ximagesink = gst_element_factory_make("ximagesink", "displaysink"); 310 | stream_queue = gst_element_factory_make("queue", "stream_queue"); 311 | file_queue = gst_element_factory_make("queue", "file_queue"); 312 | 313 | if (!pipeline || !rtspsrc || !tee || !matroskamux || !videoconvert || !ximagesink || !file_queue || !stream_decoder || !stream_queue) { 314 | g_error("Failed to create elements"); 315 | return -1; 316 | } 317 | 318 | GstPadTemplate *tee_src_pad_template; 319 | GstPad *stream_pad, *queue_file_pad; 320 | GstPad *tee_stream_pad, *tee_file_pad; 321 | 322 | if(record_vid) 323 | { 324 | filesink = gst_element_factory_make("filesink", "firstfilesink"); 325 | 326 | static std::string fileLocPattern = "/home/rakesh/Desktop/recording%s.mkv"; 327 | char present_time[20]; 328 | time_t rawtime; 329 | struct tm * timeinfo; 330 | time (&rawtime); 331 | timeinfo = localtime(&rawtime); 332 | 333 | strftime(present_time,sizeof(present_time),"%d-%m-%Y_%H-%M-%S",timeinfo); 334 | 335 | char buff[80]; 336 | memset(buff, 0, sizeof(buff)); 337 | sprintf(buff, fileLocPattern.c_str(), present_time); 338 | //printf("name%s\n",buff); 339 | 340 | g_object_set(G_OBJECT(filesink), 341 | "async", false, 342 | "location", buff, 343 | NULL); 344 | g_print("firstfilesink made \n"); 345 | } 346 | else 347 | { 348 | filesink = gst_element_factory_make("fakesink", "firstfakesink"); 349 | g_object_set(G_OBJECT(filesink), 350 | "async", false, 351 | NULL); 352 | g_print("firstfakesink made \n"); 353 | } 354 | 355 | // Initial file bin generation 356 | filebin = gst_bin_new("init_file_bin"); 357 | g_object_set(G_OBJECT(filebin), 358 | "message-forward", true, 359 | NULL); 360 | 361 | gst_bin_add_many(GST_BIN(filebin), matroskamux, filesink, NULL); 362 | gst_element_link_many(matroskamux, filesink, NULL); 363 | ///// modified for rtsp linking 364 | 365 | gst_bin_add_many (GST_BIN (pipeline), rtspsrc, rtph264depay, NULL);//h264parse, tee, file_queue, filebin, stream_queue, stream_decoder, videoconvert, ximagesink, NULL); 366 | 367 | g_signal_connect(rtspsrc, "pad-added", G_CALLBACK(cb_new_rtspsrc_pad),rtph264depay); 368 | 369 | gst_bin_add_many (GST_BIN (pipeline),h264parse,NULL); 370 | 371 | if(!gst_element_link(rtph264depay,h264parse)) 372 | printf("\ndepay and parse not linked\n"); 373 | 374 | 375 | //if(!gst_element_link_many (rtspsrc, rtph264depay, h264parse,NULL)) 376 | // printf("error link_many_rtsp\n"); 377 | 378 | gst_bin_add_many (GST_BIN (pipeline), tee, file_queue, filebin, stream_queue, stream_decoder, videoconvert, ximagesink, NULL); 379 | 380 | gst_element_link_filtered (h264parse, tee, NULL); 381 | 382 | if(!gst_element_link_many (stream_queue, stream_decoder, videoconvert, ximagesink, NULL)) 383 | printf("error link many stream_queue\n"); 384 | //gst_element_link_many (file_queue, file_encoder, NULL); 385 | 386 | //Manually linking the Tee 387 | if ( !(tee_src_pad_template = gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (tee), "src_%u"))) 388 | { 389 | gst_object_unref (pipeline); 390 | g_critical ("Unable to get tee pad template"); 391 | return 0; 392 | } 393 | 394 | tee_stream_pad = gst_element_request_pad (tee, tee_src_pad_template, NULL, NULL); 395 | g_print ("Obtained request pad %s for tee stream branch.\n", gst_pad_get_name (tee_stream_pad)); 396 | stream_pad = gst_element_get_static_pad (stream_queue, "sink"); 397 | 398 | tee_file_pad = gst_element_request_pad (tee, tee_src_pad_template, NULL, NULL); 399 | g_print ("Obtained request pad %s for tee file branch.\n", gst_pad_get_name (tee_file_pad)); 400 | queue_file_pad = gst_element_get_static_pad (file_queue, "sink"); 401 | 402 | /* Link the tee to the stream_pad */ 403 | if (gst_pad_link (tee_stream_pad, stream_pad) != GST_PAD_LINK_OK ) 404 | { 405 | g_critical ("Tee for stream could not be linked.\n"); 406 | gst_object_unref (pipeline); 407 | return 0; 408 | } 409 | 410 | /* Link the tee to the file_queue */ 411 | if (gst_pad_link (tee_file_pad, queue_file_pad) != GST_PAD_LINK_OK) 412 | { 413 | g_critical ("Tee for file could not be linked.\n"); 414 | gst_object_unref (pipeline); 415 | return 0; 416 | } 417 | 418 | gst_object_unref (stream_pad); 419 | gst_object_unref (queue_file_pad); 420 | 421 | 422 | //get request pad from mux 423 | GstPadTemplate * mux_sink_padTemplate; 424 | if( !(mux_sink_padTemplate = gst_element_class_get_pad_template(GST_ELEMENT_GET_CLASS(matroskamux), "video_%u")) ) 425 | { 426 | printf("Unable to get source pad template from muxing element\n"); 427 | } 428 | 429 | //Obtain request pad from mux 430 | mux_sink_pad = gst_element_request_pad(matroskamux, mux_sink_padTemplate, NULL, NULL); 431 | 432 | if(mux_sink_pad ==NULL) 433 | printf("error mux_sink_pad\n"); 434 | 435 | //Add a copy of mux sink pad to bin 436 | GstPad *ghostpad = gst_ghost_pad_new("sink", mux_sink_pad); 437 | 438 | if(!gst_element_add_pad(filebin, ghostpad)) 439 | printf("error ghost pad could not be added to bin\n"); 440 | 441 | gst_object_unref(GST_OBJECT(mux_sink_pad)); 442 | 443 | 444 | //Get src pad from queue element 445 | GstPad *file_queue_src = gst_element_get_static_pad(file_queue, "src"); 446 | 447 | //Link to ghostpad 448 | if (gst_pad_link(file_queue_src, ghostpad) != GST_PAD_LINK_OK ) 449 | { 450 | 451 | printf("file_queue_src cannot be linked to mux_sink_pad.\n"); 452 | } 453 | 454 | g_signal_connect(rtph264depay, "pad-added", G_CALLBACK(on_pad_added), h264parse); 455 | 456 | 457 | gst_element_sync_state_with_parent(filebin); 458 | //gst_pad_set_active(ghostpad,true); 459 | 460 | gst_bus_add_watch (GST_ELEMENT_BUS (pipeline), bus_cb, loop); 461 | 462 | struct sigaction act; 463 | act.sa_handler = signal_handler; 464 | sigemptyset(&act.sa_mask); 465 | act.sa_flags = 0; 466 | 467 | gst_element_set_state (pipeline, GST_STATE_PLAYING); 468 | 469 | sigaction(SIGINT, &act, 0); 470 | sigaction(SIGTSTP, &act, 0); 471 | sigaction(SIGABRT, &act, 0); 472 | sigaction(SIGQUIT, &act, 0); 473 | 474 | g_main_loop_run (loop); 475 | 476 | return 1; 477 | 478 | } 479 | 480 | 481 | int main(int argc, char *argv[]) 482 | { 483 | RecordInit_Run(); 484 | 485 | return 0; 486 | } 487 | --------------------------------------------------------------------------------