From: Sangchul Lee Date: Mon, 28 Sep 2020 07:44:42 +0000 (+0900) Subject: Split webrtc_private.c X-Git-Tag: submit/tizen/20210729.023123~208 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=1a92c47a4c90a431564024de37fa05281c3e6e20;p=platform%2Fcore%2Fapi%2Fwebrtc.git Split webrtc_private.c webrtc_source.c is added and codes regarding media source are moved into it. webrtc_sink.c is added and codes regarding rendering audio and video are moved into it. [Version] 0.1.34 [Issue Type] Refactoring Change-Id: I5835ddbc832386151cb537388da503714c44f64d Signed-off-by: Sangchul Lee --- diff --git a/include/webrtc_private.h b/include/webrtc_private.h index 3205b452..9ed043b2 100644 --- a/include/webrtc_private.h +++ b/include/webrtc_private.h @@ -107,6 +107,41 @@ do { \ #define SAFE_FREE(src) { if (src) { free(src); src = NULL; } } #define SAFE_STR(str) (str) ? str : "null" +#define CREATE_ELEMENT_FROM_REGISTRY(x_elem_info, x_klass_name, x_sink_caps, x_src_caps, x_element) \ +do { \ + x_elem_info.klass_name = x_klass_name; \ + x_elem_info.sink_caps = x_sink_caps; \ + x_elem_info.src_caps = x_src_caps; \ + x_element = _create_element_from_registry(&x_elem_info); \ + if (x_elem_info.sink_caps) \ + gst_caps_unref(x_elem_info.sink_caps); \ + if (x_elem_info.src_caps) \ + gst_caps_unref(x_elem_info.src_caps); \ + if (!x_element) { \ + LOG_ERROR("failed to create element of [%s]", x_klass_name); \ + return WEBRTC_ERROR_INVALID_OPERATION; \ + } \ +} while (0) + +#define MALLOC_AND_INIT_SLOT(x_slot, x_id, x_bin_name) \ +do { \ + x_slot = g_new0(webrtc_gst_slot_s, 1); \ + x_slot->id = x_id; \ + x_slot->bin = gst_bin_new(x_bin_name); \ + x_slot->mlines[MLINES_IDX_AUDIO] = -1; \ + x_slot->mlines[MLINES_IDX_VIDEO] = -1; \ +} while (0) + +#define GENERATE_DOT(x_webrtc, x_fmt, x_arg...) \ +do { \ + gchar *dot_name; \ + if (!x_webrtc->ini.generate_dot) \ + break; \ + dot_name = g_strdup_printf(""x_fmt"", x_arg); \ + _generate_dot(x_webrtc, dot_name); \ + g_free(dot_name); \ +} while (0) + typedef struct _webrtc_ini_s { gboolean generate_dot; gchar **gst_args; @@ -186,6 +221,13 @@ int _remove_media_source(webrtc_s *webrtc, unsigned int source_id); int _get_transceiver_direction(webrtc_s *webrtc, unsigned int source_id, webrtc_media_type_e media_type, webrtc_transceiver_direction_e *direction); int _set_transceiver_direction(webrtc_s *webrtc, unsigned int source_id, webrtc_media_type_e media_type, webrtc_transceiver_direction_e direction); +GstElement *_create_element(const char *factory_name, const char *name); +GstElement *_create_element_from_registry(element_info_s *elem_info); +int _add_no_target_ghostpad_to_slot(webrtc_gst_slot_s *slot, gboolean is_src, GstPad **new_pad); +int _set_ghost_pad_target(GstPad *ghost_pad, GstElement *target_element, gboolean is_src); +int _add_rendering_sink_bin(webrtc_s *webrtc, GstPad *src_pad); +void _generate_dot(webrtc_s *webrtc, const gchar *name); + int _webrtcbin_create_offer(webrtc_s *webrtc, char **offer); int _webrtcbin_create_answer(webrtc_s *webrtc, char **answer); int _webrtcbin_set_session_description(webrtc_s *webrtc, const char *description, gboolean is_remote); diff --git a/packaging/capi-media-webrtc.spec b/packaging/capi-media-webrtc.spec index c0be7d28..0ed53a9d 100644 --- a/packaging/capi-media-webrtc.spec +++ b/packaging/capi-media-webrtc.spec @@ -1,6 +1,6 @@ Name: capi-media-webrtc Summary: A WebRTC library in Tizen Native API -Version: 0.1.33 +Version: 0.1.34 Release: 0 Group: Multimedia/API License: Apache-2.0 diff --git a/src/webrtc_private.c b/src/webrtc_private.c index eb0efbb0..e66b2e0b 100644 --- a/src/webrtc_private.c +++ b/src/webrtc_private.c @@ -18,107 +18,9 @@ #include "webrtc.h" #include "webrtc_private.h" -#define GST_KLASS_NAME_ENCODER_AUDIO "Codec/Encoder/Audio" -#define GST_KLASS_NAME_ENCODER_VIDEO "Codec/Encoder/Video" -#define GST_KLASS_NAME_DECODER_AUDIO "Codec/Decoder/Audio" -#define GST_KLASS_NAME_DECODER_VIDEO "Codec/Decoder/Video" -#define GST_KLASS_NAME_PAYLOADER_RTP "Codec/Payloader/Network/RTP" -#define GST_KLASS_NAME_DEPAYLOADER_RTP "Codec/Depayloader/Network/RTP" -#define GST_KLASS_NAME_CONVERTER_AUDIO "Filter/Converter/Audio" -#define GST_KLASS_NAME_CONVERTER_VIDEO "Filter/Converter/Video" - -#define DEFAULT_ELEMENT_CAMERASRC "camerasrc" -#define DEFAULT_ELEMENT_AUDIOSRC "pulsesrc" -#define DEFAULT_ELEMENT_VIDEOTESTSRC "videotestsrc" -#define DEFAULT_ELEMENT_AUDIOTESTSRC "audiotestsrc" -#define DEFAULT_ELEMENT_CAPSFILTER "capsfilter" -#define DEFAULT_ELEMENT_QUEUE "queue" -#define DEFAULT_ELEMENT_VIDEOCONVERT "videoconvert" -#define DEFAULT_ELEMENT_AUDIOCONVERT "audioconvert" -#define DEFAULT_ELEMENT_AUDIORESAMPLE "audioresample" -#define DEFAULT_ELEMENT_VIDEOSINK "tizenwlsink" -#define DEFAULT_ELEMENT_AUDIOSINK "pulsesink" - -#define DEFAULT_VIDEO_ENCODED_MEDIA_TYPE "video/x-vp8" -#define DEFAULT_VIDEO_RAW_MEDIA_TYPE "video/x-raw" -#define DEFAULT_VIDEO_RAW_FORMAT "I420" -#define DEFAULT_VIDEO_WIDTH 352 -#define DEFAULT_VIDEO_HEIGHT 288 - -#define DEFAULT_AUDIO_ENCODED_MEDIA_TYPE "audio/x-opus" -#define DEFAULT_AUDIO_RAW_MEDIA_TYPE "audio/x-raw" -#define DEFAULT_AUDIO_RAW_FORMAT "S16LE" -#define DEFAULT_AUDIO_CHANNELS 1 -#define DEFAULT_AUDIO_SAMPLERATE 8000 - #define DEFAULT_DOT_FILE_NAME_PREFIX "webrtc" #define DEFAULT_DOT_DIRECTORY "/tmp" -typedef enum { - CODEC_TYPE_OPUS, - CODEC_TYPE_VORBIS, - CODEC_TYPE_VP8, - CODEC_TYPE_VP9, - CODEC_TYPE_THEORA, - CODEC_TYPE_H263, - CODEC_TYPE_H264, - CODEC_TYPE_H265, - CODEC_TYPE_NOT_SUPPORTED, -} codec_type_e; - -typedef struct { - const char *encoding_name; - const int clock_rate; -} payload_type_s; - -static payload_type_s payload_types[] = { - /* AUDIO */ - [CODEC_TYPE_OPUS] = { "OPUS", 48000 }, - [CODEC_TYPE_VORBIS] = { "VORBIS", -1 }, /* NOTE: -1 for various clock rate */ - /* VIDEO */ - [CODEC_TYPE_VP8] = { "VP8", 90000 }, - [CODEC_TYPE_VP9] = { "VP9", 90000 }, - [CODEC_TYPE_THEORA] = { "THEORA", 90000 }, - [CODEC_TYPE_H263] = { "H263", 90000 }, - [CODEC_TYPE_H264] = { "H264", 90000 }, - [CODEC_TYPE_H265] = { "H265", 90000 }, -}; - -#define CREATE_ELEMENT_FROM_REGISTRY(x_elem_info, x_klass_name, x_sink_caps, x_src_caps, x_element) \ -do { \ - x_elem_info.klass_name = x_klass_name; \ - x_elem_info.sink_caps = x_sink_caps; \ - x_elem_info.src_caps = x_src_caps; \ - x_element = __create_element_from_registry(&x_elem_info); \ - if (x_elem_info.sink_caps) \ - gst_caps_unref(x_elem_info.sink_caps); \ - if (x_elem_info.src_caps) \ - gst_caps_unref(x_elem_info.src_caps); \ - if (!x_element) { \ - LOG_ERROR("failed to create element of [%s]", x_klass_name); \ - return WEBRTC_ERROR_INVALID_OPERATION; \ - } \ -} while (0) - -#define MALLOC_AND_INIT_SLOT(x_slot, x_id, x_bin_name) \ -do { \ - x_slot = g_new0(webrtc_gst_slot_s, 1); \ - x_slot->id = x_id; \ - x_slot->bin = gst_bin_new(x_bin_name); \ - x_slot->mlines[MLINES_IDX_AUDIO] = -1; \ - x_slot->mlines[MLINES_IDX_VIDEO] = -1; \ -} while (0) - -#define GENERATE_DOT(x_webrtc, x_fmt, x_arg...) \ -do { \ - gchar *dot_name; \ - if (!x_webrtc->ini.generate_dot) \ - break; \ - dot_name = g_strdup_printf(""x_fmt"", x_arg); \ - __generate_dot(x_webrtc, dot_name); \ - g_free(dot_name); \ -} while (0) - static const char* __state_str[] = { "IDLE", "NEGOTIATING", @@ -137,7 +39,7 @@ static GstWebRTCRTPTransceiverDirection __direction_gst[] = { GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV }; -static void __generate_dot(webrtc_s *webrtc, const gchar *name) +void _generate_dot(webrtc_s *webrtc, const gchar *name) { gchar *dot_name; @@ -346,7 +248,7 @@ static gboolean __bus_watch_cb(GstBus *bus, GstMessage *message, gpointer user_d return TRUE; } -static GstElement *__create_element(const char *factory_name, const char *name) +GstElement *_create_element(const char *factory_name, const char *name) { GstElement *element = NULL; @@ -412,7 +314,7 @@ static int __rank_compare(GstPluginFeature *first, GstPluginFeature *second) return second_rank - first_rank; } -static GstElement *__create_element_from_registry(element_info_s *elem_info) +GstElement *_create_element_from_registry(element_info_s *elem_info) { GstElement *element = NULL; GList *factories = NULL; @@ -426,7 +328,7 @@ static GstElement *__create_element_from_registry(element_info_s *elem_info) if (factories) { factory = GST_ELEMENT_FACTORY(factories->data); LOG_DEBUG("sorted result element is [%s]", GST_OBJECT_NAME(factory)); - element = __create_element(GST_OBJECT_NAME(factory), NULL); + element = _create_element(GST_OBJECT_NAME(factory), NULL); } else { LOG_DEBUG("could not find any compatible element for klass_name[%s]", elem_info->klass_name); } @@ -759,185 +661,6 @@ static void __webrtcbin_ice_connection_state_cb(GstElement *webrtcbin, GParamSpe LOG_DEBUG("[IceConnectionState] is changed to [%s]", new_state); } -static webrtc_gst_slot_s* __find_sink_slot(webrtc_s *webrtc, const gchar *key) -{ - RET_VAL_IF(webrtc == NULL, NULL, "webrtc is NULL"); - RET_VAL_IF(key == NULL, NULL, "key is NULL"); - - return g_hash_table_lookup(webrtc->gst.sink_slots, key); -} - -static int __build_videosink(webrtc_s *webrtc, GstElement *decodebin, GstPad *src_pad) -{ - webrtc_gst_slot_s *sink; - GstElement *videoconvert; - GstElement *videosink; - - RET_VAL_IF(webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL"); - RET_VAL_IF(decodebin == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "decodebin is NULL"); - RET_VAL_IF(src_pad == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "src_pad is NULL"); - - sink = __find_sink_slot(webrtc, GST_ELEMENT_NAME(decodebin)); - RET_VAL_IF(sink == NULL, WEBRTC_ERROR_INVALID_OPERATION, "could not find an item by [%s] in sink slots", GST_ELEMENT_NAME(decodebin)); - RET_VAL_IF(sink->bin == NULL, WEBRTC_ERROR_INVALID_OPERATION, "bin is NULL"); - - sink->media_types |= MEDIA_TYPE_VIDEO; - - if (!(videoconvert = __create_element(DEFAULT_ELEMENT_VIDEOCONVERT, NULL))) { - LOG_ERROR("failed to create videoconvert"); - return WEBRTC_ERROR_INVALID_OPERATION; - } - - /* FIXME: get factory name from ini */ - if (!(videosink = __create_element(DEFAULT_ELEMENT_VIDEOSINK, NULL))) { - LOG_ERROR("failed to create videosink"); - return WEBRTC_ERROR_INVALID_OPERATION; - } - - gst_bin_add_many(GST_BIN(sink->bin), videoconvert, videosink, NULL); - - if (!gst_element_sync_state_with_parent(videoconvert)) { - LOG_ERROR("failed to gst_element_sync_state_with_parent() for [%s]", GST_ELEMENT_NAME(videoconvert)); - return WEBRTC_ERROR_INVALID_OPERATION; - } - if (!gst_element_sync_state_with_parent(videosink)) { - LOG_ERROR("failed to gst_element_sync_state_with_parent() for [%s]", GST_ELEMENT_NAME(videosink)); - return WEBRTC_ERROR_INVALID_OPERATION; - } - - if (!gst_element_link_many(decodebin, videoconvert, videosink, NULL)) { - LOG_ERROR("failed to gst_element_link_many()"); - return WEBRTC_ERROR_INVALID_OPERATION; - } - - return WEBRTC_ERROR_NONE; -} - -static int __build_audiosink(webrtc_s *webrtc, GstElement *decodebin, GstPad *src_pad) -{ - webrtc_gst_slot_s *sink; - GstElement *audioconvert; - GstElement *audioresample; - GstElement *audiosink; - - RET_VAL_IF(webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL"); - RET_VAL_IF(decodebin == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "decodebin is NULL"); - RET_VAL_IF(src_pad == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "src_pad is NULL"); - - sink = __find_sink_slot(webrtc, GST_ELEMENT_NAME(decodebin)); - RET_VAL_IF(sink == NULL, WEBRTC_ERROR_INVALID_OPERATION, "could not find an item by [%s] in sink slots", GST_ELEMENT_NAME(decodebin)); - RET_VAL_IF(sink->bin == NULL, WEBRTC_ERROR_INVALID_OPERATION, "bin is NULL"); - - sink->media_types |= MEDIA_TYPE_AUDIO; - - if (!(audioconvert = __create_element(DEFAULT_ELEMENT_AUDIOCONVERT, NULL))) { - LOG_ERROR("failed to create audioconvert"); - return WEBRTC_ERROR_INVALID_OPERATION; - } - - if (!(audioresample = __create_element(DEFAULT_ELEMENT_AUDIORESAMPLE, NULL))) { - LOG_ERROR("failed to create audioresample"); - return WEBRTC_ERROR_INVALID_OPERATION; - } - - /* FIXME: get factory name from ini */ - if (!(audiosink = __create_element(DEFAULT_ELEMENT_AUDIOSINK, NULL))) { - LOG_ERROR("failed to create audiosink"); - return WEBRTC_ERROR_INVALID_OPERATION; - } - - gst_bin_add_many(GST_BIN(sink->bin), audioconvert, audioresample, audiosink, NULL); - - if (!gst_element_sync_state_with_parent(audioconvert)) { - LOG_ERROR("failed to gst_element_sync_state_with_parent() for [%s]", GST_ELEMENT_NAME(audioconvert)); - return WEBRTC_ERROR_INVALID_OPERATION; - } - if (!gst_element_sync_state_with_parent(audioresample)) { - LOG_ERROR("failed to gst_element_sync_state_with_parent() for [%s]", GST_ELEMENT_NAME(audioresample)); - return WEBRTC_ERROR_INVALID_OPERATION; - } - if (!gst_element_sync_state_with_parent(audiosink)) { - LOG_ERROR("failed to gst_element_sync_state_with_parent() for [%s]", GST_ELEMENT_NAME(audiosink)); - return WEBRTC_ERROR_INVALID_OPERATION; - } - - if (!gst_element_link_many(decodebin, audioconvert, audioresample, audiosink, NULL)) { - LOG_ERROR("failed to gst_element_link_many()"); - return WEBRTC_ERROR_INVALID_OPERATION; - } - - return WEBRTC_ERROR_NONE; -} - -static void __decodebin_pad_added_cb(GstElement *decodebin, GstPad *new_pad, gpointer user_data) -{ - int ret = WEBRTC_ERROR_NONE; - webrtc_s *webrtc = (webrtc_s *)user_data; - const gchar *media_type; - - RET_IF(webrtc == NULL, "webrtc is NULL"); - - if (GST_PAD_DIRECTION(new_pad) != GST_PAD_SRC) - return; - - media_type = gst_structure_get_name(gst_caps_get_structure(gst_pad_get_current_caps(new_pad), 0)); - LOG_INFO("[%s], new_pad[%s], media_type[%s]", GST_ELEMENT_NAME(decodebin), GST_PAD_NAME(new_pad), media_type); - - if (g_strrstr(media_type, "video")) { - ret = __build_videosink(webrtc, decodebin, new_pad); - - } else if (g_strrstr(media_type, "audio")) { - ret = __build_audiosink(webrtc, decodebin, new_pad); - - } else { - LOG_ERROR("not supported media type[%s]", media_type); - return; - } - - if (ret != WEBRTC_ERROR_NONE) - LOG_ERROR("failed to build a rendering pipeline"); - - GENERATE_DOT(webrtc, "%s", GST_ELEMENT_NAME(decodebin)); -} - -static int __decodebin_autoplug_select_cb(GstElement *decodebin, GstPad *pad, GstCaps *caps, GstElementFactory *factory, gpointer user_data) -{ - /* NOTE : GstAutoplugSelectResult is defined in gstplay-enum.h but not exposed */ - typedef enum { - GST_AUTOPLUG_SELECT_TRY, - GST_AUTOPLUG_SELECT_EXPOSE, - GST_AUTOPLUG_SELECT_SKIP - } GstAutoplugSelectResult; - gchar *factory_name; - const gchar *klass; - GstAutoplugSelectResult result = GST_AUTOPLUG_SELECT_TRY; - webrtc_s *webrtc = (webrtc_s *)user_data; - - RET_VAL_IF(webrtc == NULL, GST_AUTOPLUG_SELECT_SKIP, "webrtc is NULL, skip it"); - - factory_name = GST_OBJECT_NAME(factory); - klass = gst_element_factory_get_metadata(factory, GST_ELEMENT_METADATA_KLASS); - - LOG_INFO("factory [name:%s, klass:%s]", factory_name, klass); - - return result; -} - -static unsigned int __get_id_from_pad_name(const gchar* name) -{ - gchar **tokens = NULL; - gint64 id; - - RET_VAL_IF(name == NULL, 0, "name is NULL"); - - tokens = g_strsplit(name, "_", 2); - id = g_ascii_strtoll(tokens[1], NULL, 10); - - g_strfreev(tokens); - - return (unsigned int)id; -} - static GstPad* __add_no_target_ghostpad(GstElement *bin, const char *pad_name, bool is_src) { gchar *bin_name = NULL; @@ -966,7 +689,7 @@ static GstPad* __add_no_target_ghostpad(GstElement *bin, const char *pad_name, b return ghost_pad; } -static int __add_no_target_ghostpad_to_slot(webrtc_gst_slot_s *slot, gboolean is_src, GstPad **new_pad) +int _add_no_target_ghostpad_to_slot(webrtc_gst_slot_s *slot, gboolean is_src, GstPad **new_pad) { gchar *pad_name; @@ -985,7 +708,7 @@ static int __add_no_target_ghostpad_to_slot(webrtc_gst_slot_s *slot, gboolean is return WEBRTC_ERROR_NONE; } -static int __set_ghost_pad_target(GstPad *ghost_pad, GstElement *target_element, gboolean is_src) +int _set_ghost_pad_target(GstPad *ghost_pad, GstElement *target_element, gboolean is_src) { GstPad *target_pad; @@ -1010,78 +733,6 @@ static int __set_ghost_pad_target(GstPad *ghost_pad, GstElement *target_element, return WEBRTC_ERROR_NONE; } -static int __add_rendering_sink_bin(webrtc_s *webrtc, GstPad *src_pad) -{ - int ret = WEBRTC_ERROR_NONE; - unsigned int id; - gchar *bin_name; - gchar *decodebin_name; - webrtc_gst_slot_s *sink; - GstElement *decodebin; - GstPad *sink_pad; - - RET_VAL_IF(webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL"); - RET_VAL_IF(src_pad == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "src_pad is NULL"); - - /* decodebin_name/sink will be freed by function which is set to g_hash_table_new_full() */ - id = __get_id_from_pad_name(GST_PAD_NAME(src_pad)); - bin_name = g_strdup_printf("rendering_sink_%u", id); - decodebin_name = g_strdup_printf("%s_decodebin", bin_name); - - MALLOC_AND_INIT_SLOT(sink, id, bin_name); - - g_free(bin_name); - - decodebin = __create_element("decodebin", decodebin_name); - if (!decodebin) - goto error_before_insert; - - gst_bin_add(GST_BIN(sink->bin), decodebin); - - g_signal_connect(decodebin, "pad-added", G_CALLBACK(__decodebin_pad_added_cb), webrtc); - g_signal_connect(decodebin, "autoplug-select", G_CALLBACK(__decodebin_autoplug_select_cb), webrtc); - - ret = __add_no_target_ghostpad_to_slot(sink, FALSE, &sink_pad); - if (ret != WEBRTC_ERROR_NONE) - goto error_before_insert; - - ret = __set_ghost_pad_target(sink_pad, decodebin, FALSE); - if (ret != WEBRTC_ERROR_NONE) - goto error_before_insert; - - if (!gst_bin_add(GST_BIN(webrtc->gst.pipeline), sink->bin)) { - LOG_ERROR("failed to gst_bin_add(), [%s] -> [%s] pipeline", GST_ELEMENT_NAME(sink->bin), GST_ELEMENT_NAME(webrtc->gst.pipeline)); - goto error_before_insert; - } - - if (gst_pad_link(src_pad, sink_pad) != GST_PAD_LINK_OK) { - LOG_ERROR("failed to gst_pad_link(), %s:%s", GST_PAD_NAME(src_pad), GST_PAD_NAME(sink_pad)); - goto error_before_insert; - } - - LOG_DEBUG("link pads successfully, [%s:%s] - [%s:%s]", - GST_ELEMENT_NAME(webrtc->gst.webrtcbin), GST_PAD_NAME(src_pad), GST_ELEMENT_NAME(sink->bin), GST_PAD_NAME(sink_pad)); - - if (!g_hash_table_insert(webrtc->gst.sink_slots, decodebin_name, (gpointer)sink)) { - LOG_ERROR("should not be reached here, bin_name[%s] already exist, sink id[%u] will be removed", decodebin_name, sink->id); - g_hash_table_remove(webrtc->gst.sink_slots, decodebin_name); - return WEBRTC_ERROR_INVALID_OPERATION; - } - - gst_element_sync_state_with_parent(sink->bin); - - LOG_INFO("added a sink slot[%p, id:%u]", sink, sink->id); - - return WEBRTC_ERROR_NONE; - -error_before_insert: - gst_object_unref(sink_pad); - g_free(decodebin_name); - g_free(sink); - - return WEBRTC_ERROR_INVALID_OPERATION; -} - static void __webrtcbin_pad_added_cb(GstElement *webrtcbin, GstPad *new_pad, gpointer user_data) { int ret = WEBRTC_ERROR_NONE; @@ -1094,8 +745,8 @@ static void __webrtcbin_pad_added_cb(GstElement *webrtcbin, GstPad *new_pad, gpo LOG_INFO("new pad[%s] is added", GST_PAD_NAME(new_pad)); - ret = __add_rendering_sink_bin(webrtc, new_pad); - RET_IF(ret != WEBRTC_ERROR_NONE, "failed to __add_rendering_sink_bin()"); + ret = _add_rendering_sink_bin(webrtc, new_pad); + RET_IF(ret != WEBRTC_ERROR_NONE, "failed to _add_rendering_sink_bin()"); GENERATE_DOT(webrtc, "webrtcbin_%s", GST_PAD_NAME(new_pad)); } @@ -1157,7 +808,7 @@ int _gst_build_pipeline(webrtc_s *webrtc) goto error; } - if (!(webrtc->gst.webrtcbin = __create_element("webrtcbin", NULL))) { + if (!(webrtc->gst.webrtcbin = _create_element("webrtcbin", NULL))) { LOG_ERROR("failed to create webrtcbin"); goto error; } @@ -1249,471 +900,6 @@ int _gst_pipeline_set_state(webrtc_s *webrtc, GstState state) return WEBRTC_ERROR_NONE; } -static GstCaps *__make_default_raw_caps(webrtc_media_source_type_e type) -{ - GstCaps *caps = NULL; - - switch (type) { - case WEBRTC_MEDIA_SOURCE_TYPE_CAMERA: - case WEBRTC_MEDIA_SOURCE_TYPE_VIDEOTEST: - /* FIXME: get default value from ini */ - caps = gst_caps_new_simple(DEFAULT_VIDEO_RAW_MEDIA_TYPE, - "format", G_TYPE_STRING, DEFAULT_VIDEO_RAW_FORMAT, - "width", G_TYPE_INT, DEFAULT_VIDEO_WIDTH, - "height", G_TYPE_INT, DEFAULT_VIDEO_HEIGHT, - NULL); - break; - - case WEBRTC_MEDIA_SOURCE_TYPE_MIC: - case WEBRTC_MEDIA_SOURCE_TYPE_AUDIOTEST: - /* FIXME: get default value from ini */ - caps = gst_caps_new_simple(DEFAULT_AUDIO_RAW_MEDIA_TYPE, - "format", G_TYPE_STRING, DEFAULT_AUDIO_RAW_FORMAT, - "channels", G_TYPE_INT, DEFAULT_AUDIO_CHANNELS, - "rate", G_TYPE_INT, DEFAULT_AUDIO_SAMPLERATE, - NULL); - break; - - default: - LOG_ERROR_IF_REACHED("type(%d)", type); - break; - } - - return caps; -} - -/* Use g_free() to free the media_type parameter. */ -static GstCaps *__make_default_encoded_caps(webrtc_media_source_type_e type, gchar **media_type) -{ - GstCaps *caps; - const char *_media_type; - - switch (type) { - case WEBRTC_MEDIA_SOURCE_TYPE_CAMERA: - case WEBRTC_MEDIA_SOURCE_TYPE_VIDEOTEST: - /* FIXME: get default value from ini */ - _media_type = DEFAULT_VIDEO_ENCODED_MEDIA_TYPE; - caps = gst_caps_new_simple(_media_type, - "width", G_TYPE_INT, DEFAULT_VIDEO_WIDTH, - "height", G_TYPE_INT, DEFAULT_VIDEO_HEIGHT, - NULL); - break; - - case WEBRTC_MEDIA_SOURCE_TYPE_MIC: - case WEBRTC_MEDIA_SOURCE_TYPE_AUDIOTEST: - /* FIXME: get default value from ini */ - _media_type = DEFAULT_AUDIO_ENCODED_MEDIA_TYPE; - caps = gst_caps_new_simple(DEFAULT_AUDIO_ENCODED_MEDIA_TYPE, - "channels", G_TYPE_INT, DEFAULT_AUDIO_CHANNELS, - "rate", G_TYPE_INT, DEFAULT_AUDIO_SAMPLERATE, - NULL); - break; - - default: - LOG_ERROR_IF_REACHED("type(%d)", type); - return NULL; - } - - if (media_type) - *media_type = g_strdup(_media_type); - - return caps; -} - -static codec_type_e __get_codec_type(const gchar *media_type) -{ - if (!g_strcmp0(media_type, "audio/x-opus")) - return CODEC_TYPE_OPUS; - else if (!g_strcmp0(media_type, "audio/x-vorbis")) - return CODEC_TYPE_VORBIS; - else if (!g_strcmp0(media_type, "video/x-vp8")) - return CODEC_TYPE_VP8; - else if (!g_strcmp0(media_type, "video/x-vp9")) - return CODEC_TYPE_VP9; - else if (!g_strcmp0(media_type, "video/x-theora")) - return CODEC_TYPE_THEORA; - else if (!g_strcmp0(media_type, "video/x-h263")) - return CODEC_TYPE_H263; - else if (!g_strcmp0(media_type, "video/x-h264")) - return CODEC_TYPE_H264; - else if (!g_strcmp0(media_type, "video/x-h265")) - return CODEC_TYPE_H265; - else - return CODEC_TYPE_NOT_SUPPORTED; -} - -static GstCaps *__make_rtp_caps(const gchar *media_type, unsigned int id) -{ - gchar *caps_str; - GstCaps *caps; - codec_type_e codec_type = __get_codec_type(media_type); - - RET_VAL_IF(codec_type == CODEC_TYPE_NOT_SUPPORTED, NULL, "media_type[%s] is not supported", media_type); - - caps = gst_caps_new_simple("application/x-rtp", - "media", G_TYPE_STRING, g_strrstr(media_type, "video") ? "video" : "audio", - "clock-rate", G_TYPE_INT, payload_types[codec_type].clock_rate, /* FIXME: support various clock-rate */ - "encoding-name", G_TYPE_STRING, payload_types[codec_type].encoding_name, - "payload", G_TYPE_INT, id + 95, NULL); - - caps_str = gst_caps_to_string(caps); - LOG_DEBUG("RTP caps is created [%s]", caps_str); - - g_free(caps_str); - - return caps; -} - -static int __create_rest_of_elements(webrtc_gst_slot_s *source, webrtc_media_source_type_e type, GstElement **capsfilter, GstElement **encoder, GstElement **payloader, GstElement **queue, GstElement **capsfilter2) -{ - GstCaps *sink_caps; - element_info_s elem_info; - const gchar *encoder_klass_name; - gchar *media_type; - - RET_VAL_IF(source == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "source is NULL"); - RET_VAL_IF(capsfilter == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "capsfilter is NULL"); - RET_VAL_IF(encoder == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "encoder is NULL"); - RET_VAL_IF(payloader == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "payloader is NULL"); - RET_VAL_IF(queue == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "queue is NULL"); - RET_VAL_IF(capsfilter2 == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "capsfilter2 is NULL"); - - if (!(*capsfilter = __create_element(DEFAULT_ELEMENT_CAPSFILTER, NULL))) { - LOG_ERROR("failed to create capsfilter"); - return WEBRTC_ERROR_INVALID_OPERATION; - } - if ((sink_caps = __make_default_raw_caps(type))) { - g_object_set(G_OBJECT(*capsfilter), "caps", sink_caps, NULL); - gst_caps_unref(sink_caps); - } - - if (type == WEBRTC_MEDIA_SOURCE_TYPE_CAMERA || type == WEBRTC_MEDIA_SOURCE_TYPE_VIDEOTEST) - encoder_klass_name = GST_KLASS_NAME_ENCODER_VIDEO; - else - encoder_klass_name = GST_KLASS_NAME_ENCODER_AUDIO; - - CREATE_ELEMENT_FROM_REGISTRY(elem_info, encoder_klass_name, - __make_default_raw_caps(type), - __make_default_encoded_caps(type, NULL), - *encoder); - - CREATE_ELEMENT_FROM_REGISTRY(elem_info, GST_KLASS_NAME_PAYLOADER_RTP, - __make_default_encoded_caps(type, &media_type), - NULL, - *payloader); - - if (!(*queue = __create_element(DEFAULT_ELEMENT_QUEUE, NULL))) { - LOG_ERROR("failed to create queue"); - g_free(media_type); - return WEBRTC_ERROR_INVALID_OPERATION; - } - - if (!(*capsfilter2 = __create_element(DEFAULT_ELEMENT_CAPSFILTER, NULL))) { - LOG_ERROR("failed to create capsfilter"); - g_free(media_type); - return WEBRTC_ERROR_INVALID_OPERATION; - } - if ((sink_caps = __make_rtp_caps(media_type, source->id))) { - g_object_set(G_OBJECT(*capsfilter2), "caps", sink_caps, NULL); - gst_caps_unref(sink_caps); - } - - g_free(media_type); - - return WEBRTC_ERROR_NONE; -} - -static int __build_camerasrc(webrtc_gst_slot_s *source, GstPad *ghost_src_pad) -{ - int ret = WEBRTC_ERROR_NONE; - GstElement *camerasrc; - GstElement *capsfilter; - GstElement *videoenc; - GstElement *videopay; - GstElement *queue; - GstElement *capsfilter2; - - RET_VAL_IF(source == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "source is NULL"); - RET_VAL_IF(ghost_src_pad == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "ghost_src_pad is NULL"); - RET_VAL_IF(source->bin == NULL, WEBRTC_ERROR_INVALID_OPERATION, "bin is NULL"); - - source->media_types |= MEDIA_TYPE_VIDEO; - - /* FIXME: get factory name from ini */ - if (!(camerasrc = __create_element(DEFAULT_ELEMENT_CAMERASRC, NULL))) { - LOG_ERROR("failed to create camerasrc"); - return WEBRTC_ERROR_INVALID_OPERATION; - } - /* FIXME: set camera default setting from ini */ - - if ((ret = __create_rest_of_elements(source, WEBRTC_MEDIA_SOURCE_TYPE_CAMERA, &capsfilter, &videoenc, &videopay, &queue, &capsfilter2)) != WEBRTC_ERROR_NONE) - return ret; - - gst_bin_add_many(GST_BIN(source->bin), camerasrc, capsfilter, videoenc, videopay, queue, capsfilter2, NULL); - if (!gst_element_link_many(camerasrc, capsfilter, videoenc, videopay, queue, capsfilter2, NULL)) { - LOG_ERROR("failed to gst_element_link_many()"); - return WEBRTC_ERROR_INVALID_OPERATION; - } - - return __set_ghost_pad_target(ghost_src_pad, capsfilter2, TRUE); -} - -static int __build_audiosrc(webrtc_gst_slot_s *source, GstPad *ghost_src_pad) -{ - int ret = WEBRTC_ERROR_NONE; - GstElement *audiosrc; - GstElement *capsfilter; - GstElement *audioenc; - GstElement *audiopay; - GstElement *queue; - GstElement *capsfilter2; - - RET_VAL_IF(source == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "source is NULL"); - RET_VAL_IF(ghost_src_pad == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "ghost_src_pad is NULL"); - RET_VAL_IF(source->bin == NULL, WEBRTC_ERROR_INVALID_OPERATION, "bin is NULL"); - - source->media_types |= MEDIA_TYPE_AUDIO; - - if (!(audiosrc = __create_element(DEFAULT_ELEMENT_AUDIOSRC, NULL))) { - LOG_ERROR("failed to create audiosrc"); - return WEBRTC_ERROR_INVALID_OPERATION; - } - - if ((ret = __create_rest_of_elements(source, WEBRTC_MEDIA_SOURCE_TYPE_MIC, &capsfilter, &audioenc, &audiopay, &queue, &capsfilter2)) != WEBRTC_ERROR_NONE) - return ret; - - gst_bin_add_many(GST_BIN(source->bin), audiosrc, capsfilter, audioenc, audiopay, queue, capsfilter2, NULL); - if (!gst_element_link_many(audiosrc, capsfilter, audioenc, audiopay, queue, capsfilter2, NULL)) { - LOG_ERROR("failed to gst_element_link_many()"); - return WEBRTC_ERROR_INVALID_OPERATION; - } - - return __set_ghost_pad_target(ghost_src_pad, capsfilter2, TRUE); -} - -static int __build_videotestsrc(webrtc_gst_slot_s *source, GstPad *ghost_src_pad) -{ - int ret = WEBRTC_ERROR_NONE; - GstElement *videotestsrc; - GstElement *capsfilter; - GstElement *videoenc; - GstElement *videopay; - GstElement *queue; - GstElement *capsfilter2; - - RET_VAL_IF(source == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "source is NULL"); - RET_VAL_IF(ghost_src_pad == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "ghost_src_pad is NULL"); - RET_VAL_IF(source->bin == NULL, WEBRTC_ERROR_INVALID_OPERATION, "bin is NULL"); - - source->media_types |= MEDIA_TYPE_VIDEO; - - if (!(videotestsrc = __create_element(DEFAULT_ELEMENT_VIDEOTESTSRC, NULL))) { - LOG_ERROR("failed to create videotestsrc"); - return WEBRTC_ERROR_INVALID_OPERATION; - } - g_object_set(G_OBJECT(videotestsrc), "is-live", TRUE, NULL); - - if ((ret = __create_rest_of_elements(source, WEBRTC_MEDIA_SOURCE_TYPE_VIDEOTEST, &capsfilter, &videoenc, &videopay, &queue, &capsfilter2)) != WEBRTC_ERROR_NONE) - return ret; - - gst_bin_add_many(GST_BIN(source->bin), videotestsrc, capsfilter, videoenc, videopay, queue, capsfilter2, NULL); - if (!gst_element_link_many(videotestsrc, capsfilter, videoenc, videopay, queue, capsfilter2, NULL)) { - LOG_ERROR("failed to gst_element_link_many()"); - return WEBRTC_ERROR_INVALID_OPERATION; - } - - return __set_ghost_pad_target(ghost_src_pad, capsfilter2, TRUE); -} - -static int __build_audiotestsrc(webrtc_gst_slot_s *source, GstPad *ghost_src_pad) -{ - int ret = WEBRTC_ERROR_NONE; - GstElement *audiotestsrc; - GstElement *capsfilter; - GstElement *audioenc; - GstElement *audiopay; - GstElement *queue; - GstElement *capsfilter2; - - RET_VAL_IF(source == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "source is NULL"); - RET_VAL_IF(ghost_src_pad == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "ghost_src_pad is NULL"); - RET_VAL_IF(source->bin == NULL, WEBRTC_ERROR_INVALID_OPERATION, "bin is NULL"); - - source->media_types |= MEDIA_TYPE_AUDIO; - - if (!(audiotestsrc = __create_element(DEFAULT_ELEMENT_AUDIOTESTSRC, NULL))) { - LOG_ERROR("failed to create audiotestsrc"); - return WEBRTC_ERROR_INVALID_OPERATION; - } - g_object_set(G_OBJECT(audiotestsrc), "is-live", TRUE, NULL); - - if ((ret = __create_rest_of_elements(source, WEBRTC_MEDIA_SOURCE_TYPE_AUDIOTEST, &capsfilter, &audioenc, &audiopay, &queue, &capsfilter2)) != WEBRTC_ERROR_NONE) - return ret; - - gst_bin_add_many(GST_BIN(source->bin), audiotestsrc, capsfilter, audioenc, audiopay, queue, capsfilter2, NULL); - if (!gst_element_link_many(audiotestsrc, capsfilter, audioenc, audiopay, queue, capsfilter2, NULL)) { - LOG_ERROR("failed to gst_element_link_many()"); - return WEBRTC_ERROR_INVALID_OPERATION; - } - - return __set_ghost_pad_target(ghost_src_pad, capsfilter2, TRUE); -} - -static int __build_source_bin(webrtc_gst_slot_s *source, webrtc_media_source_type_e type) -{ - int ret = WEBRTC_ERROR_NONE; - GstPad *src_pad; - - RET_VAL_IF(source == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "source is NULL"); - RET_VAL_IF(source->bin == NULL, WEBRTC_ERROR_INVALID_OPERATION, "bin is NULL"); - - ret = __add_no_target_ghostpad_to_slot(source, TRUE, &src_pad); - RET_VAL_IF(ret != WEBRTC_ERROR_NONE, ret, "failed to __add_no_target_ghostpad_to_slot()"); - - switch (type) { - case WEBRTC_MEDIA_SOURCE_TYPE_CAMERA: - return __build_camerasrc(source, src_pad); - - case WEBRTC_MEDIA_SOURCE_TYPE_MIC: - return __build_audiosrc(source, src_pad); - - case WEBRTC_MEDIA_SOURCE_TYPE_VIDEOTEST: - return __build_videotestsrc(source, src_pad); - - case WEBRTC_MEDIA_SOURCE_TYPE_AUDIOTEST: - return __build_audiotestsrc(source, src_pad); - - default: - LOG_ERROR_IF_REACHED("type(%d)", type); - return WEBRTC_ERROR_INVALID_PARAMETER; - } - - return WEBRTC_ERROR_NONE; -} - -static unsigned int __get_unoccupied_id(GHashTable *slots) -{ - int i; - gchar *key; - - RET_VAL_IF(slots == NULL, 0, "slot is NULL"); - - /* Payload identifiers 96–127 are used for payloads defined dynamically during a session, - * hence the id range is limited here to 1-32. */ - for (i = 1; i < 33; i++) { - key = g_strdup_printf("media_source_%u", i); - if (g_hash_table_contains(slots, key)) { - g_free(key); - continue; - } - g_free(key); - return i; - } - - LOG_ERROR("all slots are occupied(1-32)"); - - return 0; -} - -int _add_media_source(webrtc_s *webrtc, webrtc_media_source_type_e type, unsigned int *source_id) -{ - int ret = WEBRTC_ERROR_NONE; - unsigned int id; - webrtc_gst_slot_s *source = NULL; - gchar *bin_name = NULL; - GstPad *webrtc_sinkpad; - gchar *webrtc_sinkpad_name; - gchar *bin_srcpad_name = NULL; - - RET_VAL_IF(webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL"); - RET_VAL_IF(webrtc->gst.source_slots == NULL, WEBRTC_ERROR_INVALID_OPERATION, "source_slots is NULL"); - RET_VAL_IF(source_id == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "source_id is NULL"); - - /* bin_name/source will be freed by function which is set to g_hash_table_new_full() */ - id = __get_unoccupied_id(webrtc->gst.source_slots); - RET_VAL_IF(id == 0, WEBRTC_ERROR_INVALID_OPERATION, "source_slots are full"); - - bin_name = g_strdup_printf("media_source_%u", id); - - MALLOC_AND_INIT_SLOT(source, id, bin_name); - - ret = __build_source_bin(source, type); - if (ret != WEBRTC_ERROR_NONE) { - LOG_ERROR("failed to __build_source_bin()"); - goto error; - } - - if (!gst_bin_add(GST_BIN(webrtc->gst.pipeline), source->bin)) { - LOG_ERROR("failed to gst_bin_add(), [%s] -> [%s] pipeline", GST_ELEMENT_NAME(source->bin), GST_ELEMENT_NAME(webrtc->gst.pipeline)); - goto error; - } - - /* The gst_element_get_request_pad() of webrtcbin will trigger the transciever callback. To update the mline value of - * new transceiver object to the source structure in the callback, hash table inserting should be preceded. */ - if (!g_hash_table_insert(webrtc->gst.source_slots, bin_name, (gpointer)source)) { - LOG_ERROR("should not be reached here, bin_name[%s] already exist, source id[%u] will be removed", bin_name, source->id); - g_hash_table_remove(webrtc->gst.source_slots, bin_name); - return WEBRTC_ERROR_INVALID_OPERATION; - } - - if (!(webrtc_sinkpad = gst_element_get_request_pad(webrtc->gst.webrtcbin, "sink_%u"))) { - LOG_ERROR("failed to gst_element_get_request_pad()"); - goto error_after_insert; - } - if (!(webrtc_sinkpad_name = gst_pad_get_name(webrtc_sinkpad))) { - LOG_ERROR("failed to gst_pad_get_name()"); - goto error_after_insert; - } - bin_srcpad_name = g_strdup_printf("src_%u", id); - if (!gst_element_link_pads(source->bin, bin_srcpad_name, webrtc->gst.webrtcbin, webrtc_sinkpad_name)) { - LOG_ERROR("failed to link pads, [%s:%s] - [%s:%s]", - GST_ELEMENT_NAME(source->bin), bin_srcpad_name, GST_ELEMENT_NAME(webrtc->gst.webrtcbin), webrtc_sinkpad_name); - goto error_after_insert; - } - LOG_DEBUG("link pads successfully, [%s:%s] - [%s:%s]", - GST_ELEMENT_NAME(source->bin), bin_srcpad_name, GST_ELEMENT_NAME(webrtc->gst.webrtcbin), webrtc_sinkpad_name); - - *source_id = id; - - LOG_INFO("added a source slot[%p, id:%u]", source, source->id); - - g_free(bin_srcpad_name); - - return WEBRTC_ERROR_NONE; - -error_after_insert: - g_hash_table_remove(webrtc->gst.source_slots, bin_name); - g_free(bin_srcpad_name); - - return WEBRTC_ERROR_INVALID_OPERATION; - -error: - g_free(bin_name); - g_free(source); - - return WEBRTC_ERROR_INVALID_OPERATION; -} - -int _remove_media_source(webrtc_s *webrtc, unsigned int source_id) -{ - int ret = WEBRTC_ERROR_NONE; - gchar *bin_name = NULL; - - RET_VAL_IF(webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL"); - RET_VAL_IF(webrtc->gst.source_slots == NULL, WEBRTC_ERROR_INVALID_OPERATION, "source_slots is NULL"); - - bin_name = g_strdup_printf("media_source_%u", source_id); - - if (!g_hash_table_remove(webrtc->gst.source_slots, (gpointer)bin_name)) { - LOG_ERROR("failed to find media source by id[%u]", source_id); - ret = WEBRTC_ERROR_INVALID_PARAMETER; - } - - g_free(bin_name); - - return ret; -} - static gboolean __check_id_equal_cb(gpointer key, gpointer value, gpointer user_data) { webrtc_gst_slot_s *slot = value; diff --git a/src/webrtc_sink.c b/src/webrtc_sink.c new file mode 100644 index 00000000..5f07664f --- /dev/null +++ b/src/webrtc_sink.c @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "webrtc.h" +#include "webrtc_private.h" + +#define DEFAULT_ELEMENT_VIDEOCONVERT "videoconvert" +#define DEFAULT_ELEMENT_AUDIOCONVERT "audioconvert" +#define DEFAULT_ELEMENT_AUDIORESAMPLE "audioresample" +#define DEFAULT_ELEMENT_VIDEOSINK "tizenwlsink" +#define DEFAULT_ELEMENT_AUDIOSINK "pulsesink" + +static webrtc_gst_slot_s* __find_sink_slot(webrtc_s *webrtc, const gchar *key) +{ + RET_VAL_IF(webrtc == NULL, NULL, "webrtc is NULL"); + RET_VAL_IF(key == NULL, NULL, "key is NULL"); + + return g_hash_table_lookup(webrtc->gst.sink_slots, key); +} + +static int __build_videosink(webrtc_s *webrtc, GstElement *decodebin, GstPad *src_pad) +{ + webrtc_gst_slot_s *sink; + GstElement *videoconvert; + GstElement *videosink; + + RET_VAL_IF(webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL"); + RET_VAL_IF(decodebin == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "decodebin is NULL"); + RET_VAL_IF(src_pad == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "src_pad is NULL"); + + sink = __find_sink_slot(webrtc, GST_ELEMENT_NAME(decodebin)); + RET_VAL_IF(sink == NULL, WEBRTC_ERROR_INVALID_OPERATION, "could not find an item by [%s] in sink slots", GST_ELEMENT_NAME(decodebin)); + RET_VAL_IF(sink->bin == NULL, WEBRTC_ERROR_INVALID_OPERATION, "bin is NULL"); + + sink->media_types |= MEDIA_TYPE_VIDEO; + + if (!(videoconvert = _create_element(DEFAULT_ELEMENT_VIDEOCONVERT, NULL))) { + LOG_ERROR("failed to create videoconvert"); + return WEBRTC_ERROR_INVALID_OPERATION; + } + + /* FIXME: get factory name from ini */ + if (!(videosink = _create_element(DEFAULT_ELEMENT_VIDEOSINK, NULL))) { + LOG_ERROR("failed to create videosink"); + return WEBRTC_ERROR_INVALID_OPERATION; + } + + gst_bin_add_many(GST_BIN(sink->bin), videoconvert, videosink, NULL); + + if (!gst_element_sync_state_with_parent(videoconvert)) { + LOG_ERROR("failed to gst_element_sync_state_with_parent() for [%s]", GST_ELEMENT_NAME(videoconvert)); + return WEBRTC_ERROR_INVALID_OPERATION; + } + if (!gst_element_sync_state_with_parent(videosink)) { + LOG_ERROR("failed to gst_element_sync_state_with_parent() for [%s]", GST_ELEMENT_NAME(videosink)); + return WEBRTC_ERROR_INVALID_OPERATION; + } + + if (!gst_element_link_many(decodebin, videoconvert, videosink, NULL)) { + LOG_ERROR("failed to gst_element_link_many()"); + return WEBRTC_ERROR_INVALID_OPERATION; + } + + return WEBRTC_ERROR_NONE; +} + +static int __build_audiosink(webrtc_s *webrtc, GstElement *decodebin, GstPad *src_pad) +{ + webrtc_gst_slot_s *sink; + GstElement *audioconvert; + GstElement *audioresample; + GstElement *audiosink; + + RET_VAL_IF(webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL"); + RET_VAL_IF(decodebin == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "decodebin is NULL"); + RET_VAL_IF(src_pad == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "src_pad is NULL"); + + sink = __find_sink_slot(webrtc, GST_ELEMENT_NAME(decodebin)); + RET_VAL_IF(sink == NULL, WEBRTC_ERROR_INVALID_OPERATION, "could not find an item by [%s] in sink slots", GST_ELEMENT_NAME(decodebin)); + RET_VAL_IF(sink->bin == NULL, WEBRTC_ERROR_INVALID_OPERATION, "bin is NULL"); + + sink->media_types |= MEDIA_TYPE_AUDIO; + + if (!(audioconvert = _create_element(DEFAULT_ELEMENT_AUDIOCONVERT, NULL))) { + LOG_ERROR("failed to create audioconvert"); + return WEBRTC_ERROR_INVALID_OPERATION; + } + + if (!(audioresample = _create_element(DEFAULT_ELEMENT_AUDIORESAMPLE, NULL))) { + LOG_ERROR("failed to create audioresample"); + return WEBRTC_ERROR_INVALID_OPERATION; + } + + /* FIXME: get factory name from ini */ + if (!(audiosink = _create_element(DEFAULT_ELEMENT_AUDIOSINK, NULL))) { + LOG_ERROR("failed to create audiosink"); + return WEBRTC_ERROR_INVALID_OPERATION; + } + + gst_bin_add_many(GST_BIN(sink->bin), audioconvert, audioresample, audiosink, NULL); + + if (!gst_element_sync_state_with_parent(audioconvert)) { + LOG_ERROR("failed to gst_element_sync_state_with_parent() for [%s]", GST_ELEMENT_NAME(audioconvert)); + return WEBRTC_ERROR_INVALID_OPERATION; + } + if (!gst_element_sync_state_with_parent(audioresample)) { + LOG_ERROR("failed to gst_element_sync_state_with_parent() for [%s]", GST_ELEMENT_NAME(audioresample)); + return WEBRTC_ERROR_INVALID_OPERATION; + } + if (!gst_element_sync_state_with_parent(audiosink)) { + LOG_ERROR("failed to gst_element_sync_state_with_parent() for [%s]", GST_ELEMENT_NAME(audiosink)); + return WEBRTC_ERROR_INVALID_OPERATION; + } + + if (!gst_element_link_many(decodebin, audioconvert, audioresample, audiosink, NULL)) { + LOG_ERROR("failed to gst_element_link_many()"); + return WEBRTC_ERROR_INVALID_OPERATION; + } + + return WEBRTC_ERROR_NONE; +} + +static void __decodebin_pad_added_cb(GstElement *decodebin, GstPad *new_pad, gpointer user_data) +{ + int ret = WEBRTC_ERROR_NONE; + webrtc_s *webrtc = (webrtc_s *)user_data; + const gchar *media_type; + + RET_IF(webrtc == NULL, "webrtc is NULL"); + + if (GST_PAD_DIRECTION(new_pad) != GST_PAD_SRC) + return; + + media_type = gst_structure_get_name(gst_caps_get_structure(gst_pad_get_current_caps(new_pad), 0)); + LOG_INFO("[%s], new_pad[%s], media_type[%s]", GST_ELEMENT_NAME(decodebin), GST_PAD_NAME(new_pad), media_type); + + if (g_strrstr(media_type, "video")) { + ret = __build_videosink(webrtc, decodebin, new_pad); + + } else if (g_strrstr(media_type, "audio")) { + ret = __build_audiosink(webrtc, decodebin, new_pad); + + } else { + LOG_ERROR("not supported media type[%s]", media_type); + return; + } + + if (ret != WEBRTC_ERROR_NONE) + LOG_ERROR("failed to build a rendering pipeline"); + + GENERATE_DOT(webrtc, "%s", GST_ELEMENT_NAME(decodebin)); +} + +static int __decodebin_autoplug_select_cb(GstElement *decodebin, GstPad *pad, GstCaps *caps, GstElementFactory *factory, gpointer user_data) +{ + /* NOTE : GstAutoplugSelectResult is defined in gstplay-enum.h but not exposed */ + typedef enum { + GST_AUTOPLUG_SELECT_TRY, + GST_AUTOPLUG_SELECT_EXPOSE, + GST_AUTOPLUG_SELECT_SKIP + } GstAutoplugSelectResult; + gchar *factory_name; + const gchar *klass; + GstAutoplugSelectResult result = GST_AUTOPLUG_SELECT_TRY; + webrtc_s *webrtc = (webrtc_s *)user_data; + + RET_VAL_IF(webrtc == NULL, GST_AUTOPLUG_SELECT_SKIP, "webrtc is NULL, skip it"); + + factory_name = GST_OBJECT_NAME(factory); + klass = gst_element_factory_get_metadata(factory, GST_ELEMENT_METADATA_KLASS); + + LOG_INFO("factory [name:%s, klass:%s]", factory_name, klass); + + return result; +} + +static unsigned int __get_id_from_pad_name(const gchar* name) +{ + gchar **tokens = NULL; + gint64 id; + + RET_VAL_IF(name == NULL, 0, "name is NULL"); + + tokens = g_strsplit(name, "_", 2); + id = g_ascii_strtoll(tokens[1], NULL, 10); + + g_strfreev(tokens); + + return (unsigned int)id; +} + +int _add_rendering_sink_bin(webrtc_s *webrtc, GstPad *src_pad) +{ + int ret = WEBRTC_ERROR_NONE; + unsigned int id; + gchar *bin_name; + gchar *decodebin_name; + webrtc_gst_slot_s *sink; + GstElement *decodebin; + GstPad *sink_pad; + + RET_VAL_IF(webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL"); + RET_VAL_IF(src_pad == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "src_pad is NULL"); + + /* decodebin_name/sink will be freed by function which is set to g_hash_table_new_full() */ + id = __get_id_from_pad_name(GST_PAD_NAME(src_pad)); + bin_name = g_strdup_printf("rendering_sink_%u", id); + decodebin_name = g_strdup_printf("%s_decodebin", bin_name); + + MALLOC_AND_INIT_SLOT(sink, id, bin_name); + + g_free(bin_name); + + decodebin = _create_element("decodebin", decodebin_name); + if (!decodebin) + goto error_before_insert; + + gst_bin_add(GST_BIN(sink->bin), decodebin); + + g_signal_connect(decodebin, "pad-added", G_CALLBACK(__decodebin_pad_added_cb), webrtc); + g_signal_connect(decodebin, "autoplug-select", G_CALLBACK(__decodebin_autoplug_select_cb), webrtc); + + ret = _add_no_target_ghostpad_to_slot(sink, FALSE, &sink_pad); + if (ret != WEBRTC_ERROR_NONE) + goto error_before_insert; + + ret = _set_ghost_pad_target(sink_pad, decodebin, FALSE); + if (ret != WEBRTC_ERROR_NONE) + goto error_before_insert; + + if (!gst_bin_add(GST_BIN(webrtc->gst.pipeline), sink->bin)) { + LOG_ERROR("failed to gst_bin_add(), [%s] -> [%s] pipeline", GST_ELEMENT_NAME(sink->bin), GST_ELEMENT_NAME(webrtc->gst.pipeline)); + goto error_before_insert; + } + + if (gst_pad_link(src_pad, sink_pad) != GST_PAD_LINK_OK) { + LOG_ERROR("failed to gst_pad_link(), %s:%s", GST_PAD_NAME(src_pad), GST_PAD_NAME(sink_pad)); + goto error_before_insert; + } + + LOG_DEBUG("link pads successfully, [%s:%s] - [%s:%s]", + GST_ELEMENT_NAME(webrtc->gst.webrtcbin), GST_PAD_NAME(src_pad), GST_ELEMENT_NAME(sink->bin), GST_PAD_NAME(sink_pad)); + + if (!g_hash_table_insert(webrtc->gst.sink_slots, decodebin_name, (gpointer)sink)) { + LOG_ERROR("should not be reached here, bin_name[%s] already exist, sink id[%u] will be removed", decodebin_name, sink->id); + g_hash_table_remove(webrtc->gst.sink_slots, decodebin_name); + return WEBRTC_ERROR_INVALID_OPERATION; + } + + gst_element_sync_state_with_parent(sink->bin); + + LOG_INFO("added a sink slot[%p, id:%u]", sink, sink->id); + + return WEBRTC_ERROR_NONE; + +error_before_insert: + gst_object_unref(sink_pad); + g_free(decodebin_name); + g_free(sink); + + return WEBRTC_ERROR_INVALID_OPERATION; +} diff --git a/src/webrtc_source.c b/src/webrtc_source.c new file mode 100644 index 00000000..b1146841 --- /dev/null +++ b/src/webrtc_source.c @@ -0,0 +1,541 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "webrtc.h" +#include "webrtc_private.h" + +#define GST_KLASS_NAME_ENCODER_AUDIO "Codec/Encoder/Audio" +#define GST_KLASS_NAME_ENCODER_VIDEO "Codec/Encoder/Video" +#define GST_KLASS_NAME_DECODER_AUDIO "Codec/Decoder/Audio" +#define GST_KLASS_NAME_DECODER_VIDEO "Codec/Decoder/Video" +#define GST_KLASS_NAME_PAYLOADER_RTP "Codec/Payloader/Network/RTP" +#define GST_KLASS_NAME_DEPAYLOADER_RTP "Codec/Depayloader/Network/RTP" +#define GST_KLASS_NAME_CONVERTER_AUDIO "Filter/Converter/Audio" +#define GST_KLASS_NAME_CONVERTER_VIDEO "Filter/Converter/Video" + +#define DEFAULT_ELEMENT_CAMERASRC "camerasrc" +#define DEFAULT_ELEMENT_AUDIOSRC "pulsesrc" +#define DEFAULT_ELEMENT_VIDEOTESTSRC "videotestsrc" +#define DEFAULT_ELEMENT_AUDIOTESTSRC "audiotestsrc" +#define DEFAULT_ELEMENT_CAPSFILTER "capsfilter" +#define DEFAULT_ELEMENT_QUEUE "queue" + +#define DEFAULT_VIDEO_ENCODED_MEDIA_TYPE "video/x-vp8" +#define DEFAULT_VIDEO_RAW_MEDIA_TYPE "video/x-raw" +#define DEFAULT_VIDEO_RAW_FORMAT "I420" +#define DEFAULT_VIDEO_WIDTH 352 +#define DEFAULT_VIDEO_HEIGHT 288 + +#define DEFAULT_AUDIO_ENCODED_MEDIA_TYPE "audio/x-opus" +#define DEFAULT_AUDIO_RAW_MEDIA_TYPE "audio/x-raw" +#define DEFAULT_AUDIO_RAW_FORMAT "S16LE" +#define DEFAULT_AUDIO_CHANNELS 1 +#define DEFAULT_AUDIO_SAMPLERATE 8000 + +typedef enum { + CODEC_TYPE_OPUS, + CODEC_TYPE_VORBIS, + CODEC_TYPE_VP8, + CODEC_TYPE_VP9, + CODEC_TYPE_THEORA, + CODEC_TYPE_H263, + CODEC_TYPE_H264, + CODEC_TYPE_H265, + CODEC_TYPE_NOT_SUPPORTED, +} codec_type_e; + +typedef struct { + const char *encoding_name; + const int clock_rate; +} payload_type_s; + +static payload_type_s payload_types[] = { + /* AUDIO */ + [CODEC_TYPE_OPUS] = { "OPUS", 48000 }, + [CODEC_TYPE_VORBIS] = { "VORBIS", -1 }, /* NOTE: -1 for various clock rate */ + /* VIDEO */ + [CODEC_TYPE_VP8] = { "VP8", 90000 }, + [CODEC_TYPE_VP9] = { "VP9", 90000 }, + [CODEC_TYPE_THEORA] = { "THEORA", 90000 }, + [CODEC_TYPE_H263] = { "H263", 90000 }, + [CODEC_TYPE_H264] = { "H264", 90000 }, + [CODEC_TYPE_H265] = { "H265", 90000 }, +}; + +static GstCaps *__make_default_raw_caps(webrtc_media_source_type_e type) +{ + GstCaps *caps = NULL; + + switch (type) { + case WEBRTC_MEDIA_SOURCE_TYPE_CAMERA: + case WEBRTC_MEDIA_SOURCE_TYPE_VIDEOTEST: + /* FIXME: get default value from ini */ + caps = gst_caps_new_simple(DEFAULT_VIDEO_RAW_MEDIA_TYPE, + "format", G_TYPE_STRING, DEFAULT_VIDEO_RAW_FORMAT, + "width", G_TYPE_INT, DEFAULT_VIDEO_WIDTH, + "height", G_TYPE_INT, DEFAULT_VIDEO_HEIGHT, + NULL); + break; + + case WEBRTC_MEDIA_SOURCE_TYPE_MIC: + case WEBRTC_MEDIA_SOURCE_TYPE_AUDIOTEST: + /* FIXME: get default value from ini */ + caps = gst_caps_new_simple(DEFAULT_AUDIO_RAW_MEDIA_TYPE, + "format", G_TYPE_STRING, DEFAULT_AUDIO_RAW_FORMAT, + "channels", G_TYPE_INT, DEFAULT_AUDIO_CHANNELS, + "rate", G_TYPE_INT, DEFAULT_AUDIO_SAMPLERATE, + NULL); + break; + + default: + LOG_ERROR_IF_REACHED("type(%d)", type); + break; + } + + return caps; +} + +/* Use g_free() to free the media_type parameter. */ +static GstCaps *__make_default_encoded_caps(webrtc_media_source_type_e type, gchar **media_type) +{ + GstCaps *caps; + const char *_media_type; + + switch (type) { + case WEBRTC_MEDIA_SOURCE_TYPE_CAMERA: + case WEBRTC_MEDIA_SOURCE_TYPE_VIDEOTEST: + /* FIXME: get default value from ini */ + _media_type = DEFAULT_VIDEO_ENCODED_MEDIA_TYPE; + caps = gst_caps_new_simple(_media_type, + "width", G_TYPE_INT, DEFAULT_VIDEO_WIDTH, + "height", G_TYPE_INT, DEFAULT_VIDEO_HEIGHT, + NULL); + break; + + case WEBRTC_MEDIA_SOURCE_TYPE_MIC: + case WEBRTC_MEDIA_SOURCE_TYPE_AUDIOTEST: + /* FIXME: get default value from ini */ + _media_type = DEFAULT_AUDIO_ENCODED_MEDIA_TYPE; + caps = gst_caps_new_simple(DEFAULT_AUDIO_ENCODED_MEDIA_TYPE, + "channels", G_TYPE_INT, DEFAULT_AUDIO_CHANNELS, + "rate", G_TYPE_INT, DEFAULT_AUDIO_SAMPLERATE, + NULL); + break; + + default: + LOG_ERROR_IF_REACHED("type(%d)", type); + return NULL; + } + + if (media_type) + *media_type = g_strdup(_media_type); + + return caps; +} + +static codec_type_e __get_codec_type(const gchar *media_type) +{ + if (!g_strcmp0(media_type, "audio/x-opus")) + return CODEC_TYPE_OPUS; + else if (!g_strcmp0(media_type, "audio/x-vorbis")) + return CODEC_TYPE_VORBIS; + else if (!g_strcmp0(media_type, "video/x-vp8")) + return CODEC_TYPE_VP8; + else if (!g_strcmp0(media_type, "video/x-vp9")) + return CODEC_TYPE_VP9; + else if (!g_strcmp0(media_type, "video/x-theora")) + return CODEC_TYPE_THEORA; + else if (!g_strcmp0(media_type, "video/x-h263")) + return CODEC_TYPE_H263; + else if (!g_strcmp0(media_type, "video/x-h264")) + return CODEC_TYPE_H264; + else if (!g_strcmp0(media_type, "video/x-h265")) + return CODEC_TYPE_H265; + else + return CODEC_TYPE_NOT_SUPPORTED; +} + +static GstCaps *__make_rtp_caps(const gchar *media_type, unsigned int id) +{ + gchar *caps_str; + GstCaps *caps; + codec_type_e codec_type = __get_codec_type(media_type); + + RET_VAL_IF(codec_type == CODEC_TYPE_NOT_SUPPORTED, NULL, "media_type[%s] is not supported", media_type); + + caps = gst_caps_new_simple("application/x-rtp", + "media", G_TYPE_STRING, g_strrstr(media_type, "video") ? "video" : "audio", + "clock-rate", G_TYPE_INT, payload_types[codec_type].clock_rate, /* FIXME: support various clock-rate */ + "encoding-name", G_TYPE_STRING, payload_types[codec_type].encoding_name, + "payload", G_TYPE_INT, id + 95, NULL); + + caps_str = gst_caps_to_string(caps); + LOG_DEBUG("RTP caps is created [%s]", caps_str); + + g_free(caps_str); + + return caps; +} + +static int __create_rest_of_elements(webrtc_gst_slot_s *source, webrtc_media_source_type_e type, GstElement **capsfilter, GstElement **encoder, GstElement **payloader, GstElement **queue, GstElement **capsfilter2) +{ + GstCaps *sink_caps; + element_info_s elem_info; + const gchar *encoder_klass_name; + gchar *media_type; + + RET_VAL_IF(source == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "source is NULL"); + RET_VAL_IF(capsfilter == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "capsfilter is NULL"); + RET_VAL_IF(encoder == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "encoder is NULL"); + RET_VAL_IF(payloader == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "payloader is NULL"); + RET_VAL_IF(queue == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "queue is NULL"); + RET_VAL_IF(capsfilter2 == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "capsfilter2 is NULL"); + + if (!(*capsfilter = _create_element(DEFAULT_ELEMENT_CAPSFILTER, NULL))) { + LOG_ERROR("failed to create capsfilter"); + return WEBRTC_ERROR_INVALID_OPERATION; + } + if ((sink_caps = __make_default_raw_caps(type))) { + g_object_set(G_OBJECT(*capsfilter), "caps", sink_caps, NULL); + gst_caps_unref(sink_caps); + } + + if (type == WEBRTC_MEDIA_SOURCE_TYPE_CAMERA || type == WEBRTC_MEDIA_SOURCE_TYPE_VIDEOTEST) + encoder_klass_name = GST_KLASS_NAME_ENCODER_VIDEO; + else + encoder_klass_name = GST_KLASS_NAME_ENCODER_AUDIO; + + CREATE_ELEMENT_FROM_REGISTRY(elem_info, encoder_klass_name, + __make_default_raw_caps(type), + __make_default_encoded_caps(type, NULL), + *encoder); + + CREATE_ELEMENT_FROM_REGISTRY(elem_info, GST_KLASS_NAME_PAYLOADER_RTP, + __make_default_encoded_caps(type, &media_type), + NULL, + *payloader); + + if (!(*queue = _create_element(DEFAULT_ELEMENT_QUEUE, NULL))) { + LOG_ERROR("failed to create queue"); + g_free(media_type); + return WEBRTC_ERROR_INVALID_OPERATION; + } + + if (!(*capsfilter2 = _create_element(DEFAULT_ELEMENT_CAPSFILTER, NULL))) { + LOG_ERROR("failed to create capsfilter"); + g_free(media_type); + return WEBRTC_ERROR_INVALID_OPERATION; + } + if ((sink_caps = __make_rtp_caps(media_type, source->id))) { + g_object_set(G_OBJECT(*capsfilter2), "caps", sink_caps, NULL); + gst_caps_unref(sink_caps); + } + + g_free(media_type); + + return WEBRTC_ERROR_NONE; +} + +static int __build_camerasrc(webrtc_gst_slot_s *source, GstPad *ghost_src_pad) +{ + int ret = WEBRTC_ERROR_NONE; + GstElement *camerasrc; + GstElement *capsfilter; + GstElement *videoenc; + GstElement *videopay; + GstElement *queue; + GstElement *capsfilter2; + + RET_VAL_IF(source == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "source is NULL"); + RET_VAL_IF(ghost_src_pad == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "ghost_src_pad is NULL"); + RET_VAL_IF(source->bin == NULL, WEBRTC_ERROR_INVALID_OPERATION, "bin is NULL"); + + source->media_types |= MEDIA_TYPE_VIDEO; + + /* FIXME: get factory name from ini */ + if (!(camerasrc = _create_element(DEFAULT_ELEMENT_CAMERASRC, NULL))) { + LOG_ERROR("failed to create camerasrc"); + return WEBRTC_ERROR_INVALID_OPERATION; + } + /* FIXME: set camera default setting from ini */ + + if ((ret = __create_rest_of_elements(source, WEBRTC_MEDIA_SOURCE_TYPE_CAMERA, &capsfilter, &videoenc, &videopay, &queue, &capsfilter2)) != WEBRTC_ERROR_NONE) + return ret; + + gst_bin_add_many(GST_BIN(source->bin), camerasrc, capsfilter, videoenc, videopay, queue, capsfilter2, NULL); + if (!gst_element_link_many(camerasrc, capsfilter, videoenc, videopay, queue, capsfilter2, NULL)) { + LOG_ERROR("failed to gst_element_link_many()"); + return WEBRTC_ERROR_INVALID_OPERATION; + } + + return _set_ghost_pad_target(ghost_src_pad, capsfilter2, TRUE); +} + +static int __build_audiosrc(webrtc_gst_slot_s *source, GstPad *ghost_src_pad) +{ + int ret = WEBRTC_ERROR_NONE; + GstElement *audiosrc; + GstElement *capsfilter; + GstElement *audioenc; + GstElement *audiopay; + GstElement *queue; + GstElement *capsfilter2; + + RET_VAL_IF(source == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "source is NULL"); + RET_VAL_IF(ghost_src_pad == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "ghost_src_pad is NULL"); + RET_VAL_IF(source->bin == NULL, WEBRTC_ERROR_INVALID_OPERATION, "bin is NULL"); + + source->media_types |= MEDIA_TYPE_AUDIO; + + if (!(audiosrc = _create_element(DEFAULT_ELEMENT_AUDIOSRC, NULL))) { + LOG_ERROR("failed to create audiosrc"); + return WEBRTC_ERROR_INVALID_OPERATION; + } + + if ((ret = __create_rest_of_elements(source, WEBRTC_MEDIA_SOURCE_TYPE_MIC, &capsfilter, &audioenc, &audiopay, &queue, &capsfilter2)) != WEBRTC_ERROR_NONE) + return ret; + + gst_bin_add_many(GST_BIN(source->bin), audiosrc, capsfilter, audioenc, audiopay, queue, capsfilter2, NULL); + if (!gst_element_link_many(audiosrc, capsfilter, audioenc, audiopay, queue, capsfilter2, NULL)) { + LOG_ERROR("failed to gst_element_link_many()"); + return WEBRTC_ERROR_INVALID_OPERATION; + } + + return _set_ghost_pad_target(ghost_src_pad, capsfilter2, TRUE); +} + +static int __build_videotestsrc(webrtc_gst_slot_s *source, GstPad *ghost_src_pad) +{ + int ret = WEBRTC_ERROR_NONE; + GstElement *videotestsrc; + GstElement *capsfilter; + GstElement *videoenc; + GstElement *videopay; + GstElement *queue; + GstElement *capsfilter2; + + RET_VAL_IF(source == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "source is NULL"); + RET_VAL_IF(ghost_src_pad == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "ghost_src_pad is NULL"); + RET_VAL_IF(source->bin == NULL, WEBRTC_ERROR_INVALID_OPERATION, "bin is NULL"); + + source->media_types |= MEDIA_TYPE_VIDEO; + + if (!(videotestsrc = _create_element(DEFAULT_ELEMENT_VIDEOTESTSRC, NULL))) { + LOG_ERROR("failed to create videotestsrc"); + return WEBRTC_ERROR_INVALID_OPERATION; + } + g_object_set(G_OBJECT(videotestsrc), "is-live", TRUE, NULL); + + if ((ret = __create_rest_of_elements(source, WEBRTC_MEDIA_SOURCE_TYPE_VIDEOTEST, &capsfilter, &videoenc, &videopay, &queue, &capsfilter2)) != WEBRTC_ERROR_NONE) + return ret; + + gst_bin_add_many(GST_BIN(source->bin), videotestsrc, capsfilter, videoenc, videopay, queue, capsfilter2, NULL); + if (!gst_element_link_many(videotestsrc, capsfilter, videoenc, videopay, queue, capsfilter2, NULL)) { + LOG_ERROR("failed to gst_element_link_many()"); + return WEBRTC_ERROR_INVALID_OPERATION; + } + + return _set_ghost_pad_target(ghost_src_pad, capsfilter2, TRUE); +} + +static int __build_audiotestsrc(webrtc_gst_slot_s *source, GstPad *ghost_src_pad) +{ + int ret = WEBRTC_ERROR_NONE; + GstElement *audiotestsrc; + GstElement *capsfilter; + GstElement *audioenc; + GstElement *audiopay; + GstElement *queue; + GstElement *capsfilter2; + + RET_VAL_IF(source == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "source is NULL"); + RET_VAL_IF(ghost_src_pad == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "ghost_src_pad is NULL"); + RET_VAL_IF(source->bin == NULL, WEBRTC_ERROR_INVALID_OPERATION, "bin is NULL"); + + source->media_types |= MEDIA_TYPE_AUDIO; + + if (!(audiotestsrc = _create_element(DEFAULT_ELEMENT_AUDIOTESTSRC, NULL))) { + LOG_ERROR("failed to create audiotestsrc"); + return WEBRTC_ERROR_INVALID_OPERATION; + } + g_object_set(G_OBJECT(audiotestsrc), "is-live", TRUE, NULL); + + if ((ret = __create_rest_of_elements(source, WEBRTC_MEDIA_SOURCE_TYPE_AUDIOTEST, &capsfilter, &audioenc, &audiopay, &queue, &capsfilter2)) != WEBRTC_ERROR_NONE) + return ret; + + gst_bin_add_many(GST_BIN(source->bin), audiotestsrc, capsfilter, audioenc, audiopay, queue, capsfilter2, NULL); + if (!gst_element_link_many(audiotestsrc, capsfilter, audioenc, audiopay, queue, capsfilter2, NULL)) { + LOG_ERROR("failed to gst_element_link_many()"); + return WEBRTC_ERROR_INVALID_OPERATION; + } + + return _set_ghost_pad_target(ghost_src_pad, capsfilter2, TRUE); +} + +static int __build_source_bin(webrtc_gst_slot_s *source, webrtc_media_source_type_e type) +{ + int ret = WEBRTC_ERROR_NONE; + GstPad *src_pad; + + RET_VAL_IF(source == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "source is NULL"); + RET_VAL_IF(source->bin == NULL, WEBRTC_ERROR_INVALID_OPERATION, "bin is NULL"); + + ret = _add_no_target_ghostpad_to_slot(source, TRUE, &src_pad); + RET_VAL_IF(ret != WEBRTC_ERROR_NONE, ret, "failed to _add_no_target_ghostpad_to_slot()"); + + switch (type) { + case WEBRTC_MEDIA_SOURCE_TYPE_CAMERA: + return __build_camerasrc(source, src_pad); + + case WEBRTC_MEDIA_SOURCE_TYPE_MIC: + return __build_audiosrc(source, src_pad); + + case WEBRTC_MEDIA_SOURCE_TYPE_VIDEOTEST: + return __build_videotestsrc(source, src_pad); + + case WEBRTC_MEDIA_SOURCE_TYPE_AUDIOTEST: + return __build_audiotestsrc(source, src_pad); + + default: + LOG_ERROR_IF_REACHED("type(%d)", type); + return WEBRTC_ERROR_INVALID_PARAMETER; + } + + return WEBRTC_ERROR_NONE; +} + +static unsigned int __get_unoccupied_id(GHashTable *slots) +{ + int i; + gchar *key; + + RET_VAL_IF(slots == NULL, 0, "slot is NULL"); + + /* Payload identifiers 96–127 are used for payloads defined dynamically during a session, + * hence the id range is limited here to 1-32. */ + for (i = 1; i < 33; i++) { + key = g_strdup_printf("media_source_%u", i); + if (g_hash_table_contains(slots, key)) { + g_free(key); + continue; + } + g_free(key); + return i; + } + + LOG_ERROR("all slots are occupied(1-32)"); + + return 0; +} + +int _add_media_source(webrtc_s *webrtc, webrtc_media_source_type_e type, unsigned int *source_id) +{ + int ret = WEBRTC_ERROR_NONE; + unsigned int id; + webrtc_gst_slot_s *source = NULL; + gchar *bin_name = NULL; + GstPad *webrtc_sinkpad; + gchar *webrtc_sinkpad_name; + gchar *bin_srcpad_name = NULL; + + RET_VAL_IF(webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL"); + RET_VAL_IF(webrtc->gst.source_slots == NULL, WEBRTC_ERROR_INVALID_OPERATION, "source_slots is NULL"); + RET_VAL_IF(source_id == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "source_id is NULL"); + + /* bin_name/source will be freed by function which is set to g_hash_table_new_full() */ + id = __get_unoccupied_id(webrtc->gst.source_slots); + RET_VAL_IF(id == 0, WEBRTC_ERROR_INVALID_OPERATION, "source_slots are full"); + + bin_name = g_strdup_printf("media_source_%u", id); + + MALLOC_AND_INIT_SLOT(source, id, bin_name); + + ret = __build_source_bin(source, type); + if (ret != WEBRTC_ERROR_NONE) { + LOG_ERROR("failed to __build_source_bin()"); + goto error; + } + + if (!gst_bin_add(GST_BIN(webrtc->gst.pipeline), source->bin)) { + LOG_ERROR("failed to gst_bin_add(), [%s] -> [%s] pipeline", GST_ELEMENT_NAME(source->bin), GST_ELEMENT_NAME(webrtc->gst.pipeline)); + goto error; + } + + /* The gst_element_get_request_pad() of webrtcbin will trigger the transciever callback. To update the mline value of + * new transceiver object to the source structure in the callback, hash table inserting should be preceded. */ + if (!g_hash_table_insert(webrtc->gst.source_slots, bin_name, (gpointer)source)) { + LOG_ERROR("should not be reached here, bin_name[%s] already exist, source id[%u] will be removed", bin_name, source->id); + g_hash_table_remove(webrtc->gst.source_slots, bin_name); + return WEBRTC_ERROR_INVALID_OPERATION; + } + + if (!(webrtc_sinkpad = gst_element_get_request_pad(webrtc->gst.webrtcbin, "sink_%u"))) { + LOG_ERROR("failed to gst_element_get_request_pad()"); + goto error_after_insert; + } + if (!(webrtc_sinkpad_name = gst_pad_get_name(webrtc_sinkpad))) { + LOG_ERROR("failed to gst_pad_get_name()"); + goto error_after_insert; + } + bin_srcpad_name = g_strdup_printf("src_%u", id); + if (!gst_element_link_pads(source->bin, bin_srcpad_name, webrtc->gst.webrtcbin, webrtc_sinkpad_name)) { + LOG_ERROR("failed to link pads, [%s:%s] - [%s:%s]", + GST_ELEMENT_NAME(source->bin), bin_srcpad_name, GST_ELEMENT_NAME(webrtc->gst.webrtcbin), webrtc_sinkpad_name); + goto error_after_insert; + } + LOG_DEBUG("link pads successfully, [%s:%s] - [%s:%s]", + GST_ELEMENT_NAME(source->bin), bin_srcpad_name, GST_ELEMENT_NAME(webrtc->gst.webrtcbin), webrtc_sinkpad_name); + + *source_id = id; + + LOG_INFO("added a source slot[%p, id:%u]", source, source->id); + + g_free(bin_srcpad_name); + + return WEBRTC_ERROR_NONE; + +error_after_insert: + g_hash_table_remove(webrtc->gst.source_slots, bin_name); + g_free(bin_srcpad_name); + + return WEBRTC_ERROR_INVALID_OPERATION; + +error: + g_free(bin_name); + g_free(source); + + return WEBRTC_ERROR_INVALID_OPERATION; +} + +int _remove_media_source(webrtc_s *webrtc, unsigned int source_id) +{ + int ret = WEBRTC_ERROR_NONE; + gchar *bin_name = NULL; + + RET_VAL_IF(webrtc == NULL, WEBRTC_ERROR_INVALID_PARAMETER, "webrtc is NULL"); + RET_VAL_IF(webrtc->gst.source_slots == NULL, WEBRTC_ERROR_INVALID_OPERATION, "source_slots is NULL"); + + bin_name = g_strdup_printf("media_source_%u", source_id); + + if (!g_hash_table_remove(webrtc->gst.source_slots, (gpointer)bin_name)) { + LOG_ERROR("failed to find media source by id[%u]", source_id); + ret = WEBRTC_ERROR_INVALID_PARAMETER; + } + + g_free(bin_name); + + return ret; +}