/* * owr_local_media_source_get_pad * * The beginning of a media source chain in the pipeline looks like this: * * +--------+ +------------+ +-----+ * | source +---+ capsfilter +---+ tee +--- * +--------+ +------------+ +-----+ * * Only one such chain is created per media source for the initial get_pad * call. Subsequent calls will just obtain another tee pad. After these initial * elements are created, they are linked together and synced up to the PLAYING * state. * * Once the initial chain is created, a block is placed on the new src pad of * the tee. The rest of the new chain (conversion elements, capsfilter, queues, * etc.) is created, linked and synced in the pad block callback. */ static GstPad *owr_local_media_source_get_pad(OwrMediaSource *media_source, GstCaps *caps) { OwrLocalMediaSource *local_source; OwrLocalMediaSourcePrivate *priv; GstElement *source_bin, *post_tee_bin; GstElement *source = NULL, *capsfilter = NULL, *tee; GstPad *ghostpad = NULL; gchar *pad_name; OwrMediaType media_type = OWR_MEDIA_TYPE_UNKNOWN; OwrSourceType source_type = OWR_SOURCE_TYPE_UNKNOWN; OwrCodecType codec_type = OWR_CODEC_TYPE_NONE; guint source_id; g_assert(media_source); local_source = OWR_LOCAL_MEDIA_SOURCE(media_source); priv = local_source->priv; g_object_get(media_source, "media-type", &media_type, "type", &source_type, NULL); /* only create the source bin for this media source once */ if (_owr_media_source_get_element(media_source)) { GST_DEBUG_OBJECT(media_source, "Re-using existing source element/bin"); source_bin = _owr_media_source_get_element(media_source); tee = priv->source_tee; } else { GEnumClass *media_enum_class, *source_enum_class; GEnumValue *media_enum_value, *source_enum_value; gchar *bin_name; GstCaps *source_caps; GstStructure *source_structure; GstElement *fakesink; media_enum_class = G_ENUM_CLASS(g_type_class_ref(OWR_TYPE_MEDIA_TYPE)); source_enum_class = G_ENUM_CLASS(g_type_class_ref(OWR_TYPE_SOURCE_TYPE)); media_enum_value = g_enum_get_value(media_enum_class, media_type); source_enum_value = g_enum_get_value(source_enum_class, source_type); bin_name = g_strdup_printf("local-%s-%s-source-bin-%u", media_enum_value ? media_enum_value->value_nick : "unknown", source_enum_value ? source_enum_value->value_nick : "unknown", g_atomic_int_add(&unique_bin_id, 1)); g_type_class_unref(media_enum_class); g_type_class_unref(source_enum_class); source_bin = gst_bin_new(bin_name); g_free(bin_name); bin_name = NULL; gst_bin_add(GST_BIN(_owr_get_pipeline()), source_bin); gst_element_sync_state_with_parent(GST_ELEMENT(source_bin)); GST_DEBUG_OBJECT(local_source, "media_type: %d, type: %d", media_type, source_type); if (media_type == OWR_MEDIA_TYPE_UNKNOWN || source_type == OWR_SOURCE_TYPE_UNKNOWN) { GST_ERROR_OBJECT(local_source, "Cannot connect source with unknown type or media type to other component"); goto done; } switch (media_type) { case OWR_MEDIA_TYPE_AUDIO: { switch (source_type) { case OWR_SOURCE_TYPE_CAPTURE: CREATE_ELEMENT(source, AUDIO_SRC, "audio-source"); #if !defined(__APPLE__) || !TARGET_IPHONE_SIMULATOR g_object_set(source, "buffer-time", G_GINT64_CONSTANT(40000), "latency-time", G_GINT64_CONSTANT(10000), NULL); #ifdef __APPLE__ g_object_set(source, "device", priv->device_index, NULL); #endif #endif break; case OWR_SOURCE_TYPE_TEST: CREATE_ELEMENT(source, "audiotestsrc", "audio-source"); g_object_set(source, "is-live", TRUE, NULL); break; case OWR_SOURCE_TYPE_UNKNOWN: default: g_assert_not_reached(); goto done; } #if defined(__APPLE__) && !TARGET_IPHONE_SIMULATOR /* workaround for osxaudiosrc bug * https://bugzilla.gnome.org/show_bug.cgi?id=711764 */ CREATE_ELEMENT(capsfilter, "capsfilter", "audio-source-capsfilter"); source_caps = gst_caps_copy(caps); source_structure = gst_caps_get_structure(source_caps, 0); gst_structure_set(source_structure, "format", G_TYPE_STRING, "S32LE", "rate", G_TYPE_INT, 44100, NULL); gst_structure_remove_field(source_structure, "channels"); g_object_set(capsfilter, "caps", source_caps, NULL); gst_caps_unref(source_caps); gst_bin_add(GST_BIN(source_bin), capsfilter); #endif break; } case OWR_MEDIA_TYPE_VIDEO: { switch (source_type) { case OWR_SOURCE_TYPE_CAPTURE: CREATE_ELEMENT(source, VIDEO_SRC, "video-source"); #if defined(__APPLE__) && !TARGET_IPHONE_SIMULATOR g_object_set(source, "device-index", priv->device_index, NULL); #elif defined(__ANDROID__) g_object_set(source, "cam-index", priv->device_index, NULL); #elif defined(__linux__) { gchar *device = g_strdup_printf("/dev/video%u", priv->device_index); g_object_set(source, "device", device, NULL); g_free(device); } #endif break; case OWR_SOURCE_TYPE_TEST: CREATE_ELEMENT(source, "videotestsrc", "video-source"); g_object_set(source, "is-live", TRUE, NULL); break; case OWR_SOURCE_TYPE_UNKNOWN: default: g_assert_not_reached(); goto done; } CREATE_ELEMENT(capsfilter, "capsfilter", "video-source-capsfilter"); source_caps = gst_caps_copy(caps); source_structure = gst_caps_get_structure(source_caps, 0); gst_structure_remove_field(source_structure, "format"); gst_structure_remove_field(source_structure, "framerate"); g_object_set(capsfilter, "caps", source_caps, NULL); gst_caps_unref(source_caps); gst_bin_add(GST_BIN(source_bin), capsfilter); break; } case OWR_MEDIA_TYPE_UNKNOWN: default: g_assert_not_reached(); goto done; } g_assert(source); CREATE_ELEMENT(tee, "tee", "source-tee"); CREATE_ELEMENT(fakesink, "fakesink", "source-tee-fakesink"); g_object_set(fakesink, "async", FALSE, NULL); gst_bin_add_many(GST_BIN(source_bin), source, tee, fakesink, NULL); gst_element_sync_state_with_parent(fakesink); LINK_ELEMENTS(tee, fakesink); if (!source) GST_ERROR_OBJECT(media_source, "Failed to create source element!"); } codec_type = _owr_caps_to_codec_type(caps); source_id = g_atomic_int_add(&unique_pad_id, 1); pad_name = g_strdup_printf("src_%u_%u", codec_type, source_id); ghostpad = gst_ghost_pad_new_no_target(pad_name, GST_PAD_SRC); g_free(pad_name); post_tee_bin = create_post_tee_bin(media_source, source_bin, caps, ghostpad, source_id); if (!post_tee_bin) { gst_object_unref(ghostpad); ghostpad = NULL; goto done; } if (!gst_element_link(tee, post_tee_bin)) { GST_ERROR("Failed to link source tee to source-post-tee-bin-%u", source_id); g_object_unref(post_tee_bin); ghostpad = NULL; goto done; } if (!_owr_media_source_get_element(media_source)) { /* the next code block inside the if is a workaround for avfvideosrc * not handling on-the-fly reconfiguration * on upstream reconfigure events, we drop the event in the probe */ if (media_type == OWR_MEDIA_TYPE_VIDEO) { GstPad *tee_sinkpad; tee_sinkpad = gst_element_get_static_pad(tee, "sink"); gst_pad_add_probe(tee_sinkpad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, drop_reconfigure_cb, NULL, NULL); } if (capsfilter) { LINK_ELEMENTS(capsfilter, tee); gst_element_sync_state_with_parent(tee); gst_element_sync_state_with_parent(capsfilter); LINK_ELEMENTS(source, capsfilter); } else { gst_element_sync_state_with_parent(tee); LINK_ELEMENTS(source, tee); } gst_element_sync_state_with_parent(source); _owr_media_source_set_element(media_source, source_bin); priv->source_tee = tee; } done: return ghostpad; }
/* * The following chain is created after the tee for each output from the * source: * * +-----------+ +-------------------------------+ +----------+ * | inter*src +---+ converters/queues/capsfilters +---+ ghostpad | * +-----------+ +-------------------------------+ +----------+ * */ static GstElement *owr_media_source_request_source_default(OwrMediaSource *media_source, GstCaps *caps) { OwrMediaType media_type; GstElement *source_pipeline, *tee; GstElement *source_bin, *source = NULL, *queue_pre, *queue_post; GstElement *capsfilter; GstElement *sink, *sink_queue, *sink_bin; GstPad *bin_pad = NULL, *srcpad, *sinkpad; gchar *bin_name; guint source_id; gchar *channel_name; g_return_val_if_fail(media_source->priv->source_bin, NULL); g_return_val_if_fail(media_source->priv->source_tee, NULL); source_pipeline = gst_object_ref(media_source->priv->source_bin); tee = gst_object_ref(media_source->priv->source_tee); source_id = g_atomic_int_add(&unique_bin_id, 1); bin_name = g_strdup_printf("source-bin-%u", source_id); source_bin = gst_bin_new(bin_name); g_free(bin_name); CREATE_ELEMENT_WITH_ID(queue_pre, "queue", "source-queue", source_id); CREATE_ELEMENT_WITH_ID(capsfilter, "capsfilter", "source-output-capsfilter", source_id); CREATE_ELEMENT_WITH_ID(queue_post, "queue", "source-output-queue", source_id); CREATE_ELEMENT_WITH_ID(sink_queue, "queue", "sink-queue", source_id); g_object_get(media_source, "media-type", &media_type, NULL); switch (media_type) { case OWR_MEDIA_TYPE_AUDIO: { GstElement *audioresample, *audioconvert; CREATE_ELEMENT_WITH_ID(source, "interaudiosrc", "source", source_id); CREATE_ELEMENT_WITH_ID(sink, "interaudiosink", "sink", source_id); g_object_set(capsfilter, "caps", caps, NULL); CREATE_ELEMENT_WITH_ID(audioresample, "audioresample", "source-audio-resample", source_id); CREATE_ELEMENT_WITH_ID(audioconvert, "audioconvert", "source-audio-convert", source_id); gst_bin_add_many(GST_BIN(source_bin), queue_pre, audioconvert, audioresample, capsfilter, queue_post, NULL); LINK_ELEMENTS(capsfilter, queue_post); LINK_ELEMENTS(audioresample, capsfilter); LINK_ELEMENTS(audioconvert, audioresample); LINK_ELEMENTS(queue_pre, audioconvert); break; } case OWR_MEDIA_TYPE_VIDEO: { GstElement *videoscale, *videoconvert; CREATE_ELEMENT_WITH_ID(source, "intervideosrc", "source", source_id); CREATE_ELEMENT_WITH_ID(sink, "intervideosink", "sink", source_id); srcpad = gst_element_get_static_pad(source, "src"); gst_pad_add_probe(srcpad, GST_PAD_PROBE_TYPE_BUFFER, drop_gap_buffers, NULL, NULL); gst_object_unref(srcpad); g_object_set(capsfilter, "caps", caps, NULL); CREATE_ELEMENT_WITH_ID(videoconvert, VIDEO_CONVERT, "source-video-convert", source_id); CREATE_ELEMENT_WITH_ID(videoscale, "videoscale", "source-video-scale", source_id); gst_bin_add_many(GST_BIN(source_bin), queue_pre, videoscale, videoconvert, capsfilter, queue_post, NULL); LINK_ELEMENTS(capsfilter, queue_post); LINK_ELEMENTS(videoconvert, capsfilter); LINK_ELEMENTS(videoscale, videoconvert); LINK_ELEMENTS(queue_pre, videoscale); break; } case OWR_MEDIA_TYPE_UNKNOWN: default: g_assert_not_reached(); goto done; } channel_name = g_strdup_printf("source-%u", source_id); g_object_set(source, "channel", channel_name, NULL); g_object_set(sink, "channel", channel_name, NULL); g_free(channel_name); /* Add and link the inter*sink to the actual source pipeline */ bin_name = g_strdup_printf("source-sink-bin-%u", source_id); sink_bin = gst_bin_new(bin_name); g_free(bin_name); gst_bin_add_many(GST_BIN(sink_bin), sink, sink_queue, NULL); gst_element_sync_state_with_parent(sink); gst_element_sync_state_with_parent(sink_queue); LINK_ELEMENTS(sink_queue, sink); sinkpad = gst_element_get_static_pad(sink_queue, "sink"); bin_pad = gst_ghost_pad_new("sink", sinkpad); gst_object_unref(sinkpad); gst_pad_set_active(bin_pad, TRUE); gst_element_add_pad(sink_bin, bin_pad); bin_pad = NULL; gst_bin_add(GST_BIN(source_pipeline), sink_bin); gst_element_sync_state_with_parent(sink_bin); LINK_ELEMENTS(tee, sink_bin); /* Start up our new bin and link it all */ srcpad = gst_element_get_static_pad(queue_post, "src"); g_assert(srcpad); bin_pad = gst_ghost_pad_new("src", srcpad); gst_object_unref(srcpad); gst_pad_set_active(bin_pad, TRUE); gst_element_add_pad(source_bin, bin_pad); gst_bin_add(GST_BIN(source_bin), source); LINK_ELEMENTS(source, queue_pre); done: gst_object_unref(source_pipeline); gst_object_unref(tee); return source_bin; }
/* * create_post_tee_bin * * The following chain is created after the tee for each output from the * source: * * +-------+ +---------------------+ +-------+ * ---+ queue +---+ conversion elements +---+ queue +--- * +-------+ +---------------------+ +-------+ */ static GstElement *create_post_tee_bin(OwrMediaSource *media_source, GstElement *source_bin, GstCaps *caps, GstPad *ghostpad, guint source_id) { OwrMediaType media_type; GstElement *post_tee_bin, *queue_pre, *queue_post, *capsfilter; GstPad *bin_pad, *queue_pre_pad, *srcpad; GSList *list = NULL; gchar *bin_name; bin_name = g_strdup_printf("source-post-tee-bin-%u", source_id); post_tee_bin = gst_bin_new(bin_name); if (!gst_bin_add(GST_BIN(source_bin), post_tee_bin)) { GST_ERROR("Failed to add %s to source bin", bin_name); g_free(bin_name); g_object_unref(post_tee_bin); post_tee_bin = NULL; goto done; } g_free(bin_name); gst_element_sync_state_with_parent(post_tee_bin); CREATE_ELEMENT_WITH_ID(queue_pre, "queue", "source-post-tee-queue", source_id); CREATE_ELEMENT_WITH_ID(capsfilter, "capsfilter", "source-output-capsfilter", source_id); list = g_slist_append(list, capsfilter); CREATE_ELEMENT_WITH_ID(queue_post, "queue", "source-output-queue", source_id); list = g_slist_append(list, queue_post); g_object_get(media_source, "media-type", &media_type, NULL); switch (media_type) { case OWR_MEDIA_TYPE_AUDIO: { GstElement *audioresample, *audioconvert; g_object_set(capsfilter, "caps", caps, NULL); CREATE_ELEMENT_WITH_ID(audioresample, "audioresample", "source-audio-resample", source_id); list = g_slist_prepend(list, audioresample); CREATE_ELEMENT_WITH_ID(audioconvert, "audioconvert", "source-audio-convert", source_id); list = g_slist_prepend(list, audioconvert); list = g_slist_prepend(list, queue_pre); gst_bin_add_many(GST_BIN(post_tee_bin), queue_pre, audioconvert, audioresample, capsfilter, queue_post, NULL); LINK_ELEMENTS(capsfilter, queue_post); LINK_ELEMENTS(audioresample, capsfilter); LINK_ELEMENTS(audioconvert, audioresample); LINK_ELEMENTS(queue_pre, audioconvert); break; } case OWR_MEDIA_TYPE_VIDEO: { GstElement *videorate, *videoscale, *videoconvert; GstCaps *source_caps; GstStructure *source_structure; gint fps_n = 0, fps_d = 1; source_caps = gst_caps_copy(caps); source_structure = gst_caps_get_structure(source_caps, 0); if (gst_structure_get_fraction(source_structure, "framerate", &fps_n, &fps_d)) gst_structure_remove_field(source_structure, "framerate"); g_object_set(capsfilter, "caps", source_caps, NULL); gst_caps_unref(source_caps); CREATE_ELEMENT_WITH_ID(videoconvert, VIDEO_CONVERT, "source-video-convert", source_id); list = g_slist_prepend(list, videoconvert); CREATE_ELEMENT_WITH_ID(videoscale, "videoscale", "source-video-scale", source_id); list = g_slist_prepend(list, videoscale); CREATE_ELEMENT_WITH_ID(videorate, "videorate", "source-video-rate", source_id); g_object_set(videorate, "drop-only", TRUE, "max-rate", fps_n / fps_d, NULL); list = g_slist_prepend(list, videorate); list = g_slist_prepend(list, queue_pre); gst_bin_add_many(GST_BIN(post_tee_bin), queue_pre, videorate, videoscale, videoconvert, capsfilter, queue_post, NULL); LINK_ELEMENTS(capsfilter, queue_post); LINK_ELEMENTS(videoconvert, capsfilter); LINK_ELEMENTS(videoscale, videoconvert); LINK_ELEMENTS(videorate, videoscale); LINK_ELEMENTS(queue_pre, videorate); break; } case OWR_MEDIA_TYPE_UNKNOWN: default: g_assert_not_reached(); goto done; } srcpad = gst_element_get_static_pad(queue_post, "src"); g_assert(srcpad); bin_pad = gst_ghost_pad_new("src", srcpad); gst_pad_set_active(bin_pad, TRUE); gst_element_add_pad(post_tee_bin, bin_pad); gst_object_unref(srcpad); gst_ghost_pad_set_target(GST_GHOST_PAD(ghostpad), bin_pad); gst_pad_set_active(ghostpad, TRUE); gst_element_add_pad(source_bin, ghostpad); g_slist_foreach(list, sync_to_parent, NULL); queue_pre_pad = gst_element_get_static_pad(queue_pre, "sink"); g_assert(queue_pre_pad); bin_pad = gst_ghost_pad_new("sink", queue_pre_pad); gst_pad_set_active(bin_pad, TRUE); gst_element_add_pad(post_tee_bin, bin_pad); gst_object_unref(queue_pre_pad); done: g_slist_free(list); list = NULL; return post_tee_bin; }