diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h
index 670dbbe15df9472c712b0cfe159434ffe7292e19..98d77734fa18c345d66ea4b4a5e81e21dc6d4c4a 100644
--- a/include/asterisk/rtp_engine.h
+++ b/include/asterisk/rtp_engine.h
@@ -533,6 +533,18 @@ enum ast_rtp_dtls_verify {
 	AST_RTP_DTLS_VERIFY_CERTIFICATE = (1 << 1), /*!< Verify the certificate */
 };
 
+/*!
+ * \brief Known RTP extensions
+ */
+enum ast_rtp_extension {
+	/*! Per the RFC 0 should not be used, so we treat it as an unsupported extension placeholder */
+	AST_RTP_EXTENSION_UNSUPPORTED = 0,
+	/*! abs-send-time from https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03 */
+	AST_RTP_EXTENSION_ABS_SEND_TIME,
+	/*! The maximum number of known RTP extensions */
+	AST_RTP_EXTENSION_MAX,
+};
+
 /*! \brief DTLS configuration structure */
 struct ast_rtp_dtls_cfg {
 	unsigned int enabled:1;                /*!< Whether DTLS support is enabled or not */
@@ -654,6 +666,8 @@ struct ast_rtp_engine {
 	struct ast_rtp_engine_ice *ice;
 	/*! Callback to pointer for optional DTLS SRTP support */
 	struct ast_rtp_engine_dtls *dtls;
+	/*! Callback to enable an RTP extension (returns non-zero if supported) */
+	int (*extension_enable)(struct ast_rtp_instance *instance, enum ast_rtp_extension extension);
 	/*! Linked list information */
 	AST_RWLIST_ENTRY(ast_rtp_engine) entry;
 };
@@ -720,6 +734,22 @@ struct ast_rtp_glue {
 	AST_RWLIST_ENTRY(ast_rtp_glue) entry;
 };
 
+/*!
+ * \brief Directions for RTP extensions
+ */
+enum ast_rtp_extension_direction {
+	/*! The extension is not negotiated and is not flowing */
+	AST_RTP_EXTENSION_DIRECTION_NONE,
+	/*! Send and receive */
+	AST_RTP_EXTENSION_DIRECTION_SENDRECV,
+	/*! Send only */
+	AST_RTP_EXTENSION_DIRECTION_SENDONLY,
+	/*! Receive only */
+	AST_RTP_EXTENSION_DIRECTION_RECVONLY,
+	/*! Negotiated but not sending or receiving */
+	AST_RTP_EXTENSION_DIRECTION_INACTIVE,
+};
+
 /*!
  * \brief Allocation routine for \ref ast_rtp_payload_type
  *
@@ -1246,6 +1276,109 @@ int ast_rtp_instance_get_prop(struct ast_rtp_instance *instance, enum ast_rtp_pr
  */
 struct ast_rtp_codecs *ast_rtp_instance_get_codecs(struct ast_rtp_instance *instance);
 
+/*!
+ * \brief Enable support for an RTP extension on an instance
+ *
+ * \param instance The RTP instance to enable the extension on
+ * \param id The unique local identifier to use for this extension (-1 to have one auto selected)
+ * \param extension The RTP extension
+ * \param direction The initial direction that the RTP extension should be used in
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \since 15.5.0
+ */
+int ast_rtp_instance_extmap_enable(struct ast_rtp_instance *instance, int id, enum ast_rtp_extension extension,
+	enum ast_rtp_extension_direction direction);
+
+/*!
+ * \brief Negotiate received RTP extension information
+ *
+ * \param instance The RTP instance to set the extension on
+ * \param id The local identifier for the extension
+ * \param direction The direction that the extension should be used in
+ * \param uri The unique URI for the extension
+ * \param attributes Attributes specific to this extension (if NULL or empty then no attributes)
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \since 15.5.0
+ */
+int ast_rtp_instance_extmap_negotiate(struct ast_rtp_instance *instance, int id, enum ast_rtp_extension_direction direction,
+	const char *uri, const char *attributes);
+
+/*!
+ * \brief Clear negotiated RTP extension information
+ *
+ * \param instance The RTP instance to clear negotiated extension information on
+ */
+void ast_rtp_instance_extmap_clear(struct ast_rtp_instance *instance);
+
+/*!
+ * \brief Retrieve the id for an RTP extension
+ *
+ * \param instance The RTP instance to retrieve the id from
+ * \param extension The RTP extension
+ *
+ * \retval -1 not negotiated
+ * \retval id if negotiated
+ *
+ * \since 15.5.0
+ */
+int ast_rtp_instance_extmap_get_id(struct ast_rtp_instance *instance, enum ast_rtp_extension extension);
+
+/*!
+ * \brief Get the number of known unique identifiers
+ *
+ * \param instance The RTP instance to retrieve the count from
+ *
+ * \return the number of known unique identifiers
+ *
+ * \since 15.5.0
+ */
+size_t ast_rtp_instance_extmap_count(struct ast_rtp_instance *instance);
+
+/*!
+ * \brief Retrieve the extension for an RTP extension id
+ *
+ * \param instance The RTP instance to retrieve the extension from
+ * \param id The negotiated RTP extension id
+ *
+ * \retval extension the extension that maps to the id
+ *
+ * \since 15.5.0
+ *
+ * \note This will return AST_RTP_EXTENSION_UNSUPPORTED if an extension was proposed for this unique identifier
+ * but it is not supported or if the unique identifier is unused.
+ */
+enum ast_rtp_extension ast_rtp_instance_extmap_get_extension(struct ast_rtp_instance *instance, int id);
+
+/*!
+ * \brief Retrieve the negotiated direction for an RTP extension id
+ *
+ * \param instance The RTP instance to retrieve the direction from
+ * \param id The negotiated RTP extension id
+ *
+ * \retval direction the direction that has been negotiated
+ *
+ * \since 15.5.0
+ */
+enum ast_rtp_extension_direction ast_rtp_instance_extmap_get_direction(struct ast_rtp_instance *instance, int id);
+
+/*!
+ * \brief Retrieve the URI for an RTP extension id
+ *
+ * \param instance The RTP instance to retrieve the direction from
+ * \param id The negotiated RTP extension id
+ *
+ * \retval uri The URI for the RTP extension
+ *
+ * \since 15.5.0
+ */
+const char *ast_rtp_instance_extmap_get_uri(struct ast_rtp_instance *instance, int id);
+
 /*!
  * \brief Initialize an RTP codecs structure
  *
diff --git a/main/rtp_engine.c b/main/rtp_engine.c
index 3c8f6e82fabf075967e3deec7a1f64e54719856e..c11027af2f11e845c3c1cbd366e37244b7aae5ca 100644
--- a/main/rtp_engine.c
+++ b/main/rtp_engine.c
@@ -177,6 +177,14 @@
 struct ast_srtp_res *res_srtp = NULL;
 struct ast_srtp_policy_res *res_srtp_policy = NULL;
 
+/*! Structure that contains extmap negotiation information */
+struct rtp_extmap {
+	/*! The RTP extension */
+	enum ast_rtp_extension extension;
+	/*! The current negotiated direction */
+	enum ast_rtp_extension_direction direction;
+};
+
 /*! Structure that represents an RTP session (instance) */
 struct ast_rtp_instance {
 	/*! Engine that is handling this RTP instance */
@@ -213,6 +221,20 @@ struct ast_rtp_instance {
 	time_t last_tx;
 	/*! Time of last packet received */
 	time_t last_rx;
+	/*! Enabled RTP extensions */
+	AST_VECTOR(, enum ast_rtp_extension_direction) extmap_enabled;
+	/*! Negotiated RTP extensions (using index based on extension) */
+	AST_VECTOR(, int) extmap_negotiated;
+	/*! Negotiated RTP extensions (using index based on unique id) */
+	AST_VECTOR(, struct rtp_extmap) extmap_unique_ids;
+};
+
+/*!
+ * \brief URIs for known RTP extensions
+ */
+static const char * const rtp_extension_uris[AST_RTP_EXTENSION_MAX] = {
+	[AST_RTP_EXTENSION_UNSUPPORTED]		= "",
+	[AST_RTP_EXTENSION_ABS_SEND_TIME]	= "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time",
 };
 
 /*! List of RTP engines that are currently registered */
@@ -408,6 +430,10 @@ static void instance_destructor(void *obj)
 
 	ast_rtp_codecs_payloads_destroy(&instance->codecs);
 
+	AST_VECTOR_FREE(&instance->extmap_enabled);
+	AST_VECTOR_FREE(&instance->extmap_negotiated);
+	AST_VECTOR_FREE(&instance->extmap_unique_ids);
+
 	/* Drop our engine reference */
 	ast_module_unref(instance->engine->mod);
 
@@ -474,6 +500,14 @@ struct ast_rtp_instance *ast_rtp_instance_new(const char *engine_name,
 		return NULL;
 	}
 
+	/* Initialize RTP extension support */
+	if (AST_VECTOR_INIT(&instance->extmap_enabled, 0) ||
+		AST_VECTOR_INIT(&instance->extmap_negotiated, 0) ||
+		AST_VECTOR_INIT(&instance->extmap_unique_ids, 0)) {
+		ao2_ref(instance, -1);
+		return NULL;
+	}
+
 	ast_debug(1, "Using engine '%s' for RTP instance '%p'\n", engine->name, instance);
 
 	/*
@@ -680,6 +714,232 @@ struct ast_rtp_codecs *ast_rtp_instance_get_codecs(struct ast_rtp_instance *inst
 	return &instance->codecs;
 }
 
+int ast_rtp_instance_extmap_enable(struct ast_rtp_instance *instance, int id, enum ast_rtp_extension extension,
+	enum ast_rtp_extension_direction direction)
+{
+	struct rtp_extmap extmap = {
+		.extension = extension,
+		.direction = direction,
+	};
+
+	ao2_lock(instance);
+
+	if (!instance->engine->extension_enable || !instance->engine->extension_enable(instance, extension)) {
+		ao2_unlock(instance);
+		return 0;
+	}
+
+	/* We store enabled extensions separately so we can easily do negotiation */
+	if (AST_VECTOR_REPLACE(&instance->extmap_enabled, extension, direction)) {
+		ao2_unlock(instance);
+		return -1;
+	}
+
+	if (id <= 0) {
+		/* We find a free unique identifier for this extension by just appending it to the
+		 * vector of unique ids. The size of the vector will become its unique identifier.
+		 * As well when we are asking for information on the extensions it will be returned,
+		 * allowing it to be added to the SDP offer.
+		 */
+		if (AST_VECTOR_APPEND(&instance->extmap_unique_ids, extmap)) {
+			AST_VECTOR_REPLACE(&instance->extmap_enabled, extension, AST_RTP_EXTENSION_DIRECTION_NONE);
+			ao2_unlock(instance);
+			return -1;
+		}
+		id = AST_VECTOR_SIZE(&instance->extmap_unique_ids);
+	} else {
+		/* Otherwise we put it precisely where they want it */
+		if (AST_VECTOR_REPLACE(&instance->extmap_unique_ids, id - 1, extmap)) {
+			AST_VECTOR_REPLACE(&instance->extmap_enabled, extension, AST_RTP_EXTENSION_DIRECTION_NONE);
+			ao2_unlock(instance);
+			return -1;
+		}
+	}
+
+	/* Now that we have an id add the extension to here */
+	if (AST_VECTOR_REPLACE(&instance->extmap_negotiated, extension, id)) {
+		extmap.extension = AST_RTP_EXTENSION_UNSUPPORTED;
+		extmap.direction = AST_RTP_EXTENSION_DIRECTION_NONE;
+		AST_VECTOR_REPLACE(&instance->extmap_enabled, extension, AST_RTP_EXTENSION_DIRECTION_NONE);
+		AST_VECTOR_REPLACE(&instance->extmap_unique_ids, id - 1, extmap);
+		ao2_unlock(instance);
+		return -1;
+	}
+
+	ao2_unlock(instance);
+
+	return 0;
+}
+
+/*! \brief Helper function which negotiates two RTP extension directions to get our current direction */
+static enum ast_rtp_extension_direction rtp_extmap_negotiate_direction(enum ast_rtp_extension_direction ours,
+	enum ast_rtp_extension_direction theirs)
+{
+	if (theirs == AST_RTP_EXTENSION_DIRECTION_NONE || ours == AST_RTP_EXTENSION_DIRECTION_NONE) {
+		/* This should not occur but if it does tolerate either side not having this extension
+		 * in use.
+		 */
+		return AST_RTP_EXTENSION_DIRECTION_NONE;
+	} else if (theirs == AST_RTP_EXTENSION_DIRECTION_INACTIVE) {
+		/* Inactive is always inactive on our side */
+		return AST_RTP_EXTENSION_DIRECTION_INACTIVE;
+	} else if (theirs == AST_RTP_EXTENSION_DIRECTION_SENDRECV) {
+		return ours;
+	} else if (theirs == AST_RTP_EXTENSION_DIRECTION_SENDONLY) {
+		/* If they are send only then we become recvonly if we are configured as sendrecv or recvonly */
+		if (ours == AST_RTP_EXTENSION_DIRECTION_SENDRECV || ours == AST_RTP_EXTENSION_DIRECTION_RECVONLY) {
+			return AST_RTP_EXTENSION_DIRECTION_RECVONLY;
+		}
+	} else if (theirs == AST_RTP_EXTENSION_DIRECTION_RECVONLY) {
+		/* If they are recv only then we become sendonly if we are configured as sendrecv or sendonly */
+		if (ours == AST_RTP_EXTENSION_DIRECTION_SENDRECV || ours == AST_RTP_EXTENSION_DIRECTION_SENDONLY) {
+			return AST_RTP_EXTENSION_DIRECTION_SENDONLY;
+		}
+	}
+
+	return AST_RTP_EXTENSION_DIRECTION_NONE;
+}
+
+int ast_rtp_instance_extmap_negotiate(struct ast_rtp_instance *instance, int id, enum ast_rtp_extension_direction direction,
+	const char *uri, const char *attributes)
+{
+	/* 'attributes' is currently unused but exists in the API to ensure it does not need to be altered
+	 * in the future in case we need to use it.
+	 */
+	int idx;
+	enum ast_rtp_extension extension = AST_RTP_EXTENSION_UNSUPPORTED;
+
+	/* Per the RFC the identifier has to be 1 or above */
+	if (id < 1) {
+		return -1;
+	}
+
+	/* Convert the provided URI to the internal representation */
+	for (idx = 0; idx < ARRAY_LEN(rtp_extension_uris); ++idx) {
+		if (!strcasecmp(rtp_extension_uris[idx], uri)) {
+			extension = idx;
+			break;
+		}
+	}
+
+	ao2_lock(instance);
+	/* We only accept the extension if it is enabled */
+	if (extension < AST_VECTOR_SIZE(&instance->extmap_enabled) &&
+		AST_VECTOR_GET(&instance->extmap_enabled, extension) != AST_RTP_EXTENSION_DIRECTION_NONE) {
+		struct rtp_extmap extmap = {
+			.extension = extension,
+			.direction = rtp_extmap_negotiate_direction(AST_VECTOR_GET(&instance->extmap_enabled, extension), direction),
+		};
+
+		/* If the direction negotiation failed then don't accept or use this extension */
+		if (extmap.direction != AST_RTP_EXTENSION_DIRECTION_NONE) {
+			if (extension != AST_RTP_EXTENSION_UNSUPPORTED) {
+				AST_VECTOR_REPLACE(&instance->extmap_negotiated, extension, id);
+			}
+			AST_VECTOR_REPLACE(&instance->extmap_unique_ids, id - 1, extmap);
+		}
+	}
+	ao2_unlock(instance);
+
+	return 0;
+}
+
+void ast_rtp_instance_extmap_clear(struct ast_rtp_instance *instance)
+{
+	static const struct rtp_extmap extmap_none = {
+		.extension = AST_RTP_EXTENSION_UNSUPPORTED,
+		.direction = AST_RTP_EXTENSION_DIRECTION_NONE,
+	};
+	int idx;
+
+	ao2_lock(instance);
+
+	/* Clear both the known unique ids and the negotiated extensions as we are about to have
+	 * new results set on us.
+	 */
+	for (idx = 0; idx < AST_VECTOR_SIZE(&instance->extmap_unique_ids); ++idx) {
+		AST_VECTOR_REPLACE(&instance->extmap_unique_ids, idx, extmap_none);
+	}
+
+	for (idx = 0; idx < AST_VECTOR_SIZE(&instance->extmap_negotiated); ++idx) {
+		AST_VECTOR_REPLACE(&instance->extmap_negotiated, idx, -1);
+	}
+
+	ao2_unlock(instance);
+}
+
+int ast_rtp_instance_extmap_get_id(struct ast_rtp_instance *instance, enum ast_rtp_extension extension)
+{
+	int id = -1;
+
+	ao2_lock(instance);
+	if (extension < AST_VECTOR_SIZE(&instance->extmap_negotiated)) {
+		id = AST_VECTOR_GET(&instance->extmap_negotiated, extension);
+	}
+	ao2_unlock(instance);
+
+	return id;
+}
+
+size_t ast_rtp_instance_extmap_count(struct ast_rtp_instance *instance)
+{
+	size_t count;
+
+	ao2_lock(instance);
+	count = AST_VECTOR_SIZE(&instance->extmap_unique_ids);
+	ao2_unlock(instance);
+
+	return count;
+}
+
+enum ast_rtp_extension ast_rtp_instance_extmap_get_extension(struct ast_rtp_instance *instance, int id)
+{
+	enum ast_rtp_extension extension = AST_RTP_EXTENSION_UNSUPPORTED;
+
+	ao2_lock(instance);
+
+	/* The local unique identifier starts at '1' so the highest unique identifier
+	 * can be the actual size of the vector. We compensate (as it is 0 index based)
+	 * by dropping it down to 1 to get the correct information.
+	 */
+	if (0 < id && id <= AST_VECTOR_SIZE(&instance->extmap_unique_ids)) {
+		struct rtp_extmap *extmap = AST_VECTOR_GET_ADDR(&instance->extmap_unique_ids, id - 1);
+
+		extension = extmap->extension;
+	}
+	ao2_unlock(instance);
+
+	return extension;
+}
+
+enum ast_rtp_extension_direction ast_rtp_instance_extmap_get_direction(struct ast_rtp_instance *instance, int id)
+{
+	enum ast_rtp_extension_direction direction = AST_RTP_EXTENSION_DIRECTION_NONE;
+
+	ao2_lock(instance);
+
+	if (0 < id && id <= AST_VECTOR_SIZE(&instance->extmap_unique_ids)) {
+		struct rtp_extmap *extmap = AST_VECTOR_GET_ADDR(&instance->extmap_unique_ids, id - 1);
+
+		direction = extmap->direction;
+	}
+	ao2_unlock(instance);
+
+	return direction;
+}
+
+const char *ast_rtp_instance_extmap_get_uri(struct ast_rtp_instance *instance, int id)
+{
+	enum ast_rtp_extension extension = ast_rtp_instance_extmap_get_extension(instance, id);
+
+	if (extension == AST_RTP_EXTENSION_UNSUPPORTED ||
+		(unsigned int)extension >= ARRAY_LEN(rtp_extension_uris)) {
+		return NULL;
+	}
+
+	return rtp_extension_uris[extension];
+}
+
 int ast_rtp_codecs_payloads_initialize(struct ast_rtp_codecs *codecs)
 {
 	int res;
diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c
index 14ed3b186375a1c64ad2763bfdb876d870fd38fe..727aeb0deccdaf11952f750a6bb6bb131c787298 100644
--- a/res/res_pjsip_sdp_rtp.c
+++ b/res/res_pjsip_sdp_rtp.c
@@ -151,8 +151,57 @@ static void enable_rtcp(struct ast_sip_session *session, struct ast_sip_session_
 	ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RTCP, rtcp_type);
 }
 
+/*!
+ * \brief Enable an RTP extension on an RTP session.
+ */
+static void enable_rtp_extension(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+	enum ast_rtp_extension extension, enum ast_rtp_extension_direction direction,
+	const pjmedia_sdp_session *sdp)
+{
+	int id = -1;
+
+	/* For a bundle group the local unique identifier space is shared across all streams within
+	 * it.
+	 */
+	if (session_media->bundle_group != -1) {
+		int index;
+
+		for (index = 0; index < sdp->media_count; ++index) {
+			struct ast_sip_session_media *other_session_media;
+			int other_id;
+
+			if (index >= AST_VECTOR_SIZE(&session->pending_media_state->sessions)) {
+				break;
+			}
+
+			other_session_media = AST_VECTOR_GET(&session->pending_media_state->sessions, index);
+			if (!other_session_media->rtp || other_session_media->bundle_group != session_media->bundle_group) {
+				continue;
+			}
+
+			other_id = ast_rtp_instance_extmap_get_id(other_session_media->rtp, extension);
+			if (other_id == -1) {
+				/* Worst case we have to fall back to the highest available free local unique identifier
+				 * for the bundle group.
+				 */
+				other_id = ast_rtp_instance_extmap_count(other_session_media->rtp) + 1;
+				if (id < other_id) {
+					id = other_id;
+				}
+				continue;
+			}
+
+			id = other_id;
+			break;
+		}
+	}
+
+	ast_rtp_instance_extmap_enable(session_media->rtp, id, extension, direction);
+}
+
 /*! \brief Internal function which creates an RTP instance */
-static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_media *session_media)
+static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+	const pjmedia_sdp_session *sdp)
 {
 	struct ast_rtp_engine_ice *ice;
 	struct ast_sockaddr temp_media_address;
@@ -223,6 +272,7 @@ static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_me
 		ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RETRANS_RECV, session->endpoint->media.webrtc);
 		ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RETRANS_SEND, session->endpoint->media.webrtc);
 		ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_REMB, session->endpoint->media.webrtc);
+		enable_rtp_extension(session, session_media, AST_RTP_EXTENSION_ABS_SEND_TIME, AST_RTP_EXTENSION_DIRECTION_SENDRECV, sdp);
 		if (session->endpoint->media.tos_video || session->endpoint->media.cos_video) {
 			ast_rtp_instance_set_qos(session_media->rtp, session->endpoint->media.tos_video,
 					session->endpoint->media.cos_video, "SIP RTP Video");
@@ -1101,6 +1151,113 @@ static void add_rtcp_fb_to_stream(struct ast_sip_session *session,
 	pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr);
 }
 
+static void add_extmap_to_stream(struct ast_sip_session *session,
+	struct ast_sip_session_media *session_media, pj_pool_t *pool, pjmedia_sdp_media *media)
+{
+	int idx;
+	char extmap_value[256];
+
+	if (!session->endpoint->media.webrtc || session_media->type != AST_MEDIA_TYPE_VIDEO) {
+		return;
+	}
+
+	/* RTP extension local unique identifiers start at '1' */
+	for (idx = 1; idx <= ast_rtp_instance_extmap_count(session_media->rtp); ++idx) {
+		enum ast_rtp_extension extension = ast_rtp_instance_extmap_get_extension(session_media->rtp, idx);
+		const char *direction_str = "";
+		pj_str_t stmp;
+		pjmedia_sdp_attr *attr;
+
+		/* If this is an unsupported RTP extension we can't place it into the SDP */
+		if (extension == AST_RTP_EXTENSION_UNSUPPORTED) {
+			continue;
+		}
+
+		switch (ast_rtp_instance_extmap_get_direction(session_media->rtp, idx)) {
+		case AST_RTP_EXTENSION_DIRECTION_SENDRECV:
+			/* Lack of a direction indicates sendrecv, so we leave it out */
+			direction_str = "";
+			break;
+		case AST_RTP_EXTENSION_DIRECTION_SENDONLY:
+			direction_str = "/sendonly";
+			break;
+		case AST_RTP_EXTENSION_DIRECTION_RECVONLY:
+			direction_str = "/recvonly";
+			break;
+		case AST_RTP_EXTENSION_DIRECTION_NONE:
+			/* It is impossible for a "none" direction extension to be negotiated but just in case
+			 * we treat it as inactive.
+			 */
+		case AST_RTP_EXTENSION_DIRECTION_INACTIVE:
+			direction_str = "/inactive";
+			break;
+		}
+
+		snprintf(extmap_value, sizeof(extmap_value), "%d%s %s", idx, direction_str,
+			ast_rtp_instance_extmap_get_uri(session_media->rtp, idx));
+		attr = pjmedia_sdp_attr_create(pool, "extmap", pj_cstr(&stmp, extmap_value));
+		pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr);
+	}
+}
+
+/*! \brief Function which processes extmap attributes in a stream */
+static void process_extmap_attributes(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+				   const struct pjmedia_sdp_media *remote_stream)
+{
+	int index;
+
+	if (!session->endpoint->media.webrtc || session_media->type != AST_MEDIA_TYPE_VIDEO) {
+		return;
+	}
+
+	ast_rtp_instance_extmap_clear(session_media->rtp);
+
+	for (index = 0; index < remote_stream->attr_count; ++index) {
+		pjmedia_sdp_attr *attr = remote_stream->attr[index];
+		char attr_value[pj_strlen(&attr->value) + 1];
+		char *uri;
+		int id;
+		char direction_str[10] = "";
+		char *attributes;
+		enum ast_rtp_extension_direction direction = AST_RTP_EXTENSION_DIRECTION_SENDRECV;
+
+		/* We only care about extmap attributes */
+		if (pj_strcmp2(&attr->name, "extmap")) {
+			continue;
+		}
+
+		ast_copy_pj_str(attr_value, &attr->value, sizeof(attr_value));
+
+		/* Split the combined unique identifier and direction away from the URI and attributes for easier parsing */
+		uri = strchr(attr_value, ' ');
+		if (ast_strlen_zero(uri)) {
+			continue;
+		}
+		*uri++ = '\0';
+
+		if ((sscanf(attr_value, "%30d%9s", &id, direction_str) < 1) || (id < 1)) {
+			/* We require at a minimum the unique identifier */
+			continue;
+		}
+
+		/* Convert from the string to the internal representation */
+		if (!strcasecmp(direction_str, "/sendonly")) {
+			direction = AST_RTP_EXTENSION_DIRECTION_SENDONLY;
+		} else if (!strcasecmp(direction_str, "/recvonly")) {
+			direction = AST_RTP_EXTENSION_DIRECTION_RECVONLY;
+		} else if (!strcasecmp(direction_str, "/inactive")) {
+			direction = AST_RTP_EXTENSION_DIRECTION_INACTIVE;
+		}
+
+		attributes = strchr(uri, ' ');
+		if (!ast_strlen_zero(attributes)) {
+			*attributes++ = '\0';
+		}
+
+		ast_rtp_instance_extmap_negotiate(session_media->rtp, id, direction, uri, attributes);
+	}
+}
+
 /*! \brief Function which negotiates an incoming media stream */
 static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,
 	struct ast_sip_session_media *session_media, const pjmedia_sdp_session *sdp,
@@ -1139,11 +1296,12 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,
 	}
 
 	/* Using the connection information create an appropriate RTP instance */
-	if (!session_media->rtp && create_rtp(session, session_media)) {
+	if (!session_media->rtp && create_rtp(session, session_media, sdp)) {
 		return -1;
 	}
 
 	process_ssrc_attributes(session, session_media, stream);
+	process_extmap_attributes(session, session_media, stream);
 	session_media_transport = ast_sip_session_media_get_transport(session, session_media);
 
 	if (session_media_transport == session_media || !session_media->bundled) {
@@ -1377,7 +1535,7 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
 		return 1;
 	}
 
-	if (!session_media->rtp && create_rtp(session, session_media)) {
+	if (!session_media->rtp && create_rtp(session, session_media, sdp)) {
 		return -1;
 	}
 
@@ -1602,6 +1760,7 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
 	add_ssrc_to_stream(session, session_media, pool, media);
 	add_msid_to_stream(session, session_media, pool, media, stream);
 	add_rtcp_fb_to_stream(session, session_media, pool, media);
+	add_extmap_to_stream(session, session_media, pool, media);
 
 	/* Add the media stream to the SDP */
 	sdp->media[sdp->media_count++] = media;
@@ -1676,11 +1835,12 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session,
 	}
 
 	/* Create an RTP instance if need be */
-	if (!session_media->rtp && create_rtp(session, session_media)) {
+	if (!session_media->rtp && create_rtp(session, session_media, local)) {
 		return -1;
 	}
 
 	process_ssrc_attributes(session, session_media, remote_stream);
+	process_extmap_attributes(session, session_media, remote_stream);
 
 	session_media_transport = ast_sip_session_media_get_transport(session, session_media);
 
diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c
index c87d87953a648629b8314ca02430504946e481ea..e28e58b9b20895ccc4dbdcc0a6cd8e71ced38dd2 100644
--- a/res/res_rtp_asterisk.c
+++ b/res/res_rtp_asterisk.c
@@ -551,6 +551,7 @@ static unsigned int ast_rtp_get_ssrc(struct ast_rtp_instance *instance);
 static const char *ast_rtp_get_cname(struct ast_rtp_instance *instance);
 static void ast_rtp_set_remote_ssrc(struct ast_rtp_instance *instance, unsigned int ssrc);
 static void ast_rtp_set_stream_num(struct ast_rtp_instance *instance, int stream_num);
+static int ast_rtp_extension_enable(struct ast_rtp_instance *instance, enum ast_rtp_extension extension);
 static int ast_rtp_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent);
 
 #ifdef HAVE_OPENSSL_SRTP
@@ -2249,6 +2250,7 @@ static struct ast_rtp_engine asterisk_rtp_engine = {
 	.cname_get = ast_rtp_get_cname,
 	.set_remote_ssrc = ast_rtp_set_remote_ssrc,
 	.set_stream_num = ast_rtp_set_stream_num,
+	.extension_enable = ast_rtp_extension_enable,
 	.bundle = ast_rtp_bundle,
 };
 
@@ -4282,6 +4284,20 @@ static int ast_rtcp_write(const void *data)
 	return res;
 }
 
+static void put_unaligned_time24(void *p, uint32_t time_msw, uint32_t time_lsw)
+{
+	unsigned char *cp = p;
+	uint32_t datum;
+
+	/* Convert the time to 6.18 format */
+	datum = (time_msw << 18) & 0x00fc0000;
+	datum |= (time_lsw >> 14) & 0x0003ffff;
+
+	cp[0] = datum >> 16;
+	cp[1] = datum >> 8;
+	cp[2] = datum;
+}
+
 /*! \pre instance is locked */
 static int rtp_raw_write(struct ast_rtp_instance *instance, struct ast_frame *frame, int codec)
 {
@@ -4399,14 +4415,46 @@ static int rtp_raw_write(struct ast_rtp_instance *instance, struct ast_frame *fr
 
 	/* If we know the remote address construct a packet and send it out */
 	if (!ast_sockaddr_isnull(&remote_address)) {
-		int hdrlen = 12, res, ice;
-		int packet_len = frame->datalen + hdrlen;
-		unsigned char *rtpheader = (unsigned char *)(frame->data.ptr - hdrlen);
+		int hdrlen = 12;
+		int res;
+		int ice;
+		int ext = 0;
+		int abs_send_time_id;
+		int packet_len;
+		unsigned char *rtpheader;
 
-		put_unaligned_uint32(rtpheader, htonl((2 << 30) | (codec << 16) | (seqno) | (mark << 23)));
+		/* If the abs-send-time extension has been negotiated determine how much space we need */
+		abs_send_time_id = ast_rtp_instance_extmap_get_id(instance, AST_RTP_EXTENSION_ABS_SEND_TIME);
+		if (abs_send_time_id != -1) {
+			/* 4 bytes for the shared information, 1 byte for identifier, 3 bytes for abs-send-time */
+			hdrlen += 8;
+			ext = 1;
+		}
+
+		packet_len = frame->datalen + hdrlen;
+		rtpheader = (unsigned char *)(frame->data.ptr - hdrlen);
+
+		put_unaligned_uint32(rtpheader, htonl((2 << 30) | (ext << 28) | (codec << 16) | (seqno) | (mark << 23)));
 		put_unaligned_uint32(rtpheader + 4, htonl(rtp->lastts));
 		put_unaligned_uint32(rtpheader + 8, htonl(rtp->ssrc));
 
+		/* We assume right now that we will only ever have the abs-send-time extension in the packet
+		 * which simplifies things a bit.
+		 */
+		if (abs_send_time_id != -1) {
+			unsigned int now_msw;
+			unsigned int now_lsw;
+
+			/* This happens before being placed into the retransmission buffer so that when we
+			 * retransmit we only have to update the timestamp, not everything else.
+			 */
+			put_unaligned_uint32(rtpheader + 12, htonl((0xBEDE << 16) | 1));
+			rtpheader[16] = (abs_send_time_id << 4) | 2;
+
+			timeval2ntp(ast_tvnow(), &now_msw, &now_lsw);
+			put_unaligned_time24(rtpheader + 17, now_msw, now_lsw);
+		}
+
 		/* If retransmissions are enabled, we need to store this packet for future use */
 		if (rtp->send_buffer) {
 			struct ast_rtp_rtcp_nack_payload *payload;
@@ -5263,12 +5311,20 @@ static int ast_rtp_rtcp_handle_nack(struct ast_rtp_instance *instance, unsigned
 	unsigned int pid;	/* Packet ID which refers to seqno of lost packet */
 	unsigned int blp;	/* Bitmask of following lost packets */
 	struct ast_sockaddr remote_address = { {0,} };
+	int abs_send_time_id;
+	unsigned int now_msw = 0;
+	unsigned int now_lsw = 0;
 
 	if (!rtp->send_buffer) {
 		ast_debug(1, "Tried to handle NACK request, but we don't have a RTP packet storage!\n");
 		return res;
 	}
 
+	abs_send_time_id = ast_rtp_instance_extmap_get_id(instance, AST_RTP_EXTENSION_ABS_SEND_TIME);
+	if (abs_send_time_id != -1) {
+		timeval2ntp(ast_tvnow(), &now_msw, &now_lsw);
+	}
+
 	ast_rtp_instance_get_remote_address(instance, &remote_address);
 
 	/*
@@ -5281,6 +5337,12 @@ static int ast_rtp_rtcp_handle_nack(struct ast_rtp_instance *instance, unsigned
 		/* We know the remote end is missing this packet. Go ahead and send it if we still have it. */
 		payload = (struct ast_rtp_rtcp_nack_payload *)ast_data_buffer_get(rtp->send_buffer, pid);
 		if (payload) {
+			if (abs_send_time_id != -1) {
+				/* On retransmission we need to update the timestamp within the packet, as it
+				 * is supposed to contain when the packet was actually sent.
+				 */
+				put_unaligned_time24(payload->buf + 17, now_msw, now_lsw);
+			}
 			res += rtp_sendto(instance, payload->buf, payload->size, 0, &remote_address, &ice);
 		} else {
 			ast_debug(1, "Received NACK request for RTP packet with seqno %d, but we don't have it\n", pid);
@@ -5299,6 +5361,9 @@ static int ast_rtp_rtcp_handle_nack(struct ast_rtp_instance *instance, unsigned
 				unsigned int seqno = (pid + blp_index) % 65536;
 				payload = (struct ast_rtp_rtcp_nack_payload *)ast_data_buffer_get(rtp->send_buffer, seqno);
 				if (payload) {
+					if (abs_send_time_id != -1) {
+						put_unaligned_time24(payload->buf + 17, now_msw, now_lsw);
+					}
 					res += rtp_sendto(instance, payload->buf, payload->size, 0, &remote_address, &ice);
 				} else {
 					ast_debug(1, "Remote end also requested RTP packet with seqno %d, but we don't have it\n", seqno);
@@ -7165,6 +7230,16 @@ static void ast_rtp_set_stream_num(struct ast_rtp_instance *instance, int stream
 	rtp->stream_num = stream_num;
 }
 
+static int ast_rtp_extension_enable(struct ast_rtp_instance *instance, enum ast_rtp_extension extension)
+{
+	switch (extension) {
+	case AST_RTP_EXTENSION_ABS_SEND_TIME:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
 /*! \pre child is locked */
 static int ast_rtp_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent)
 {