summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--include/baresip.h2
-rw-r--r--src/audio.c166
-rw-r--r--src/config.c6
-rw-r--r--src/core.h7
-rw-r--r--src/stream.c67
-rw-r--r--src/video.c5
-rw-r--r--test/call.c64
-rw-r--r--test/main.c1
-rw-r--r--test/test.h1
10 files changed, 308 insertions, 13 deletions
diff --git a/README.md b/README.md
index 5e40612..81d9481 100644
--- a/README.md
+++ b/README.md
@@ -330,6 +330,7 @@ zrtp ZRTP media encryption module
* RFC 4867 RTP Payload Format for the AMR and AMR-WB Audio Codecs
* RFC 4961 Symmetric RTP / RTP Control Protocol (RTCP)
* RFC 5168 XML Schema for Media Control
+* RFC 5285 A General Mechanism for RTP Header Extensions
* RFC 5506 Support for Reduced-Size RTCP
* RFC 5574 RTP Payload Format for the Speex Codec
* RFC 5576 Source-Specific Media Attributes in SDP
@@ -341,6 +342,7 @@ zrtp ZRTP media encryption module
* RFC 5764 DTLS Extension to Establish Keys for SRTP
* RFC 5780 NAT Behaviour Discovery Using STUN
* RFC 6263 App. Mechanism for Keeping Alive NAT Associated with RTP / RTCP
+* RFC 6464 A RTP Header Extension for Client-to-Mixer Audio Level Indication
* RFC 6716 Definition of the Opus Audio Codec
* RFC 6886 NAT Port Mapping Protocol (NAT-PMP)
* RFC 7587 RTP Payload Format for the Opus Speech and Audio Codec
diff --git a/include/baresip.h b/include/baresip.h
index ec7dafa..3c5e2c2 100644
--- a/include/baresip.h
+++ b/include/baresip.h
@@ -200,6 +200,7 @@ struct config_audio {
uint32_t channels_src; /**< Opt. channels for source */
bool src_first; /**< Audio source opened first */
enum audio_mode txmode; /**< Audio transmit mode */
+ bool level; /**< Enable audio level indication */
};
#ifdef USE_VIDEO
@@ -950,6 +951,7 @@ void audio_set_devicename(struct audio *a, const char *src, const char *play);
int audio_set_source(struct audio *au, const char *mod, const char *device);
int audio_set_player(struct audio *au, const char *mod, const char *device);
void audio_encoder_cycle(struct audio *audio);
+int audio_level_get(const struct audio *au, double *level);
int audio_debug(struct re_printf *pf, const struct audio *a);
diff --git a/src/audio.c b/src/audio.c
index 820ff1b..1d42065 100644
--- a/src/audio.c
+++ b/src/audio.c
@@ -134,6 +134,8 @@ struct aurx {
int16_t *sampv_rs; /**< Sample buffer for resampler */
uint32_t ptime; /**< Packet time for receiving */
int pt; /**< Payload type for incoming RTP */
+ double level_last;
+ bool level_set;
};
@@ -146,12 +148,18 @@ struct audio {
struct telev *telev; /**< Telephony events */
struct config_audio cfg; /**< Audio configuration */
bool started; /**< Stream is started flag */
+ bool level_enabled; /**< Audio level RTP ext. enabled */
+ unsigned extmap_aulevel; /**< ID Range 1-14 inclusive */
audio_event_h *eventh; /**< Event handler */
audio_err_h *errh; /**< Audio error handler */
void *arg; /**< Handler argument */
};
+/* RFC 6464 */
+static const char *uri_aulevel = "urn:ietf:params:rtp-hdrext:ssrc-audio-level";
+
+
static void stop_tx(struct autx *tx, struct audio *a)
{
if (!tx || !a)
@@ -307,6 +315,29 @@ static int add_audio_codec(struct audio *a, struct sdp_media *m,
}
+static int append_rtpext(struct audio *au, struct mbuf *mb,
+ int16_t *sampv, size_t sampc)
+{
+ uint8_t data[1];
+ double level;
+ int err;
+
+ /* audio level must be calculated from the audio samples that
+ * are actually sent on the network. */
+ level = aulevel_calc_dbov(sampv, sampc);
+
+ data[0] = (int)-level & 0x7f;
+
+ err = rtpext_encode(mb, au->extmap_aulevel, 1, data);
+ if (err) {
+ warning("audio: rtpext_encode failed (%m)\n", err);
+ return err;
+ }
+
+ return err;
+}
+
+
/**
* Encoder audio and send via stream
*
@@ -323,12 +354,36 @@ static void encode_rtp_send(struct audio *a, struct autx *tx,
size_t frame_size; /* number of samples per channel */
size_t sampc_rtp;
size_t len;
+ size_t ext_len = 0;
int err;
if (!tx->ac || !tx->ac->ench)
return;
tx->mb->pos = tx->mb->end = STREAM_PRESZ;
+
+ if (a->level_enabled) {
+
+ /* skip the extension header */
+ tx->mb->pos += RTPEXT_HDR_SIZE;
+
+ err = append_rtpext(a, tx->mb, sampv, sampc);
+ if (err)
+ return;
+
+ ext_len = tx->mb->pos - STREAM_PRESZ;
+
+ /* write the Extension header at the beginning */
+ tx->mb->pos = STREAM_PRESZ;
+
+ err = rtpext_hdr_encode(tx->mb, ext_len - RTPEXT_HDR_SIZE);
+ if (err)
+ return;
+
+ tx->mb->pos = STREAM_PRESZ + ext_len;
+ tx->mb->end = STREAM_PRESZ + ext_len;
+ }
+
len = mbuf_get_space(tx->mb);
err = tx->ac->ench(tx->enc, mbuf_buf(tx->mb), &len, sampv, sampc);
@@ -344,11 +399,12 @@ static void encode_rtp_send(struct audio *a, struct autx *tx,
}
tx->mb->pos = STREAM_PRESZ;
- tx->mb->end = STREAM_PRESZ + len;
+ tx->mb->end = STREAM_PRESZ + ext_len + len;
if (mbuf_get_left(tx->mb)) {
+
if (len) {
- err = stream_send(a->strm, tx->marker, -1,
+ err = stream_send(a->strm, ext_len!=0, tx->marker, -1,
tx->ts, tx->mb);
if (err)
goto out;
@@ -438,7 +494,7 @@ static void check_telev(struct audio *a, struct autx *tx)
return;
tx->mb->pos = STREAM_PRESZ;
- err = stream_send(a->strm, marker, fmt->pt, tx->ts_tel, tx->mb);
+ err = stream_send(a->strm, false, marker, fmt->pt, tx->ts_tel, tx->mb);
if (err) {
warning("audio: telev: stream_send %m\n", err);
}
@@ -617,10 +673,12 @@ static int aurx_stream_decode(struct aurx *rx, struct mbuf *mb)
/* Handle incoming stream data from the network */
static void stream_recv_handler(const struct rtp_header *hdr,
+ struct rtpext *extv, size_t extc,
struct mbuf *mb, void *arg)
{
struct audio *a = arg;
struct aurx *rx = &a->rx;
+ size_t i;
int err;
if (!mb)
@@ -651,6 +709,20 @@ static void stream_recv_handler(const struct rtp_header *hdr,
return;
}
+ /* RFC 5285 -- A General Mechanism for RTP Header Extensions */
+ for (i=0; i<extc; i++) {
+
+ if (extv[i].id == a->extmap_aulevel) {
+
+ a->rx.level_last = -(double)(extv[i].data[0] & 0x7f);
+ a->rx.level_set = true;
+ }
+ else {
+ info("audio: rtp header ext ignored (id=%u)\n",
+ extv[i].id);
+ }
+ }
+
out:
(void)aurx_stream_decode(&a->rx, mb);
}
@@ -718,6 +790,18 @@ int audio_alloc(struct audio **ap, const struct config *cfg,
if (err)
goto out;
+ if (cfg->audio.level && offerer) {
+
+ a->extmap_aulevel = 1;
+
+ err = sdp_media_set_lattr(stream_sdpmedia(a->strm), true,
+ "extmap",
+ "%u %s",
+ a->extmap_aulevel, uri_aulevel);
+ if (err)
+ goto out;
+ }
+
/* Audio codecs */
for (le = list_head(aucodecl); le; le = le->next) {
err = add_audio_codec(a, stream_sdpmedia(a->strm), le->data);
@@ -1377,6 +1461,45 @@ bool audio_ismuted(const struct audio *a)
}
+static bool extmap_handler(const char *name, const char *value, void *arg)
+{
+ struct audio *au = arg;
+ struct sdp_extmap extmap;
+ int err;
+ (void)name;
+
+ err = sdp_extmap_decode(&extmap, value);
+ if (err) {
+ warning("audio: sdp_extmap_decode error (%m)\n", err);
+ return false;
+ }
+
+ if (0 == pl_strcasecmp(&extmap.name, uri_aulevel)) {
+
+ if (extmap.id < RTPEXT_ID_MIN || extmap.id > RTPEXT_ID_MAX) {
+ warning("audio: extmap id out of range (%u)\n",
+ extmap.id);
+ return false;
+ }
+
+ au->extmap_aulevel = extmap.id;
+
+ err = sdp_media_set_lattr(stream_sdpmedia(au->strm), true,
+ "extmap",
+ "%u %s",
+ au->extmap_aulevel,
+ uri_aulevel);
+ if (err)
+ return false;
+
+ au->level_enabled = true;
+ info("audio: client-to-mixer audio levels enabled\n");
+ }
+
+ return false;
+}
+
+
void audio_sdp_attr_decode(struct audio *a)
{
const char *attr;
@@ -1404,6 +1527,39 @@ void audio_sdp_attr_decode(struct audio *a)
}
}
}
+
+ /* Client-to-Mixer Audio Level Indication */
+ if (a->cfg.level) {
+ sdp_media_rattr_apply(stream_sdpmedia(a->strm),
+ "extmap",
+ extmap_handler, a);
+ }
+}
+
+
+/**
+ * Get the last value of the audio level from incoming RTP packets
+ *
+ * @param au Audio object
+ * @param levelp Pointer to where to write audio level value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int audio_level_get(const struct audio *au, double *levelp)
+{
+ if (!au)
+ return EINVAL;
+
+ if (!au->level_enabled)
+ return ENOTSUP;
+
+ if (!au->rx.level_set)
+ return ENOENT;
+
+ if (levelp)
+ *levelp = au->rx.level_last;
+
+ return 0;
}
@@ -1440,6 +1596,10 @@ int audio_debug(struct re_printf *pf, const struct audio *a)
aucodec_print, rx->ac,
aubuf_debug, rx->aubuf,
rx->ptime, rx->pt);
+ if (rx->level_set) {
+ err |= re_hprintf(pf, " level %.3f dBov\n",
+ rx->level_last);
+ }
err |= re_hprintf(pf,
" %H"
diff --git a/src/config.c b/src/config.c
index cd6430a..9f2de5d 100644
--- a/src/config.c
+++ b/src/config.c
@@ -50,6 +50,7 @@ static struct config core_config = {
0,
false,
AUDIO_MODE_POLL,
+ false,
},
#ifdef USE_VIDEO
@@ -195,6 +196,8 @@ int config_parse_conf(struct config *cfg, const struct conf *conf)
0 == conf_get(conf, "audio_player", &ap))
cfg->audio.src_first = as.p < ap.p;
+ (void)conf_get_bool(conf, "audio_level", &cfg->audio.level);
+
#ifdef USE_VIDEO
/* Video */
(void)conf_get_csv(conf, "video_source",
@@ -277,6 +280,7 @@ int config_print(struct re_printf *pf, const struct config *cfg)
"ausrc_srate\t\t%u\n"
"auplay_channels\t\t%u\n"
"ausrc_channels\t\t%u\n"
+ "audio_level\t\t%s\n"
"\n"
#ifdef USE_VIDEO
"# Video\n"
@@ -320,6 +324,7 @@ int config_print(struct re_printf *pf, const struct config *cfg)
range_print, &cfg->audio.channels,
cfg->audio.srate_play, cfg->audio.srate_src,
cfg->audio.channels_play, cfg->audio.channels_src,
+ cfg->audio.level ? "yes" : "no",
#ifdef USE_VIDEO
cfg->video.src_mod, cfg->video.src_dev,
@@ -447,6 +452,7 @@ static int core_config_template(struct re_printf *pf, const struct config *cfg)
"#auplay_srate\t\t48000\n"
"#ausrc_channels\t\t0\n"
"#auplay_channels\t\t0\n"
+ "audio_level\t\tno\n"
,
poll_method_name(poll_method_best()),
cfg->call.local_timeout,
diff --git a/src/core.h b/src/core.h
index 5dc5e9d..9c0c2ba 100644
--- a/src/core.h
+++ b/src/core.h
@@ -328,8 +328,9 @@ struct rtp_header;
enum {STREAM_PRESZ = 4+12}; /* same as RTP_HEADER_SIZE */
-typedef void (stream_rtp_h)(const struct rtp_header *hdr, struct mbuf *mb,
- void *arg);
+typedef void (stream_rtp_h)(const struct rtp_header *hdr,
+ struct rtpext *extv, size_t extc,
+ struct mbuf *mb, void *arg);
typedef void (stream_rtcp_h)(struct rtcp_msg *msg, void *arg);
typedef void (stream_error_h)(struct stream *strm, int err, void *arg);
@@ -377,7 +378,7 @@ int stream_alloc(struct stream **sp, const struct config_avt *cfg,
const char *cname,
stream_rtp_h *rtph, stream_rtcp_h *rtcph, void *arg);
struct sdp_media *stream_sdpmedia(const struct stream *s);
-int stream_send(struct stream *s, bool marker, int pt, uint32_t ts,
+int stream_send(struct stream *s, bool ext, bool marker, int pt, uint32_t ts,
struct mbuf *mb);
void stream_update(struct stream *s);
void stream_update_encoder(struct stream *s, int pt_enc);
diff --git a/src/stream.c b/src/stream.c
index acd4766..b9a0d6a 100644
--- a/src/stream.c
+++ b/src/stream.c
@@ -148,6 +148,61 @@ static void stream_destructor(void *arg)
}
+static void handle_rtp(struct stream *s, const struct rtp_header *hdr,
+ struct mbuf *mb)
+{
+ struct rtpext extv[8];
+ size_t extc = 0;
+
+ /* RFC 5285 -- A General Mechanism for RTP Header Extensions */
+ if (hdr->ext && hdr->x.len && mb) {
+
+ const size_t pos = mb->pos;
+ const size_t end = mb->end;
+ const size_t ext_stop = mb->pos;
+ size_t ext_len;
+ size_t i;
+ int err;
+
+ if (hdr->x.type != RTPEXT_TYPE_MAGIC) {
+ info("stream: unknown ext type ignored (0x%04x)\n",
+ hdr->x.type);
+ goto handler;
+ }
+
+ ext_len = hdr->x.len*sizeof(uint32_t);
+ if (mb->pos < ext_len) {
+ warning("stream: corrupt rtp packet,"
+ " not enough space for rtpext of %zu bytes\n",
+ ext_len);
+ return;
+ }
+
+ mb->pos = mb->pos - ext_len;
+ mb->end = ext_stop;
+
+ for (i=0; i<ARRAY_SIZE(extv) && mbuf_get_left(mb); i++) {
+
+ err = rtpext_decode(&extv[i], mb);
+ if (err) {
+ warning("stream: rtpext_decode failed (%m)\n",
+ err);
+ return;
+ }
+ }
+
+ extc = i;
+
+ mb->pos = pos;
+ mb->end = end;
+ }
+
+ handler:
+ s->rtph(hdr, extv, extc, mb, s->arg);
+
+}
+
+
static void rtp_recv(const struct sa *src, const struct rtp_header *hdr,
struct mbuf *mb, void *arg)
{
@@ -204,17 +259,17 @@ static void rtp_recv(const struct sa *src, const struct rtp_header *hdr,
s->jbuf_started = true;
if (lostcalc(s, hdr2.seq) > 0)
- s->rtph(hdr, NULL, s->arg);
+ handle_rtp(s, hdr, NULL);
- s->rtph(&hdr2, mb2, s->arg);
+ handle_rtp(s, &hdr2, mb2);
mem_deref(mb2);
}
else {
if (lostcalc(s, hdr->seq) > 0)
- s->rtph(hdr, NULL, s->arg);
+ handle_rtp(s, hdr, NULL);
- s->rtph(hdr, mb, s->arg);
+ handle_rtp(s, hdr, mb);
}
}
@@ -419,7 +474,7 @@ static void stream_start_keepalive(struct stream *s)
}
-int stream_send(struct stream *s, bool marker, int pt, uint32_t ts,
+int stream_send(struct stream *s, bool ext, bool marker, int pt, uint32_t ts,
struct mbuf *mb)
{
int err = 0;
@@ -438,7 +493,7 @@ int stream_send(struct stream *s, bool marker, int pt, uint32_t ts,
pt = s->pt_enc;
if (pt >= 0) {
- err = rtp_send(s->rtp, sdp_media_raddr(s->sdp), false,
+ err = rtp_send(s->rtp, sdp_media_raddr(s->sdp), ext,
marker, pt, ts, mb);
if (err)
s->metric_tx.n_err++;
diff --git a/src/video.c b/src/video.c
index 9077a0c..a73ea34 100644
--- a/src/video.c
+++ b/src/video.c
@@ -255,7 +255,7 @@ static void vidqueue_poll(struct vtx *vtx, uint64_t jfs, uint64_t prev_jfs)
sent += mbuf_get_left(qent->mb);
- stream_send(vtx->video->strm, qent->marker, qent->pt,
+ stream_send(vtx->video->strm, false, qent->marker, qent->pt,
qent->ts, qent->mb);
le = le->next;
@@ -654,10 +654,13 @@ static int pt_handler(struct video *v, uint8_t pt_old, uint8_t pt_new)
/* Handle incoming stream data from the network */
static void stream_recv_handler(const struct rtp_header *hdr,
+ struct rtpext *extv, size_t extc,
struct mbuf *mb, void *arg)
{
struct video *v = arg;
int err;
+ (void)extv;
+ (void)extc;
if (!mb)
goto out;
diff --git a/test/call.c b/test/call.c
index f3881c6..60af1b2 100644
--- a/test/call.c
+++ b/test/call.c
@@ -703,3 +703,67 @@ int test_call_video(void)
return err;
}
#endif
+
+
+static void mock_sample_handler(const int16_t *sampv, size_t sampc, void *arg)
+{
+ struct fixture *fix = arg;
+ bool got_aulevel;
+ (void)sampv;
+ (void)sampc;
+
+ got_aulevel =
+ 0 == audio_level_get(call_audio(ua_call(fix->a.ua)), NULL) &&
+ 0 == audio_level_get(call_audio(ua_call(fix->b.ua)), NULL);
+
+ if (got_aulevel)
+ re_cancel();
+}
+
+
+int test_call_aulevel(void)
+{
+ struct fixture fix, *f = &fix;
+ struct ausrc *ausrc = NULL;
+ struct auplay *auplay = NULL;
+ double lvl;
+ int err = 0;
+
+ /* Use a low packet time, so the test completes quickly */
+ fixture_init_prm(f, ";ptime=1");
+
+ conf_config()->audio.level = true;
+
+ err = mock_ausrc_register(&ausrc);
+ TEST_ERR(err);
+ err = mock_auplay_register(&auplay, mock_sample_handler, f);
+ TEST_ERR(err);
+
+ f->estab_action = ACTION_NOTHING;
+
+ /* Make a call from A to B */
+ err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF);
+ TEST_ERR(err);
+
+ /* run main-loop with timeout, wait for events */
+ err = re_main_timeout(5000);
+ TEST_ERR(err);
+ TEST_ERR(fix.err);
+
+ /* verify audio silence */
+ err = audio_level_get(call_audio(ua_call(f->a.ua)), &lvl);
+ TEST_ERR(err);
+ ASSERT_EQ(-96, lvl);
+ err = audio_level_get(call_audio(ua_call(f->b.ua)), &lvl);
+ TEST_ERR(err);
+ ASSERT_EQ(-96, lvl);
+
+ out:
+ conf_config()->audio.level = false;
+
+ fixture_close(f);
+ mem_deref(auplay);
+ mem_deref(ausrc);
+
+ return err;
+}
diff --git a/test/main.c b/test/main.c
index f443dcb..aa56420 100644
--- a/test/main.c
+++ b/test/main.c
@@ -33,6 +33,7 @@ static const struct test tests[] = {
TEST(test_call_multiple),
TEST(test_call_max),
TEST(test_call_dtmf),
+ TEST(test_call_aulevel),
#ifdef USE_VIDEO
TEST(test_call_video),
#endif
diff --git a/test/test.h b/test/test.h
index a68d61d..355d698 100644
--- a/test/test.h
+++ b/test/test.h
@@ -204,6 +204,7 @@ int test_call_multiple(void);
int test_call_max(void);
int test_call_dtmf(void);
int test_call_video(void);
+int test_call_aulevel(void);
#ifdef __cplusplus