|
| 1 | +#include "gst_helpers.h" |
| 2 | + |
| 3 | +void scale_input_videos(GstreamerData * data, int output_width, int output_height); |
| 4 | +void setup_video_mixer_pads(GstreamerData * data, int output_width, int output_height); |
| 5 | + |
| 6 | +/* Manually clean unused Gst Elements if not streaming to Twitch */ |
| 7 | +/* TODO Create them on-demand instead of eagerly*/ |
| 8 | +void clean_unused_streaming_gst_elements(GstreamerData * data); |
| 9 | + |
| 10 | +GstreamerData create_data() |
| 11 | +{ |
| 12 | + GstreamerData data; |
| 13 | + /* Create the elements */ |
| 14 | + data.decodebin1 = gst_element_factory_make("uridecodebin3", "decodebin1"); |
| 15 | + data.decodebin2 = gst_element_factory_make("uridecodebin3", "decodebin2"); |
| 16 | + data.decodebin3 = gst_element_factory_make("uridecodebin3", "decodebin3"); |
| 17 | + data.videoscale1 = gst_element_factory_make("videoscale", "videobox1"); |
| 18 | + data.videoscale2 = gst_element_factory_make("videoscale", "videobox2"); |
| 19 | + data.videoscale3 = gst_element_factory_make("videoscale", "videobox3"); |
| 20 | + data.video_scaled_caps1 = gst_element_factory_make("capsfilter", "video_scaled_capsfilter1"); |
| 21 | + data.video_scaled_caps2 = gst_element_factory_make("capsfilter", "video_scaled_capsfilter2"); |
| 22 | + data.video_scaled_caps3 = gst_element_factory_make("capsfilter", "video_scaled_capsfilter3"); |
| 23 | + data.video_mixer = gst_element_factory_make("videomixer", "videomixer"); |
| 24 | + |
| 25 | + data.tee = gst_element_factory_make("tee", "tee"); |
| 26 | + |
| 27 | + /* Live preview */ |
| 28 | + data.queue_preview = gst_element_factory_make("queue", "queue_preview"); |
| 29 | + data.convert_preview = gst_element_factory_make("videoconvert", "convert_preview"); |
| 30 | + data.sink_preview = gst_element_factory_make("autovideosink", "sink_preview"); |
| 31 | + |
| 32 | + /* Twitch streaming */ |
| 33 | + data.queue_streaming = gst_element_factory_make("queue", "queue_streaming"); |
| 34 | + data.video_encoder_streaming = gst_element_factory_make("x264enc", "encoder_streaming"); |
| 35 | + data.queue_encoded = gst_element_factory_make("queue", "queue_encoded"); |
| 36 | + data.muxer_streaming = gst_element_factory_make("flvmux", "muxer_streaming"); |
| 37 | + data.queue_muxed = gst_element_factory_make("queue", "queue_muxed"); |
| 38 | + data.sink_rtmp = gst_element_factory_make("rtmpsink", "sink_streaming"); |
| 39 | + |
| 40 | + data.pipeline = gst_pipeline_new("pipeline"); |
| 41 | + |
| 42 | + if (!data.pipeline || !data.decodebin1 || !data.decodebin2 || !data.decodebin3 // |
| 43 | + || !data.videoscale1 || !data.videoscale2 || !data.videoscale3 || !data.video_scaled_caps1 |
| 44 | + || !data.video_scaled_caps2 || !data.video_scaled_caps3 || !data.video_mixer || !data.tee // |
| 45 | + || !data.convert_preview || !data.queue_preview || !data.sink_preview || // |
| 46 | + !data.queue_streaming || !data.video_encoder_streaming || !data.queue_encoded || !data.muxer_streaming |
| 47 | + || !data.queue_muxed || !data.sink_rtmp) { |
| 48 | + g_printerr("Not all elements could be created.\n"); |
| 49 | + exit(1); |
| 50 | + } |
| 51 | + |
| 52 | + return data; |
| 53 | +} |
| 54 | + |
| 55 | + |
| 56 | +void link_pipeline_elements(GstreamerData * data, gboolean with_twitch) |
| 57 | +{ |
| 58 | + g_return_if_fail(data != NULL); |
| 59 | + |
| 60 | + if (with_twitch == FALSE) { clean_unused_streaming_gst_elements(data); } |
| 61 | + |
| 62 | + gboolean error = FALSE; |
| 63 | + |
| 64 | + /* Add the common part of the pipeline */ |
| 65 | + gst_bin_add_many(GST_BIN(data->pipeline), |
| 66 | + data->decodebin1, |
| 67 | + data->decodebin2, |
| 68 | + data->decodebin3, |
| 69 | + data->videoscale1, |
| 70 | + data->videoscale2, |
| 71 | + data->videoscale3, |
| 72 | + data->video_scaled_caps1, |
| 73 | + data->video_scaled_caps2, |
| 74 | + data->video_scaled_caps3, |
| 75 | + data->video_mixer, |
| 76 | + data->convert_preview, |
| 77 | + data->sink_preview, |
| 78 | + NULL); |
| 79 | + |
| 80 | + if (!gst_element_link(data->videoscale1, data->video_scaled_caps1) |
| 81 | + || !gst_element_link(data->videoscale2, data->video_scaled_caps2) |
| 82 | + || !gst_element_link(data->videoscale3, data->video_scaled_caps3)) { |
| 83 | + error = TRUE; |
| 84 | + } |
| 85 | + |
| 86 | + if (with_twitch) { |
| 87 | + gst_bin_add_many(GST_BIN(data->pipeline), |
| 88 | + data->tee, |
| 89 | + data->queue_preview, |
| 90 | + data->queue_streaming, |
| 91 | + data->video_encoder_streaming, |
| 92 | + data->queue_encoded, |
| 93 | + data->muxer_streaming, |
| 94 | + data->queue_muxed, |
| 95 | + data->sink_rtmp, |
| 96 | + NULL); |
| 97 | + |
| 98 | + g_print("Linking GStreamer elements for live preview and Twitch streaming .\n"); |
| 99 | + if (!gst_element_link_many(data->video_mixer, data->tee, NULL) |
| 100 | + || !gst_element_link_many(data->tee, |
| 101 | + data->queue_streaming, |
| 102 | + data->video_encoder_streaming, |
| 103 | + data->queue_encoded, |
| 104 | + data->muxer_streaming, |
| 105 | + data->queue_muxed, |
| 106 | + data->sink_rtmp, |
| 107 | + NULL) |
| 108 | + || !gst_element_link_many(data->tee, |
| 109 | + data->queue_preview, |
| 110 | + data->convert_preview, |
| 111 | + data->sink_preview, |
| 112 | + NULL)) { |
| 113 | + error = TRUE; |
| 114 | + } |
| 115 | + } |
| 116 | + else { |
| 117 | + g_print("Linking the elements without Twitch streaming part.\n"); |
| 118 | + if (!gst_element_link_many(data->video_mixer, data->convert_preview, data->sink_preview, NULL)) { |
| 119 | + error = TRUE; |
| 120 | + } |
| 121 | + } |
| 122 | + if (error) { |
| 123 | + g_printerr("Elements could not be linked.\n"); |
| 124 | + gst_object_unref(data->pipeline); |
| 125 | + exit(1); |
| 126 | + } |
| 127 | +} |
| 128 | + |
| 129 | +void setup_video_placement(GstreamerData * data, int output_width, int output_height) |
| 130 | +{ |
| 131 | + g_return_if_fail(data != NULL); |
| 132 | + |
| 133 | + g_object_set(data->video_mixer, "background", 1, NULL); /* set black background beneath */ |
| 134 | + |
| 135 | + scale_input_videos(data, output_width, output_height); |
| 136 | + setup_video_mixer_pads(data, output_width, output_height); |
| 137 | +} |
| 138 | + |
| 139 | +void setup_file_sources(GstreamerData * data, gchar * file_path1, gchar * file_path2, gchar * filepath3) |
| 140 | +{ |
| 141 | + g_return_if_fail(data != NULL); |
| 142 | + g_return_if_fail(file_path1 != NULL || file_path2 != NULL || filepath3 != NULL); |
| 143 | + |
| 144 | + gchar * uri1 = g_strjoin("", "file://", file_path1, NULL); |
| 145 | + gchar * uri2 = g_strjoin("", "file://", file_path2, NULL); |
| 146 | + gchar * uri3 = g_strjoin("", "file://", filepath3, NULL); |
| 147 | + g_object_set(data->decodebin1, "uri", uri1, NULL); |
| 148 | + g_object_set(data->decodebin2, "uri", uri2, NULL); |
| 149 | + g_object_set(data->decodebin3, "uri", uri3, NULL); |
| 150 | + g_free(uri1); |
| 151 | + g_free(uri2); |
| 152 | + g_free(uri3); |
| 153 | +} |
| 154 | + |
| 155 | +void setup_twitch_streaming(GstreamerData * data, gchar * twitch_api_key, gchar * twitch_server) |
| 156 | +{ |
| 157 | + g_return_if_fail(data != NULL); |
| 158 | + g_return_if_fail(twitch_api_key != NULL || twitch_server != NULL); |
| 159 | + |
| 160 | + /* Set the parameters for the Twitch stream */ |
| 161 | + g_object_set(data->video_encoder_streaming, "threads", 0, NULL); |
| 162 | + g_object_set(data->video_encoder_streaming, "bitrate", 400, NULL); |
| 163 | + g_object_set(data->video_encoder_streaming, "tune", 4, NULL); |
| 164 | + g_object_set(data->video_encoder_streaming, "key-int-max", 30, NULL); |
| 165 | + |
| 166 | + g_object_set(data->muxer_streaming, "streamable", TRUE, NULL); |
| 167 | + |
| 168 | + gchar * location = g_strjoin("", twitch_server, twitch_api_key, NULL); |
| 169 | + g_object_set(data->sink_rtmp, "location", location, NULL); |
| 170 | + g_free(location); |
| 171 | +} |
| 172 | + |
| 173 | +void clean_unused_streaming_gst_elements(GstreamerData * data) |
| 174 | +{ |
| 175 | + g_return_if_fail(data != NULL); |
| 176 | + gst_object_unref(data->tee); |
| 177 | + gst_object_unref(data->queue_preview); |
| 178 | + gst_object_unref(data->queue_streaming); |
| 179 | + gst_object_unref(data->video_encoder_streaming); |
| 180 | + gst_object_unref(data->queue_encoded); |
| 181 | + gst_object_unref(data->muxer_streaming); |
| 182 | + gst_object_unref(data->queue_muxed); |
| 183 | + gst_object_unref(data->sink_rtmp); |
| 184 | +} |
| 185 | + |
| 186 | +void try_change_pipeline_state(GstElement * pipeline, GstState state) |
| 187 | +{ |
| 188 | + GstStateChangeReturn ret = gst_element_set_state(pipeline, state); |
| 189 | + if (ret == GST_STATE_CHANGE_FAILURE) { |
| 190 | + g_printerr("Unable to set the pipeline to the paused state.\n"); |
| 191 | + gst_object_unref(pipeline); |
| 192 | + exit(1); |
| 193 | + } |
| 194 | +} |
| 195 | + |
| 196 | +/* private functions' definitions */ |
| 197 | + |
| 198 | +void scale_input_videos(GstreamerData * data, int output_width, int output_height) |
| 199 | +{ |
| 200 | + GstCaps * videocaps_half = gst_caps_new_simple("video/x-raw", |
| 201 | + "format", |
| 202 | + G_TYPE_STRING, |
| 203 | + "I420", |
| 204 | + "framerate", |
| 205 | + GST_TYPE_FRACTION, |
| 206 | + 25, |
| 207 | + 1, |
| 208 | + "pixel-aspect-ratio", |
| 209 | + GST_TYPE_FRACTION, |
| 210 | + 1, |
| 211 | + 1, |
| 212 | + "width", |
| 213 | + G_TYPE_INT, |
| 214 | + output_width / 2, |
| 215 | + "height", |
| 216 | + G_TYPE_INT, |
| 217 | + output_height / 2, |
| 218 | + NULL); |
| 219 | + |
| 220 | + |
| 221 | + g_object_set(data->video_scaled_caps1, "caps", videocaps_half, NULL); |
| 222 | + g_object_set(data->video_scaled_caps2, "caps", videocaps_half, NULL); |
| 223 | + g_object_set(data->video_scaled_caps3, "caps", videocaps_half, NULL); |
| 224 | + gst_caps_unref(videocaps_half); |
| 225 | +} |
| 226 | + |
| 227 | +void setup_video_mixer_pads(GstreamerData * data, int output_width, int output_height) |
| 228 | +{ |
| 229 | + /* Manually link the videomixer, which has "Request" pads */ |
| 230 | + GstPad *videomixer_pad1, *video1_pad = NULL; |
| 231 | + GstPad *videomixer_pad2, *video2_pad = NULL; |
| 232 | + GstPad *videomixer_pad3, *video3_pad = NULL; |
| 233 | + |
| 234 | + videomixer_pad1 = gst_element_get_request_pad(data->video_mixer, "sink_%u"); |
| 235 | + videomixer_pad2 = gst_element_get_request_pad(data->video_mixer, "sink_%u"); |
| 236 | + videomixer_pad3 = gst_element_get_request_pad(data->video_mixer, "sink_%u"); |
| 237 | + |
| 238 | + video1_pad = gst_element_get_static_pad(data->video_scaled_caps1, "src"); |
| 239 | + video2_pad = gst_element_get_static_pad(data->video_scaled_caps2, "src"); |
| 240 | + video3_pad = gst_element_get_static_pad(data->video_scaled_caps3, "src"); |
| 241 | + |
| 242 | + if (gst_pad_link(video1_pad, videomixer_pad1) != GST_PAD_LINK_OK |
| 243 | + || gst_pad_link(video2_pad, videomixer_pad2) != GST_PAD_LINK_OK |
| 244 | + || gst_pad_link(video3_pad, videomixer_pad3) != GST_PAD_LINK_OK) { |
| 245 | + g_printerr("Videomixer could not be linked.\n"); |
| 246 | + gst_object_unref(data->pipeline); |
| 247 | + exit(1); |
| 248 | + } |
| 249 | + |
| 250 | + g_object_set(videomixer_pad1, "xpos", 0, NULL); |
| 251 | + g_object_set(videomixer_pad1, "ypos", output_height / 4, NULL); |
| 252 | + g_object_set(videomixer_pad2, "xpos", output_width / 2, NULL); |
| 253 | + g_object_set(videomixer_pad2, "ypos", 0, NULL); |
| 254 | + g_object_set(videomixer_pad3, "xpos", output_width / 2, NULL); |
| 255 | + g_object_set(videomixer_pad3, "ypos", output_height / 2, NULL); |
| 256 | + |
| 257 | + gst_object_unref(videomixer_pad1); |
| 258 | + gst_object_unref(videomixer_pad2); |
| 259 | + gst_object_unref(videomixer_pad3); |
| 260 | + gst_object_unref(video1_pad); |
| 261 | + gst_object_unref(video2_pad); |
| 262 | + gst_object_unref(video3_pad); |
| 263 | +} |
0 commit comments