diff options
author | Jonas Smedegaard <dr@jones.dk> | 2018-01-08 14:56:48 +0100 |
---|---|---|
committer | Jonas Smedegaard <dr@jones.dk> | 2018-01-08 14:56:48 +0100 |
commit | fe7cb816ef28e35f4c6e6724604b4cc5b15dc92c (patch) | |
tree | 788c26905465482a58346f5ccaecbfbdfc110bd4 | |
parent | 03cac4fb3669ffe76f11057ead5b241edaec32b3 (diff) | |
parent | b7869a262cbca2241c70549af9c877529706c15f (diff) |
Update upstream source from tag 'upstream/0.5.7'
Update to upstream version '0.5.7'
with Debian dir 7917999b8a336b3f50f0f8f70d106923ec5ecf5e
85 files changed, 4058 insertions, 598 deletions
@@ -13,7 +13,7 @@ USE_VIDEO := 1 PROJECT := baresip -VERSION := 0.5.6 +VERSION := 0.5.7 DESCR := "Baresip is a modular SIP User-Agent with audio and video support" # Verbose and silent build modes @@ -106,7 +106,7 @@ Distributed under BSD license - FFmpeg/libav libavformat/avdevice input - Cairo video-source test module - Direct Show video-source - - MacOSX QTcapture/quicktime video-source + - MacOSX QTcapture/AVCapture video-source - RST media player - Linux V4L/V4L2 video-source - X11 grabber video-source @@ -246,6 +246,7 @@ gst1 Gstreamer 1.0 audio source gst_video Gstreamer video codec gst_video1 Gstreamer 1.0 video codec gtk GTK+ 2.0 UI +gzrtp ZRTP module using GNU ZRTP C++ library h265 H.265 video codec httpd HTTP webserver UI-module ice ICE protocol for NAT Traversal @@ -256,6 +257,7 @@ l16 L16 audio codec libsrtp Secure RTP encryption using libsrtp menu Interactive menu mpa MPA Speech and Audio Codec +mqtt MQTT (Message Queue Telemetry Transport) module mwi Message Waiting Indication natbd NAT Behavior Discovery Module natpmp NAT Port Mapping Protocol (NAT-PMP) module @@ -271,7 +273,6 @@ portaudio Portaudio driver pulse Pulseaudio driver presence Presence module qtcapture Apple QTCapture video source driver -quicktime Apple Quicktime video source driver (deprecated) rst Radio streamer using mpg123 sdl Simple DirectMedia Layer (SDL) video output driver sdl2 Simple DirectMedia Layer v2 (SDL2) video output driver diff --git a/docs/ChangeLog b/docs/ChangeLog index 10c9a41..d79818e 100644 --- a/docs/ChangeLog +++ b/docs/ChangeLog @@ -1,3 +1,96 @@ +2017-12-25 Alfred E. Heggestad <alfred.heggestad@gmail.com> + + * Version 0.5.7 + + * GIT URL: https://github.com/alfredh/baresip.git + * GIT tag: v0.5.7 + * NOTE: Requires libre v0.5.5 or later + Requires librem v0.5.0 or later + + * Credits: Thanks to Swedish Radio who sponsored many new + features in this release. + + * new commands: + - 'conf_reload' -- Reload config file + + * new modules: + - gzrtp ZRTP module using GNU ZRTP C++ library + (thanks glenvt18) + + - mqtt MQTT (Message Queue Telemetry Transport) module + (sponsored by Swedish Radio) + + * config: + - audio_txmode poll|thread Set audio transmit mode + - auplay_format s16|float|s24_3le Set playback sample format + - ausrc_format s16|float|s24_3le Set source sample format + - sdp_ebuacip yes|no Enable EBU-ACIP parameters + - zrtp_hash yes|no Enable/disable ZRTP hash + + * baresip-core: + - audio: add sample format conversion + - audio: add sample format for source/playback + - audio: check timestamps on incoming RTP packets + - audio: pace outgoing packets in txmode=thread + - audio: remove txmode with realtime thread + - audio: remove txmode with timer + - audio: set EBUACIP parameters in SDP + - auplay: add sample format to auplay_prm + - auplay: change write handler to any sample format + - ausrc: add sample format to ausrc_prm + - ausrc: change read handler to any sample format + - event.c: new file for generic event handling + - event: add event_encode_dict to encode event to a dictionary + - event: added UA_EVENT_CALL_RTCP for received RTCP + - log: print to stdout (ref #320) + + * selftest: + - add test for different audio tx-modes + - add test for float audio sample format + + * Modules: + + * alsa: add support for multiple sample formats + + * audiounit: add support for FLOAT sample format + + * auloop: add support for multiple sample formats + + * avahi: Bugfix: Destroy resolver after callback (#318) + (thanks Jonathan Sieber) + + * avcodec: change x264 rate control mode to ABR (#334) + (thanks Jonathan Sieber) + + * debug_cmd: add command 'conf_reload' to reload config file + + * gzrtp: ZRTP module using GNU ZRTP C++ library + (thanks glenvt18) + + * menu: add config 'ringback_disabled' to disable playing + of ringback tone. + + * mqtt: MQTT (Message Queue Telemetry Transport) module + new module using libmosquitto as the backend. + + * opus: fix encoder bitrate, ref #305 + add opus_stereo config parameter (thanks Ola Palm) + add config param opus_sprop_stereo (thanks Ola Palm) + + * portaudio: add support for FLOAT sample format + + * pulse: add support for FLOAT sample format + remove garbage at the beginning of a recording (#323) + + * quicktime: module was removed + + * rst: add support for multiple sample formats + + * zrtp: add signaling hash support (#311) + + + + 2017-10-14 Alfred E. Heggestad <alfred.heggestad@gmail.com> * Version 0.5.6 diff --git a/docs/THANKS b/docs/THANKS index de0fb7a..b7f0949 100644 --- a/docs/THANKS +++ b/docs/THANKS @@ -1,5 +1,6 @@ @GGGO @elektm93 +@glenvt18 @jungle-boogie Aaron Herting AlexMarlo diff --git a/docs/examples/config b/docs/examples/config index 0bb37e5..1eabd62 100644 --- a/docs/examples/config +++ b/docs/examples/config @@ -170,5 +170,8 @@ ice_debug no ice_nomination regular # {regular,aggressive} ice_mode full # {full,lite} +# ZRTP +#zrtp_hash no # Disable SDP zrtp-hash (not recommended) + # sndfile # snd_path /tmp/ diff --git a/include/baresip.h b/include/baresip.h index e394e75..746a95e 100644 --- a/include/baresip.h +++ b/include/baresip.h @@ -13,7 +13,7 @@ extern "C" { /** Defines the Baresip version string */ -#define BARESIP_VERSION "0.5.6" +#define BARESIP_VERSION "0.5.7" #ifndef NET_MAX_NS @@ -26,6 +26,7 @@ struct sa; struct sdp_media; struct sdp_session; struct sip_msg; +struct stream; struct ua; struct vidframe; struct vidrect; @@ -167,8 +168,6 @@ static inline bool in_range(const struct range *rng, uint32_t val) enum audio_mode { AUDIO_MODE_POLL = 0, /**< Polling mode */ AUDIO_MODE_THREAD, /**< Use dedicated thread */ - AUDIO_MODE_THREAD_REALTIME, /**< Use dedicated realtime-thread */ - AUDIO_MODE_TMR /**< Use timer */ }; @@ -204,6 +203,8 @@ struct config_audio { bool src_first; /**< Audio source opened first */ enum audio_mode txmode; /**< Audio transmit mode */ bool level; /**< Enable audio level indication */ + int src_fmt; /**< Audio source sample format */ + int play_fmt; /**< Audio playback sample format */ }; #ifdef USE_VIDEO @@ -248,6 +249,11 @@ struct config_bfcp { }; #endif +/** SDP */ +struct config_sdp { + bool ebuacip; /**< Enable EBU-ACIP parameters */ +}; + /** Core configuration */ struct config { @@ -268,6 +274,8 @@ struct config { #ifdef USE_VIDEO struct config_bfcp bfcp; #endif + + struct config_sdp sdp; }; int config_parse_conf(struct config *cfg, const struct conf *conf); @@ -358,9 +366,10 @@ struct ausrc_prm { uint32_t srate; /**< Sampling rate in [Hz] */ uint8_t ch; /**< Number of channels */ uint32_t ptime; /**< Wanted packet-time in [ms] */ + int fmt; /**< Sample format (enum aufmt) */ }; -typedef void (ausrc_read_h)(const int16_t *sampv, size_t sampc, void *arg); +typedef void (ausrc_read_h)(const void *sampv, size_t sampc, void *arg); typedef void (ausrc_error_h)(int err, const char *str, void *arg); typedef int (ausrc_alloc_h)(struct ausrc_st **stp, const struct ausrc *ausrc, @@ -390,9 +399,10 @@ struct auplay_prm { uint32_t srate; /**< Sampling rate in [Hz] */ uint8_t ch; /**< Number of channels */ uint32_t ptime; /**< Wanted packet-time in [ms] */ + int fmt; /**< Sample format (enum aufmt) */ }; -typedef void (auplay_write_h)(int16_t *sampv, size_t sampc, void *arg); +typedef void (auplay_write_h)(void *sampv, size_t sampc, void *arg); typedef int (auplay_alloc_h)(struct auplay_st **stp, const struct auplay *ap, struct auplay_prm *prm, const char *device, @@ -577,6 +587,7 @@ enum ua_event { UA_EVENT_CALL_TRANSFER_FAILED, UA_EVENT_CALL_DTMF_START, UA_EVENT_CALL_DTMF_END, + UA_EVENT_CALL_RTCP, UA_EVENT_MAX, }; @@ -974,6 +985,7 @@ 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); +struct stream *audio_strm(const struct audio *a); /* @@ -993,6 +1005,14 @@ void video_encoder_cycle(struct video *video); int video_debug(struct re_printf *pf, const struct video *v); uint32_t video_calc_rtp_timestamp(int64_t pts, unsigned fps); double video_calc_seconds(uint32_t rtp_ts); +struct stream *video_strm(const struct video *v); + + +/* + * Generic stream + */ + +const struct rtcp_stats *stream_rtcp_stats(const struct stream *strm); /* @@ -1152,6 +1172,14 @@ double mos_calculate(double *r_factor, double rtt, /* + * Generic event + */ + +int event_encode_dict(struct odict *od, struct ua *ua, enum ua_event ev, + struct call *call, const char *prm); + + +/* * Baresip instance */ diff --git a/mk/Doxyfile b/mk/Doxyfile index 30ee43a..3606286 100644 --- a/mk/Doxyfile +++ b/mk/Doxyfile @@ -4,7 +4,7 @@ # Project related configuration options #--------------------------------------------------------------------------- PROJECT_NAME = baresip -PROJECT_NUMBER = 0.5.6 +PROJECT_NUMBER = 0.5.7 OUTPUT_DIRECTORY = ../baresip-dox CREATE_SUBDIRS = NO OUTPUT_LANGUAGE = English diff --git a/mk/modules.mk b/mk/modules.mk index 20ff07a..9211dfd 100644 --- a/mk/modules.mk +++ b/mk/modules.mk @@ -187,6 +187,9 @@ USE_SPEEX_PP := $(shell [ -f $(SYSROOT)/include/speex_preprocess.h ] || \ USE_SYSLOG := $(shell [ -f $(SYSROOT)/include/syslog.h ] || \ [ -f $(SYSROOT_ALT)/include/syslog.h ] || \ [ -f $(SYSROOT)/local/include/syslog.h ] && echo "yes") +HAVE_LIBMQTT := $(shell [ -f $(SYSROOT)/include/mosquitto.h ] || \ + [ -f $(SYSROOT)/local/include/mosquitto.h ] \ + && echo "yes") USE_V4L := $(shell [ -f $(SYSROOT)/include/libv4l1.h ] || \ [ -f $(SYSROOT)/local/include/libv4l1.h ] \ && echo "yes") @@ -273,6 +276,10 @@ MODULES += srtp MODULES += uuid MODULES += debug_cmd +ifneq ($(HAVE_LIBMQTT),) +MODULES += mqtt +endif + ifneq ($(HAVE_PTHREAD),) MODULES += aubridge aufile endif @@ -452,6 +459,9 @@ endif ifneq ($(USE_ZRTP),) MODULES += zrtp endif +ifneq ($(USE_GZRTP),) +MODULES += gzrtp +endif ifneq ($(USE_DSHOW),) MODULES += dshow endif diff --git a/modules/alsa/alsa.c b/modules/alsa/alsa.c index d695056..78de296 100644 --- a/modules/alsa/alsa.c +++ b/modules/alsa/alsa.c @@ -29,7 +29,6 @@ char alsa_dev[64] = "default"; -enum aufmt alsa_sample_format = AUFMT_S16LE; static struct ausrc *ausrc; static struct auplay *auplay; @@ -146,24 +145,13 @@ static int alsa_init(void) struct pl val; int err; + /* XXX: remove check later */ if (0 == conf_get(conf_cur(), "alsa_sample_format", &val)) { - if (0 == pl_strcasecmp(&val, "s16")) { - alsa_sample_format = AUFMT_S16LE; - } - else if (0 == pl_strcasecmp(&val, "float")) { - alsa_sample_format = AUFMT_FLOAT; - } - else if (0 == pl_strcasecmp(&val, "s24_3le")) { - alsa_sample_format = AUFMT_S24_3LE; - } - else { - warning("alsa: unknown sample format '%r'\n", &val); - return EINVAL; - } - - info("alsa: configured sample format `%s'\n", - aufmt_name(alsa_sample_format)); + warning("alsa: alsa_sample_format is deprecated" + " -- use ausrc_format or auplay_format instead\n"); + + (void)val; } err = ausrc_register(&ausrc, baresip_ausrcl(), diff --git a/modules/alsa/alsa.h b/modules/alsa/alsa.h index 61c408c..fbd67f2 100644 --- a/modules/alsa/alsa.h +++ b/modules/alsa/alsa.h @@ -6,7 +6,7 @@ extern char alsa_dev[64]; -extern enum aufmt alsa_sample_format; + int alsa_reset(snd_pcm_t *pcm, uint32_t srate, uint32_t ch, uint32_t num_frames, snd_pcm_format_t pcmfmt); diff --git a/modules/alsa/alsa_play.c b/modules/alsa/alsa_play.c index aca8da2..6547e21 100644 --- a/modules/alsa/alsa_play.c +++ b/modules/alsa/alsa_play.c @@ -22,14 +22,12 @@ struct auplay_st { pthread_t thread; bool run; snd_pcm_t *write; - int16_t *sampv; - void *xsampv; + void *sampv; size_t sampc; auplay_write_h *wh; void *arg; struct auplay_prm prm; char *device; - enum aufmt aufmt; }; @@ -48,7 +46,6 @@ static void auplay_destructor(void *arg) snd_pcm_close(st->write); mem_deref(st->sampv); - mem_deref(st->xsampv); mem_deref(st->device); } @@ -67,14 +64,7 @@ static void *write_thread(void *arg) st->wh(st->sampv, st->sampc, st->arg); - if (st->aufmt == AUFMT_S16LE) { - sampv = st->sampv; - } - else { - sampv = st->xsampv; - auconv_from_s16(st->aufmt, st->xsampv, - st->sampv, st->sampc); - } + sampv = st->sampv; n = snd_pcm_writei(st->write, sampv, samples); @@ -127,26 +117,16 @@ int alsa_play_alloc(struct auplay_st **stp, const struct auplay *ap, st->ap = ap; st->wh = wh; st->arg = arg; - st->aufmt = alsa_sample_format; st->sampc = prm->srate * prm->ch * prm->ptime / 1000; num_frames = st->prm.srate * st->prm.ptime / 1000; - st->sampv = mem_alloc(2 * st->sampc, NULL); + st->sampv = mem_alloc(aufmt_sample_size(prm->fmt) * st->sampc, NULL); if (!st->sampv) { err = ENOMEM; goto out; } - if (st->aufmt != AUFMT_S16LE) { - size_t sz = aufmt_sample_size(st->aufmt) * st->sampc; - st->xsampv = mem_alloc(sz, NULL); - if (!st->xsampv) { - err = ENOMEM; - goto out; - } - } - err = snd_pcm_open(&st->write, st->device, SND_PCM_STREAM_PLAYBACK, 0); if (err < 0) { warning("alsa: could not open auplay device '%s' (%s)\n", @@ -154,10 +134,10 @@ int alsa_play_alloc(struct auplay_st **stp, const struct auplay *ap, goto out; } - pcmfmt = aufmt_to_alsaformat(st->aufmt); + pcmfmt = aufmt_to_alsaformat(prm->fmt); if (pcmfmt == SND_PCM_FORMAT_UNKNOWN) { warning("alsa: unknown sample format '%s'\n", - aufmt_name(st->aufmt)); + aufmt_name(prm->fmt)); err = EINVAL; goto out; } diff --git a/modules/alsa/alsa_src.c b/modules/alsa/alsa_src.c index 6e850a7..a21f846 100644 --- a/modules/alsa/alsa_src.c +++ b/modules/alsa/alsa_src.c @@ -22,14 +22,12 @@ struct ausrc_st { pthread_t thread; bool run; snd_pcm_t *read; - int16_t *sampv; - void *xsampv; + void *sampv; size_t sampc; ausrc_read_h *rh; void *arg; struct ausrc_prm prm; char *device; - enum aufmt aufmt; }; @@ -48,7 +46,6 @@ static void ausrc_destructor(void *arg) snd_pcm_close(st->read); mem_deref(st->sampv); - mem_deref(st->xsampv); mem_deref(st->device); } @@ -73,10 +70,7 @@ static void *read_thread(void *arg) size_t sampc; void *sampv; - if (st->aufmt == AUFMT_S16LE) - sampv = st->sampv; - else - sampv = st->xsampv; + sampv = st->sampv; err = snd_pcm_readi(st->read, sampv, num_frames); if (err == -EPIPE) { @@ -89,11 +83,6 @@ static void *read_thread(void *arg) sampc = err * st->prm.ch; - if (st->aufmt != AUFMT_S16LE) { - auconv_to_s16(st->sampv, st->aufmt, - st->xsampv, sampc); - } - st->rh(st->sampv, sampc, st->arg); } @@ -132,26 +121,16 @@ int alsa_src_alloc(struct ausrc_st **stp, const struct ausrc *as, st->as = as; st->rh = rh; st->arg = arg; - st->aufmt = alsa_sample_format; st->sampc = prm->srate * prm->ch * prm->ptime / 1000; num_frames = st->prm.srate * st->prm.ptime / 1000; - st->sampv = mem_alloc(2 * st->sampc, NULL); + st->sampv = mem_alloc(aufmt_sample_size(prm->fmt) * st->sampc, NULL); if (!st->sampv) { err = ENOMEM; goto out; } - if (st->aufmt != AUFMT_S16LE) { - size_t sz = aufmt_sample_size(st->aufmt) * st->sampc; - st->xsampv = mem_alloc(sz, NULL); - if (!st->xsampv) { - err = ENOMEM; - goto out; - } - } - err = snd_pcm_open(&st->read, st->device, SND_PCM_STREAM_CAPTURE, 0); if (err < 0) { warning("alsa: could not open ausrc device '%s' (%s)\n", @@ -159,10 +138,10 @@ int alsa_src_alloc(struct ausrc_st **stp, const struct ausrc *as, goto out; } - pcmfmt = aufmt_to_alsaformat(st->aufmt); + pcmfmt = aufmt_to_alsaformat(prm->fmt); if (pcmfmt == SND_PCM_FORMAT_UNKNOWN) { warning("alsa: unknown sample format '%s'\n", - aufmt_name(st->aufmt)); + aufmt_name(prm->fmt)); err = EINVAL; goto out; } @@ -182,7 +161,8 @@ int alsa_src_alloc(struct ausrc_st **stp, const struct ausrc *as, goto out; } - debug("alsa: recording started (%s)\n", st->device); + debug("alsa: recording started (%s) format=%s\n", + st->device, aufmt_name(prm->fmt)); out: if (err) diff --git a/modules/aubridge/play.c b/modules/aubridge/play.c index 37c04fa..ff0b98f 100644 --- a/modules/aubridge/play.c +++ b/modules/aubridge/play.c @@ -4,6 +4,7 @@ * Copyright (C) 2010 Creytiv.com */ #include <re.h> +#include <rem.h> #include <baresip.h> #include "aubridge.h" @@ -28,6 +29,12 @@ int play_alloc(struct auplay_st **stp, const struct auplay *ap, if (!stp || !ap || !prm) return EINVAL; + if (prm->fmt != AUFMT_S16LE) { + warning("aubridge: playback: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + st = mem_zalloc(sizeof(*st), auplay_destructor); if (!st) return ENOMEM; diff --git a/modules/aubridge/src.c b/modules/aubridge/src.c index 22ce13a..87fd64a 100644 --- a/modules/aubridge/src.c +++ b/modules/aubridge/src.c @@ -4,6 +4,7 @@ * Copyright (C) 2010 Creytiv.com */ #include <re.h> +#include <rem.h> #include <baresip.h> #include "aubridge.h" @@ -31,6 +32,12 @@ int src_alloc(struct ausrc_st **stp, const struct ausrc *as, if (!stp || !as || !prm) return EINVAL; + if (prm->fmt != AUFMT_S16LE) { + warning("aubridge: source: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + st = mem_zalloc(sizeof(*st), ausrc_destructor); if (!st) return ENOMEM; diff --git a/modules/audiounit/player.c b/modules/audiounit/player.c index 372f139..230b1ad 100644 --- a/modules/audiounit/player.c +++ b/modules/audiounit/player.c @@ -7,6 +7,7 @@ #include <AudioToolbox/AudioToolbox.h> #include <pthread.h> #include <re.h> +#include <rem.h> #include <baresip.h> #include "audiounit.h" @@ -18,6 +19,7 @@ struct auplay_st { pthread_mutex_t mutex; auplay_write_h *wh; void *arg; + uint32_t sampsz; }; @@ -68,7 +70,7 @@ static OSStatus output_callback(void *inRefCon, AudioBuffer *ab = &ioData->mBuffers[i]; - wh(ab->mData, ab->mDataByteSize/2, arg); + wh(ab->mData, ab->mDataByteSize/st->sampsz, arg); } return 0; @@ -86,6 +88,17 @@ static void interrupt_handler(bool interrupted, void *arg) } +static uint32_t aufmt_to_formatflags(enum aufmt fmt) +{ + switch (fmt) { + + case AUFMT_S16LE: return kLinearPCMFormatFlagIsSignedInteger; + case AUFMT_FLOAT: return kLinearPCMFormatFlagIsFloat; + default: return 0; + } +} + + int audiounit_player_alloc(struct auplay_st **stp, const struct auplay *ap, struct auplay_prm *prm, const char *device, auplay_write_h *wh, void *arg) @@ -130,21 +143,23 @@ int audiounit_player_alloc(struct auplay_st **stp, const struct auplay *ap, goto out; } + st->sampsz = (uint32_t)aufmt_sample_size(prm->fmt); + fmt.mSampleRate = prm->srate; fmt.mFormatID = kAudioFormatLinearPCM; #if TARGET_OS_IPHONE - fmt.mFormatFlags = kAudioFormatFlagIsSignedInteger + fmt.mFormatFlags = aufmt_to_formatflags(prm->fmt) | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; #else - fmt.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger - | kLinearPCMFormatFlagIsPacked; + fmt.mFormatFlags = aufmt_to_formatflags(prm->fmt) + | kAudioFormatFlagIsPacked; #endif - fmt.mBitsPerChannel = 16; + fmt.mBitsPerChannel = 8 * st->sampsz; fmt.mChannelsPerFrame = prm->ch; - fmt.mBytesPerFrame = 2 * prm->ch; + fmt.mBytesPerFrame = st->sampsz * prm->ch; fmt.mFramesPerPacket = 1; - fmt.mBytesPerPacket = 2 * prm->ch; + fmt.mBytesPerPacket = st->sampsz * prm->ch; ret = AudioUnitInitialize(st->au); if (ret) diff --git a/modules/audiounit/recorder.c b/modules/audiounit/recorder.c index cf6a1af..b66ba07 100644 --- a/modules/audiounit/recorder.c +++ b/modules/audiounit/recorder.c @@ -8,6 +8,7 @@ #include <TargetConditionals.h> #include <pthread.h> #include <re.h> +#include <rem.h> #include <baresip.h> #include "audiounit.h" @@ -20,6 +21,7 @@ struct ausrc_st { int ch; ausrc_read_h *rh; void *arg; + uint32_t sampsz; }; @@ -67,7 +69,7 @@ static OSStatus input_callback(void *inRefCon, abl.mNumberBuffers = 1; abl.mBuffers[0].mNumberChannels = st->ch; abl.mBuffers[0].mData = NULL; - abl.mBuffers[0].mDataByteSize = inNumberFrames * 2; + abl.mBuffers[0].mDataByteSize = inNumberFrames * st->sampsz; ret = AudioUnitRender(st->au, ioActionFlags, @@ -80,7 +82,8 @@ static OSStatus input_callback(void *inRefCon, return ret; } - rh(abl.mBuffers[0].mData, abl.mBuffers[0].mDataByteSize/2, arg); + rh(abl.mBuffers[0].mData, + abl.mBuffers[0].mDataByteSize/st->sampsz, arg); return 0; } @@ -97,6 +100,17 @@ static void interrupt_handler(bool interrupted, void *arg) } +static uint32_t aufmt_to_formatflags(enum aufmt fmt) +{ + switch (fmt) { + + case AUFMT_S16LE: return kLinearPCMFormatFlagIsSignedInteger; + case AUFMT_FLOAT: return kLinearPCMFormatFlagIsFloat; + default: return 0; + } +} + + int audiounit_recorder_alloc(struct ausrc_st **stp, const struct ausrc *as, struct media_ctx **ctx, struct ausrc_prm *prm, const char *device, @@ -178,21 +192,23 @@ int audiounit_recorder_alloc(struct ausrc_st **stp, const struct ausrc *as, goto out; #endif + st->sampsz = (uint32_t)aufmt_sample_size(prm->fmt); + fmt.mSampleRate = prm->srate; fmt.mFormatID = kAudioFormatLinearPCM; #if TARGET_OS_IPHONE - fmt.mFormatFlags = kAudioFormatFlagIsSignedInteger + fmt.mFormatFlags = aufmt_to_formatflags(prm->fmt) | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; #else - fmt.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger + fmt.mFormatFlags = aufmt_to_formatflags(prm->fmt) | kLinearPCMFormatFlagIsPacked; #endif - fmt.mBitsPerChannel = 16; + fmt.mBitsPerChannel = 8 * st->sampsz; fmt.mChannelsPerFrame = prm->ch; - fmt.mBytesPerFrame = 2 * prm->ch; + fmt.mBytesPerFrame = st->sampsz * prm->ch; fmt.mFramesPerPacket = 1; - fmt.mBytesPerPacket = 2 * prm->ch; + fmt.mBytesPerPacket = st->sampsz * prm->ch; fmt.mReserved = 0; ret = AudioUnitSetProperty(st->au, kAudioUnitProperty_StreamFormat, diff --git a/modules/aufile/aufile.c b/modules/aufile/aufile.c index 3f9c77f..307aee1 100644 --- a/modules/aufile/aufile.c +++ b/modules/aufile/aufile.c @@ -159,6 +159,12 @@ static int alloc_handler(struct ausrc_st **stp, const struct ausrc *as, if (!stp || !as || !prm || !rh) return EINVAL; + if (prm->fmt != AUFMT_S16LE) { + warning("aufile: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + info("aufile: loading input file '%s'\n", dev); st = mem_zalloc(sizeof(*st), destructor); diff --git a/modules/auloop/auloop.c b/modules/auloop/auloop.c index 22dd1b0..8324a88 100644 --- a/modules/auloop/auloop.c +++ b/modules/auloop/auloop.c @@ -44,6 +44,7 @@ struct audio_loop { struct tmr tmr; uint32_t srate; uint32_t ch; + enum aufmt fmt; uint32_t n_read; uint32_t n_write; @@ -90,13 +91,15 @@ static void print_stats(struct audio_loop *al) if (al->n_write) rw_ratio = 1.0 * al->n_read / al->n_write; - (void)re_fprintf(stderr, "\r%uHz %dch " + (void)re_fprintf(stdout, "\r%uHz %dch %s " " n_read=%u n_write=%u rw_ratio=%.2f", - al->srate, al->ch, + al->srate, al->ch, aufmt_name(al->fmt), al->n_read, al->n_write, rw_ratio); if (str_isset(aucodec)) - (void)re_fprintf(stderr, " codec='%s'", aucodec); + (void)re_fprintf(stdout, " codec='%s'", aucodec); + + fflush(stdout); } @@ -136,23 +139,25 @@ static int codec_read(struct audio_loop *al, int16_t *sampv, size_t sampc) } -static void read_handler(const int16_t *sampv, size_t sampc, void *arg) +static void read_handler(const void *sampv, size_t sampc, void *arg) { struct audio_loop *al = arg; + size_t num_bytes = sampc * aufmt_sample_size(al->fmt); int err; ++al->n_read; - err = aubuf_write_samp(al->ab, sampv, sampc); + err = aubuf_write(al->ab, sampv, num_bytes); if (err) { warning("auloop: aubuf_write: %m\n", err); } } -static void write_handler(int16_t *sampv, size_t sampc, void *arg) +static void write_handler(void *sampv, size_t sampc, void *arg) { struct audio_loop *al = arg; + size_t num_bytes = sampc * aufmt_sample_size(al->fmt); int err; ++al->n_write; @@ -166,7 +171,7 @@ static void write_handler(int16_t *sampv, size_t sampc, void *arg) } } else { - aubuf_read_samp(al->ab, sampv, sampc); + aubuf_read(al->ab, sampv, num_bytes); } } @@ -218,9 +223,23 @@ static int auloop_reset(struct audio_loop *al) if (!cfg) return ENOENT; + if (cfg->audio.src_fmt != cfg->audio.play_fmt) { + warning("auloop: ausrc_format and auplay_format" + " must be the same\n"); + return EINVAL; + } + + al->fmt = cfg->audio.src_fmt; + /* Optional audio codec */ - if (str_isset(aucodec)) + if (str_isset(aucodec)) { + if (cfg->audio.src_fmt != AUFMT_S16LE) { + warning("auloop: only s16 supported with codec\n"); + return EINVAL; + } + start_codec(al, aucodec); + } /* audio player/source must be stopped first */ al->auplay = mem_deref(al->auplay); @@ -248,6 +267,7 @@ static int auloop_reset(struct audio_loop *al) auplay_prm.srate = al->srate; auplay_prm.ch = al->ch; auplay_prm.ptime = PTIME; + auplay_prm.fmt = al->fmt; err = auplay_alloc(&al->auplay, baresip_auplayl(), cfg->audio.play_mod, &auplay_prm, cfg->audio.play_dev, write_handler, al); @@ -261,6 +281,7 @@ static int auloop_reset(struct audio_loop *al) ausrc_prm.srate = al->srate; ausrc_prm.ch = al->ch; ausrc_prm.ptime = PTIME; + ausrc_prm.fmt = al->fmt; err = ausrc_alloc(&al->ausrc, baresip_ausrcl(), NULL, cfg->audio.src_mod, &ausrc_prm, cfg->audio.src_dev, diff --git a/modules/avahi/avahi.c b/modules/avahi/avahi.c index 500610f..035d51a 100644 --- a/modules/avahi/avahi.c +++ b/modules/avahi/avahi.c @@ -250,6 +250,8 @@ static void resolve_callback( warning("avahi: Resolver Error on %s: %s\n", name, avahi_strerror(avahi_client_errno(avahi->client))); } + + avahi_service_resolver_free(r); } diff --git a/modules/avcodec/encode.c b/modules/avcodec/encode.c index 11d30a0..d0d5d83 100644 --- a/modules/avcodec/encode.c +++ b/modules/avcodec/encode.c @@ -429,7 +429,7 @@ static int open_encoder_x264(struct videnc_state *st, struct videnc_param *prm, xprm.i_fps_num = prm->fps; xprm.i_fps_den = 1; xprm.rc.i_bitrate = prm->bitrate / 1000; /* kbit/s */ - xprm.rc.i_rc_method = X264_RC_CQP; + xprm.rc.i_rc_method = X264_RC_ABR; xprm.i_log_level = X264_LOG_WARNING; /* ultrafast preset */ @@ -616,7 +616,7 @@ int encode_x264(struct videnc_state *st, bool update, ret = x264_encoder_encode(st->x264, &nal, &i_nal, &pic_in, &pic_out); if (ret < 0) { - fprintf(stderr, "x264 [error]: x264_encoder_encode failed\n"); + warning("avcodec: x264 [error]: x264_encoder_encode failed\n"); } if (i_nal == 0) return 0; diff --git a/modules/avformat/avformat.c b/modules/avformat/avformat.c index c0338a2..68ef088 100644 --- a/modules/avformat/avformat.c +++ b/modules/avformat/avformat.c @@ -252,6 +252,8 @@ static int alloc(struct vidsrc_st **stp, const struct vidsrc *vs, if (!stp || !vs || !prm || !size || !frameh) return EINVAL; + debug("avformat: alloc dev='%s'\n", dev); + st = mem_zalloc(sizeof(*st), destructor); if (!st) return ENOMEM; @@ -270,6 +272,12 @@ static int alloc(struct vidsrc_st **stp, const struct vidsrc *vs, #if LIBAVFORMAT_VERSION_INT >= ((52<<16) + (110<<8) + 0) (void)fmt; ret = avformat_open_input(&st->ic, dev, NULL, NULL); + if (ret < 0) { + warning("avformat: avformat_open_input(%s) failed (ret=%d)\n", + dev, ret); + err = ENOENT; + goto out; + } #else /* Params */ @@ -284,12 +292,14 @@ static int alloc(struct vidsrc_st **stp, const struct vidsrc *vs, ret = av_open_input_file(&st->ic, dev, av_find_input_format(fmt), 0, &prms); -#endif - if (ret < 0) { + warning("avformat: av_open_input_file(%s) failed (ret=%d)\n", + dev, ret); err = ENOENT; goto out; } +#endif + #if LIBAVFORMAT_VERSION_INT >= ((53<<16) + (4<<8) + 0) ret = avformat_find_stream_info(st->ic, NULL); diff --git a/modules/coreaudio/player.c b/modules/coreaudio/player.c index 16cfa33..7d247cd 100644 --- a/modules/coreaudio/player.c +++ b/modules/coreaudio/player.c @@ -88,6 +88,9 @@ int coreaudio_player_alloc(struct auplay_st **stp, const struct auplay *ap, (void)device; + if (!stp || !ap || !prm || prm->fmt != AUFMT_S16LE) + return EINVAL; + st = mem_zalloc(sizeof(*st), auplay_destructor); if (!st) return ENOMEM; diff --git a/modules/coreaudio/recorder.c b/modules/coreaudio/recorder.c index c913485..9ceae60 100644 --- a/modules/coreaudio/recorder.c +++ b/modules/coreaudio/recorder.c @@ -102,7 +102,7 @@ int coreaudio_recorder_alloc(struct ausrc_st **stp, const struct ausrc *as, (void)device; (void)errh; - if (!stp || !as || !prm) + if (!stp || !as || !prm || prm->fmt != AUFMT_S16LE) return EINVAL; st = mem_zalloc(sizeof(*st), ausrc_destructor); diff --git a/modules/debug_cmd/debug_cmd.c b/modules/debug_cmd/debug_cmd.c index 56c1a8f..faee4ee 100644 --- a/modules/debug_cmd/debug_cmd.c +++ b/modules/debug_cmd/debug_cmd.c @@ -98,6 +98,27 @@ static int cmd_play_file(struct re_printf *pf, void *arg) } +static int reload_config(struct re_printf *pf, void *arg) +{ + int err; + (void)arg; + + err = re_hprintf(pf, "reloading config file ..\n"); + if (err) + return err; + + err = conf_configure(); + if (err) { + (void)re_hprintf(pf, "reload_config failed: %m\n", err); + return err; + } + + (void)re_hprintf(pf, "done\n"); + + return 0; +} + + static const struct cmd debugcmdv[] = { {"main", 0, 0, "Main loop debug", re_debug }, {"config", 0, 0, "Print configuration", cmd_config_print }, @@ -109,6 +130,7 @@ static const struct cmd debugcmdv[] = { {"uastat", 'u', 0, "UA debug", cmd_ua_debug }, {"memstat", 'y', 0, "Memory status", mem_status }, {"play", 0, CMD_PRM, "Play audio file", cmd_play_file }, +{"conf_reload",0, 0, "Reload config file", reload_config }, }; diff --git a/modules/gst/gst.c b/modules/gst/gst.c index dc00dd4..d729b70 100644 --- a/modules/gst/gst.c +++ b/modules/gst/gst.c @@ -376,6 +376,8 @@ static int gst_alloc(struct ausrc_st **stp, const struct ausrc *as, if (!prm) return EINVAL; + if (prm->fmt != AUFMT_S16LE) + return ENOTSUP; st = mem_zalloc(sizeof(*st), gst_destructor); if (!st) diff --git a/modules/gst1/gst.c b/modules/gst1/gst.c index 6f704e5..380334a 100644 --- a/modules/gst1/gst.c +++ b/modules/gst1/gst.c @@ -3,9 +3,9 @@ * * Copyright (C) 2010 - 2015 Creytiv.com */ +#define _DEFAULT_SOURCE 1 #include <stdlib.h> #include <string.h> -#define __USE_POSIX199309 #include <time.h> #include <pthread.h> #include <gst/gst.h> @@ -383,6 +383,12 @@ static int gst_alloc(struct ausrc_st **stp, const struct ausrc *as, if (!prm) return EINVAL; + if (prm->fmt != AUFMT_S16LE) { + warning("gst: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + st = mem_zalloc(sizeof(*st), gst_destructor); if (!st) return ENOMEM; diff --git a/modules/gzrtp/gzrtp.cpp b/modules/gzrtp/gzrtp.cpp new file mode 100644 index 0000000..19aaf9d --- /dev/null +++ b/modules/gzrtp/gzrtp.cpp @@ -0,0 +1,220 @@ +/** + * @file gzrtp.cpp GNU ZRTP: Media Path Key Agreement for Unicast Secure RTP + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#include <stdint.h> + +#include <re.h> +#include <baresip.h> + +#include <string.h> + +#include <libzrtpcpp/ZRtp.h> + +#include "session.h" +#include "stream.h" + + +/** + * @defgroup gzrtp gzrtp + * + * ZRTP: Media Path Key Agreement for Unicast Secure RTP + * + * Experimental support for ZRTP + * + * See http://tools.ietf.org/html/rfc6189 + * + * + * This module is using GNU ZRTP C++ library + * + * https://github.com/wernerd/ZRTPCPP + * + * Configuration options: + * + \verbatim + zrtp_parallel {yes,no} # Start all streams at once + \endverbatim + * + */ + + +static ZRTPConfig *s_zrtp_config = NULL; + + +struct menc_sess { + Session *session; +}; + + +struct menc_media { + Stream *stream; +}; + + +static void session_destructor(void *arg) +{ + struct menc_sess *st = (struct menc_sess *)arg; + + delete st->session; +} + + +static void media_destructor(void *arg) +{ + struct menc_media *st = (struct menc_media *)arg; + + delete st->stream; +} + + +static int session_alloc(struct menc_sess **sessp, struct sdp_session *sdp, + bool offerer, menc_error_h *errorh, void *arg) +{ + struct menc_sess *st; + (void)offerer; + (void)errorh; + (void)arg; + int err = 0; + + if (!sessp || !sdp) + return EINVAL; + + st = (struct menc_sess *)mem_zalloc(sizeof(*st), session_destructor); + if (!st) + return ENOMEM; + + st->session = new Session(*s_zrtp_config); + if (!st->session) + err = ENOMEM; + + if (err) + mem_deref(st); + else + *sessp = st; + + return err; +} + + +static int media_alloc(struct menc_media **stp, struct menc_sess *sess, + struct rtp_sock *rtp, + int proto, void *rtpsock, void *rtcpsock, + struct sdp_media *sdpm) +{ + struct menc_media *st; + int err = 0; + StreamMediaType med_type; + const char *med_name; + + if (!stp || !sess || !sess->session || proto != IPPROTO_UDP) + return EINVAL; + + st = *stp; + if (st) + goto start; + + st = (struct menc_media *)mem_zalloc(sizeof(*st), media_destructor); + if (!st) + return ENOMEM; + + med_name = sdp_media_name(sdpm); + if (str_cmp(med_name, "audio") == 0) + med_type = MT_AUDIO; + else if (str_cmp(med_name, "video") == 0) + med_type = MT_VIDEO; + else if (str_cmp(med_name, "text") == 0) + med_type = MT_TEXT; + else if (str_cmp(med_name, "application") == 0) + med_type = MT_APPLICATION; + else if (str_cmp(med_name, "message") == 0) + med_type = MT_MESSAGE; + else + med_type = MT_UNKNOWN; + + st->stream = sess->session->create_stream( + *s_zrtp_config, + (struct udp_sock *)rtpsock, + (struct udp_sock *)rtcpsock, + rtp_sess_ssrc(rtp), med_type); + if (!st->stream) { + err = ENOMEM; + goto out; + } + + st->stream->sdp_encode(sdpm); + + out: + if (err) { + mem_deref(st); + return err; + } + else + *stp = st; + + start: + if (sa_isset(sdp_media_raddr(sdpm), SA_ALL)) { + st->stream->sdp_decode(sdpm); + err = sess->session->start_stream(st->stream); + if (err) { + warning("zrtp: stream start failed: %d\n", err); + } + } + + return err; +} + + +static struct menc menc_zrtp = { + LE_INIT, "zrtp", "RTP/AVP", session_alloc, media_alloc +}; + + +static const struct cmd cmdv[] = { + {"zrtp_verify", 0, CMD_PRM, "Verify ZRTP SAS <session ID>", + Session::cmd_verify_sas }, + {"zrtp_unverify", 0, CMD_PRM, "Unverify ZRTP SAS <session ID>", + Session::cmd_unverify_sas }, +}; + + +static int module_init(void) +{ + char config_path[256]; + int err = 0; + + err = conf_path_get(config_path, sizeof(config_path)); + if (err) { + warning("zrtp: could not get config path: %m\n", err); + return err; + } + + s_zrtp_config = new ZRTPConfig(conf_cur(), config_path); + if (!s_zrtp_config) + return ENOMEM; + + menc_register(baresip_mencl(), &menc_zrtp); + + return cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv)); +} + + +static int module_close(void) +{ + delete s_zrtp_config; + s_zrtp_config = NULL; + + cmd_unregister(baresip_commands(), cmdv); + + menc_unregister(&menc_zrtp); + + return 0; +} + + +extern "C" EXPORT_SYM const struct mod_export DECL_EXPORTS(gzrtp) = { + "gzrtp", + "menc", + module_init, + module_close +}; diff --git a/modules/gzrtp/messages.cpp b/modules/gzrtp/messages.cpp new file mode 100644 index 0000000..db3a7db --- /dev/null +++ b/modules/gzrtp/messages.cpp @@ -0,0 +1,256 @@ +/** + * @file messages.cpp GNU ZRTP: Engine messages + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#include <stdint.h> + +#include <re.h> +#include <baresip.h> + +#include <libzrtpcpp/ZRtp.h> + +#include "stream.h" + + +using namespace GnuZrtpCodes; + + +#define NO_MESSAGE "NO MESSAGE DEFINED" + + +static const char *info_msg(int32_t subcode) +{ + const char *msg; + + switch (subcode) { + case InfoHelloReceived: + msg = "Hello received and prepared a Commit, " + "ready to get peer's hello hash"; + break; + case InfoCommitDHGenerated: + msg = "Commit: Generated a public DH key"; + break; + case InfoRespCommitReceived: + msg = "Responder: Commit received, preparing DHPart1"; + break; + case InfoDH1DHGenerated: + msg = "DH1Part: Generated a public DH key"; + break; + case InfoInitDH1Received: + msg = "Initiator: DHPart1 received, preparing DHPart2"; + break; + case InfoRespDH2Received: + msg = "Responder: DHPart2 received, preparing Confirm1"; + break; + case InfoInitConf1Received: + msg = "Initiator: Confirm1 received, preparing Confirm2"; + break; + case InfoRespConf2Received: + msg = "Responder: Confirm2 received, preparing Conf2Ack"; + break; + case InfoRSMatchFound: + msg = "At least one retained secret matches - security OK"; + break; + case InfoSecureStateOn: + msg = "Entered secure state"; + break; + case InfoSecureStateOff: + msg = "No more security for this session"; + break; + default: + msg = NO_MESSAGE; + break; + } + + return msg; +} + + +static const char *warning_msg(int32_t subcode) +{ + const char *msg; + + switch (subcode) { + case WarningDHAESmismatch: + msg = "Commit contains an AES256 cipher but does not offer a " + "Diffie-Helman 4096 - not used DH4096 was discarded"; + break; + case WarningGoClearReceived: + msg = "Received a GoClear message"; + break; + case WarningDHShort: + msg = "Hello offers an AES256 cipher but does not offer a " + "Diffie-Helman 4096- not used DH4096 was discarded"; + break; + case WarningNoRSMatch: + msg = "No retained shared secrets available - must verify SAS"; + break; + case WarningCRCmismatch: + msg = "Internal ZRTP packet checksum mismatch - " + "packet dropped"; + break; + case WarningSRTPauthError: + msg = "Dropping packet because SRTP authentication failed!"; + break; + case WarningSRTPreplayError: + msg = "Dropping packet because SRTP replay check failed!"; + break; + case WarningNoExpectedRSMatch: + msg = "Valid retained shared secrets availabe but no matches " + "found - must verify SAS"; + break; + case WarningNoExpectedAuxMatch: + msg = "Our AUX secret was set but the other peer's AUX secret " + "does not match ours"; + break; + default: + msg = NO_MESSAGE; + break; + } + + return msg; +} + + +static const char *severe_msg(int32_t subcode) +{ + const char *msg; + + switch (subcode) { + case SevereHelloHMACFailed: + msg = "Hash HMAC check of Hello failed!"; + break; + case SevereCommitHMACFailed: + msg = "Hash HMAC check of Commit failed!"; + break; + case SevereDH1HMACFailed: + msg = "Hash HMAC check of DHPart1 failed!"; + break; + case SevereDH2HMACFailed: + msg = "Hash HMAC check of DHPart2 failed!"; + break; + case SevereCannotSend: + msg = "Cannot send data - connection or peer down?"; + break; + case SevereProtocolError: + msg = "Internal protocol error occured!"; + break; + case SevereNoTimer: + msg = "Cannot start a timer - internal resources exhausted?"; + break; + case SevereTooMuchRetries: + msg = "Too much retries during ZRTP negotiation - connection " + "or peer down?"; + break; + default: + msg = NO_MESSAGE; + break; + } + + return msg; +} + + +static const char *zrtp_msg(int32_t subcode) +{ + const char *msg; + + switch (subcode) { + case MalformedPacket: + msg = "Malformed packet (CRC OK, but wrong structure)"; + break; + case CriticalSWError: + msg = "Critical software error"; + break; + case UnsuppZRTPVersion: + msg = "Unsupported ZRTP version"; + break; + case HelloCompMismatch: + msg = "Hello components mismatch"; + break; + case UnsuppHashType: + msg = "Hash type not supported"; + break; + case UnsuppCiphertype: + msg = "Cipher type not supported"; + break; + case UnsuppPKExchange: + msg = "Public key exchange not supported"; + break; + case UnsuppSRTPAuthTag: + msg = "SRTP auth. tag not supported"; + break; + case UnsuppSASScheme: + msg = "SAS scheme not supported"; + break; + case NoSharedSecret: + msg = "No shared secret available, DH mode required"; + break; + case DHErrorWrongPV: + msg = "DH Error: bad pvi or pvr ( == 1, 0, or p-1)"; + break; + case DHErrorWrongHVI: + msg = "DH Error: hvi != hashed data"; + break; + case SASuntrustedMiTM: + msg = "Received relayed SAS from untrusted MiTM"; + break; + case ConfirmHMACWrong: + msg = "Auth. Error: Bad Confirm pkt HMAC"; + break; + case NonceReused: + msg = "Nonce reuse"; + break; + case EqualZIDHello: + msg = "Equal ZIDs in Hello"; + break; + case GoCleatNotAllowed: + msg = "GoClear packet received, but not allowed"; + break; + default: + msg = NO_MESSAGE; + break; + } + + return msg; +} + + +void Stream::print_message(GnuZrtpCodes::MessageSeverity severity, + int32_t subcode) +{ + switch (severity) { + case Info: + debug("zrtp: INFO<%s>: %s\n", + media_name(), info_msg(subcode)); + break; + case Warning: + warning("zrtp: WARNING<%s>: %s\n", + media_name(), warning_msg(subcode)); + break; + case Severe: + warning("zrtp: SEVERE<%s>: %s\n", + media_name(), severe_msg(subcode)); + break; + case ZrtpError: + warning("zrtp: ZRTP_ERR<%s>: %s\n", + media_name(), zrtp_msg(subcode)); + break; + default: + return; + } +} + + +const char *Stream::media_name() const +{ + switch (m_media_type) { + case MT_AUDIO: return "audio"; + case MT_VIDEO: return "video"; + case MT_TEXT: return "text"; + case MT_APPLICATION: return "application"; + case MT_MESSAGE: return "message"; + default: return "UNKNOWN"; + } +} diff --git a/modules/gzrtp/module.mk b/modules/gzrtp/module.mk new file mode 100644 index 0000000..42e408e --- /dev/null +++ b/modules/gzrtp/module.mk @@ -0,0 +1,38 @@ +# +# module.mk +# +# Copyright (C) 2010 - 2017 Creytiv.com +# + +# +# To build libzrtpcppcore run the following commands: +# +# git clone https://github.com/wernerd/ZRTPCPP.git +# cd ZRTPCPP +# mkdir build +# cd build +# cmake -DCMAKE_POSITION_INDEPENDENT_CODE=1 -DCORE_LIB=1 -DSDES=1 \ +# -DBUILD_STATIC=1 .. +# make +# + +# GNU ZRTP C++ library (ZRTPCPP) source directory +ZRTP_PATH ?= ../ZRTPCPP + +ZRTP_LIB := $(shell find $(ZRTP_PATH) -name libzrtpcppcore.a) + +MOD := gzrtp +$(MOD)_SRCS += gzrtp.cpp session.cpp stream.cpp messages.cpp srtp.cpp +$(MOD)_LFLAGS += $(ZRTP_LIB) -lstdc++ +$(MOD)_CXXFLAGS += \ + -I$(ZRTP_PATH) \ + -I$(ZRTP_PATH)/zrtp \ + -I$(ZRTP_PATH)/srtp + +$(MOD)_CXXFLAGS += -O2 -Wall -fPIC + +# Uncomment this if you want to use libre SRTP facilities instead of the ones +# provided by ZRTPCPP. In this case only standard ciphers (AES) are supported. +#$(MOD)_CXXFLAGS += -DGZRTP_USE_RE_SRTP=1 + +include mk/mod.mk diff --git a/modules/gzrtp/session.cpp b/modules/gzrtp/session.cpp new file mode 100644 index 0000000..422bee4 --- /dev/null +++ b/modules/gzrtp/session.cpp @@ -0,0 +1,214 @@ +/** + * @file session.h GNU ZRTP: Session class implementation + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#include <stdint.h> + +#include <re.h> +#include <baresip.h> + +#include "session.h" + + +std::vector<Session *> Session::s_sessl; + + +Session::Session(const ZRTPConfig& config) + : m_start_parallel(config.start_parallel) + , m_master(NULL) + , m_encrypted(0) +{ + int newid = 1; + for (std::vector<Session *>::iterator it = s_sessl.begin(); + it != s_sessl.end(); ++it) { + + if ((*it)->id() >= newid) + newid = (*it)->id() + 1; + } + + m_id = newid; + + s_sessl.push_back(this); + + debug("zrtp: New session <%d>\n", id()); +} + + +Session::~Session() +{ + for (std::vector<Session *>::iterator it = s_sessl.begin(); + it != s_sessl.end(); ++it) { + + if (*it == this) { + s_sessl.erase(it); + break; + } + } + + debug("zrtp: Session <%d> is destroyed\n", id()); +} + + +Stream *Session::create_stream(const ZRTPConfig& config, + udp_sock *rtpsock, + udp_sock *rtcpsock, + uint32_t local_ssrc, + StreamMediaType media_type) +{ + int err = 0; + + Stream *st = new Stream (err, config, this, rtpsock, rtcpsock, + local_ssrc, media_type); + if (!st || err) { + delete st; + return NULL; + } + + return st; +} + + +int Session::start_stream(Stream *stream) +{ + if (stream->started()) + return 0; + + m_streams.push_back(stream); + + // Start all streams in parallel using DH mode. This is a kind of + // probing. The first stream to receive HelloACK will be the master + // stream. If disabled, only the first stream starts in DH (master) + // mode. + if (m_start_parallel) { + if (m_master && m_encrypted) + // If we already have a master in secure state, + // start in multistream mode + return stream->start(m_master); + else + // Start a new stream in DH mode + return stream->start(NULL); + } + else { + if (!m_master) { + // Start the first stream in DH mode + m_master = stream; + return stream->start(NULL); + } + else if (m_encrypted) { + // Master is in secure state; multistream + return stream->start(m_master); + } + } + + return 0; +} + + +bool Session::request_master(Stream *stream) +{ + if (!m_start_parallel) + return true; + + if (m_master) + return false; + + // This is the first stream to receive HelloACK. It will be + // used as the master for the other streams in the session. + m_master = stream; + // Stop other DH-mode streams. They will be started in the + // multistream mode after the master enters secure state. + for (std::vector<Stream *>::iterator it = m_streams.begin(); + it != m_streams.end(); ++it) { + + if (*it != m_master) { + (*it)->stop(); + } + } + + return true; +} + + +void Session::on_secure(Stream *stream) +{ + ++m_encrypted; + + if (m_encrypted == m_streams.size() && m_master) { + info("zrtp: All streams are encrypted (%s), " + "SAS is [%s] (%s)\n", + m_master->get_ciphers(), + m_master->get_sas(), + (m_master->sas_verified())? "verified" : "NOT VERIFIED"); + return; + } + + if (stream != m_master) + return; + + // Master stream has just entered secure state. Start other + // streams in the multistream mode. + + debug("zrtp: Starting other streams (%d)\n", m_streams.size() - 1); + + for (std::vector<Stream *>::iterator it = m_streams.begin(); + it != m_streams.end(); ++it) { + + if (*it != m_master) { + (*it)->start(m_master); + } + } +} + + +int Session::cmd_verify_sas(struct re_printf *pf, void *arg) +{ + return cmd_sas(true, pf, arg); +} + + +int Session::cmd_unverify_sas(struct re_printf *pf, void *arg) +{ + return cmd_sas(false, pf, arg); +} + + +int Session::cmd_sas(bool verify, struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = (struct cmd_arg *)arg; + (void)pf; + int id = -1; + Session *sess = NULL; + + if (str_isset(carg->prm)) + id = atoi(carg->prm); + + for (std::vector<Session *>::iterator it = s_sessl.begin(); + it != s_sessl.end(); ++it) { + + if ((*it)->id() == id) { + sess = *it; + break; + } + } + + if (!sess) { + warning("zrtp: No session with id %d\n", id); + return EINVAL; + } + + if (!sess->m_master) { + warning("zrtp: No master stream for the session with id %d\n", + sess->id()); + return EFAULT; + } + + sess->m_master->verify_sas(verify); + + info("zrtp: Session <%d>: SAS [%s] is %s\n", sess->id(), + sess->m_master->get_sas(), + (sess->m_master->sas_verified())? "verified" : "NOT VERIFIED"); + + return 0; +} + diff --git a/modules/gzrtp/session.h b/modules/gzrtp/session.h new file mode 100644 index 0000000..90c12d6 --- /dev/null +++ b/modules/gzrtp/session.h @@ -0,0 +1,50 @@ +/** + * @file session.h GNU ZRTP: Session class + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#ifndef __SESSION_H +#define __SESSION_H + + +#include "stream.h" + + +class Stream; +class ZRTPConfig; + +class Session { +public: + Session(const ZRTPConfig& config); + + ~Session(); + + Stream *create_stream(const ZRTPConfig& config, + udp_sock *rtpsock, + udp_sock *rtcpsock, + uint32_t local_ssrc, + StreamMediaType media_type); + + int start_stream(Stream *stream); + int id() const { return m_id; } + + bool request_master(Stream *stream); + void on_secure(Stream *stream); + + static int cmd_verify_sas(struct re_printf *pf, void *arg); + static int cmd_unverify_sas(struct re_printf *pf, void *arg); + static int cmd_sas(bool verify, struct re_printf *pf, void *arg); + +private: + static std::vector<Session *> s_sessl; + + const bool m_start_parallel; + int m_id; + std::vector<Stream *> m_streams; + Stream *m_master; + unsigned int m_encrypted; +}; + + +#endif // __SESSION_H + diff --git a/modules/gzrtp/srtp.cpp b/modules/gzrtp/srtp.cpp new file mode 100644 index 0000000..9aaa4b3 --- /dev/null +++ b/modules/gzrtp/srtp.cpp @@ -0,0 +1,327 @@ +/** + * @file srtp.cpp GNU ZRTP: SRTP processing + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#include <stdint.h> + +#include <re.h> +#include <baresip.h> + +#ifdef GZRTP_USE_RE_SRTP +#include <string.h> +#else +#include <srtp/CryptoContext.h> +#include <srtp/CryptoContextCtrl.h> +#include <srtp/SrtpHandler.h> +#endif + +#include "srtp.h" + + +Srtp::Srtp(int& err, const SrtpSecret_t *secrets, EnableSecurity part) + +{ + const uint8_t *key, *salt; + uint32_t key_len, salt_len; + + err = EPERM; + +#ifdef GZRTP_USE_RE_SRTP + m_srtp = NULL; +#else + m_cc = NULL; + m_cc_ctrl = NULL; +#endif + + if (part == ForSender) { + // To encrypt packets: intiator uses initiator keys, + // responder uses responder keys + if (secrets->role == Initiator) { + key = secrets->keyInitiator; + key_len = secrets->initKeyLen / 8; + salt = secrets->saltInitiator; + salt_len = secrets->initSaltLen / 8; + } + else { + key = secrets->keyResponder; + key_len = secrets->respKeyLen / 8; + salt = secrets->saltResponder; + salt_len = secrets->respSaltLen / 8; + } + } + else if (part == ForReceiver) { + // To decrypt packets: intiator uses responder keys, + // responder initiator keys + if (secrets->role == Initiator) { + key = secrets->keyResponder; + key_len = secrets->respKeyLen / 8; + salt = secrets->saltResponder; + salt_len = secrets->respSaltLen / 8; + } + else { + key = secrets->keyInitiator; + key_len = secrets->initKeyLen / 8; + salt = secrets->saltInitiator; + salt_len = secrets->initSaltLen / 8; + } + } + else { + err = EINVAL; + return; + } + +#ifdef GZRTP_USE_RE_SRTP + + uint8_t key_buf[32 + 14]; // max key + salt + enum srtp_suite suite; + struct srtp *st; + + if (secrets->symEncAlgorithm == Aes && + secrets->authAlgorithm == Sha1) { + + if (key_len == 16 && secrets->srtpAuthTagLen == 32) + suite = SRTP_AES_CM_128_HMAC_SHA1_32; + + else if (key_len == 16 && secrets->srtpAuthTagLen == 80) + suite = SRTP_AES_CM_128_HMAC_SHA1_80; + + else if (key_len == 32 && secrets->srtpAuthTagLen == 32) + suite = SRTP_AES_256_CM_HMAC_SHA1_32; + + else if (key_len == 32 && secrets->srtpAuthTagLen == 80) + suite = SRTP_AES_256_CM_HMAC_SHA1_80; + + else { + err = ENOTSUP; + return; + } + } + else { + err = ENOTSUP; + return; + } + + if (salt_len != 14) { + err = EINVAL; + return; + } + + memcpy(key_buf, key, key_len); + memcpy(key_buf + key_len, salt, salt_len); + + err = srtp_alloc(&st, suite, key_buf, key_len + salt_len, 0); + if (err) + return; + + m_auth_tag_len = secrets->srtpAuthTagLen / 8; + m_srtp = st; + + err = 0; +#else + + CryptoContext *cc = NULL; + CryptoContextCtrl *cc_ctrl = NULL; + int cipher; + int authn; + int auth_key_len; + + switch (secrets->authAlgorithm) { + case Sha1: + authn = SrtpAuthenticationSha1Hmac; + auth_key_len = 20; + break; + case Skein: + authn = SrtpAuthenticationSkeinHmac; + auth_key_len = 32; + break; + default: + err = ENOTSUP; + return; + } + + switch (secrets->symEncAlgorithm) { + case Aes: + cipher = SrtpEncryptionAESCM; + break; + case TwoFish: + cipher = SrtpEncryptionTWOCM; + break; + default: + err = ENOTSUP; + return; + } + + cc = new CryptoContext( + 0, // SSRC (used for lookup) + 0, // Roll-Over-Counter (ROC) + 0L, // keyderivation << 48, + cipher, // encryption algo + authn, // authtentication algo + (uint8_t *)key, // Master Key + key_len, // Master Key length + (uint8_t *)salt, // Master Salt + salt_len, // Master Salt length + key_len, // encryption keyl + auth_key_len, // authentication key len + salt_len, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag lenA + + cc_ctrl = new CryptoContextCtrl( + 0, // SSRC (used for lookup) + cipher, // encryption algo + authn, // authtentication algo + (uint8_t *)key, // Master Key + key_len, // Master Key length + (uint8_t *)salt, // Master Salt + salt_len, // Master Salt length + key_len, // encryption keyl + auth_key_len, // authentication key len + salt_len, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag lenA + + if (!cc || !cc_ctrl) { + delete cc; + delete cc_ctrl; + + err = ENOMEM; + return; + } + + cc->deriveSrtpKeys(0L); + cc_ctrl->deriveSrtcpKeys(); + + m_cc = cc; + m_cc_ctrl = cc_ctrl; + + err = 0; +#endif +} + + +Srtp::~Srtp() +{ +#ifdef GZRTP_USE_RE_SRTP + mem_deref(m_srtp); +#else + delete m_cc; + delete m_cc_ctrl; +#endif +} + + +int Srtp::protect_int(struct mbuf *mb, bool control) +{ + size_t len = mbuf_get_left(mb); + + int32_t extra = (mbuf_get_space(mb) > len)? + mbuf_get_space(mb) - len : 0; + +#ifdef GZRTP_USE_RE_SRTP + if (m_auth_tag_len + (control? 4 : 0) > extra) + return ENOMEM; + + if (control) + return srtcp_encrypt(m_srtp, mb); + else + return srtp_encrypt(m_srtp, mb); +#else + if (control) { + if (m_cc_ctrl->getTagLength() + 4 + + m_cc_ctrl->getMkiLength() > extra) + return ENOMEM; + } + else { + if (m_cc->getTagLength() + + m_cc->getMkiLength() > extra) + return ENOMEM; + } + + bool rc; + + if (control) + rc = SrtpHandler::protectCtrl(m_cc_ctrl, mbuf_buf(mb), + len, &len); + else + rc = SrtpHandler::protect(m_cc, mbuf_buf(mb), len, &len); + if (!rc) + return EPROTO; + + if (len > mbuf_get_space(mb)) { + // this should never happen + error_msg("zrtp: protect: length > space (%u > %u)\n", + len, mbuf_get_space(mb)); + abort(); + } + + mb->end = mb->pos + len; + + return 0; +#endif +} + + +int Srtp::protect(struct mbuf *mb) +{ + return protect_int(mb, false); +} + + +int Srtp::protect_ctrl(struct mbuf *mb) +{ + return protect_int(mb, true); +} + + +// return value: +// 0 - OK +// EBADMSG - SRTP/RTP packet decode error +// EAUTH - SRTP authentication failed +// EALREADY - SRTP replay check failed +// other errors +int Srtp::unprotect_int(struct mbuf *mb, bool control) +{ +#ifdef GZRTP_USE_RE_SRTP + if (control) + return srtcp_decrypt(m_srtp, mb); + else + return srtp_decrypt(m_srtp, mb); +#else + size_t len = mbuf_get_left(mb); + uint32_t rc; + int err; + + if (control) + rc = SrtpHandler::unprotectCtrl(m_cc_ctrl, mbuf_buf(mb), + len, &len); + else + rc = SrtpHandler::unprotect(m_cc, mbuf_buf(mb), + len, &len, NULL); + + switch (rc) { + case 1: err = 0; break; + case 0: err = EBADMSG; break; + case -1: err = EAUTH; break; + case -2: err = EALREADY; break; + default: err = EINVAL; + } + + if (!err) + mb->end = mb->pos + len; + + return err; +#endif +} + + +int Srtp::unprotect(struct mbuf *mb) +{ + return unprotect_int(mb, false); +} + + +int Srtp::unprotect_ctrl(struct mbuf *mb) +{ + return unprotect_int(mb, true); +} + diff --git a/modules/gzrtp/srtp.h b/modules/gzrtp/srtp.h new file mode 100644 index 0000000..d42f4cf --- /dev/null +++ b/modules/gzrtp/srtp.h @@ -0,0 +1,46 @@ +/** + * @file srtp.h GNU ZRTP: SRTP processing + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#ifndef __SRTP_H +#define __SRTP_H + + +#include <libzrtpcpp/ZrtpCallback.h> + + +#ifdef GZRTP_USE_RE_SRTP +struct srtp; +#else +class CryptoContext; +class CryptoContextCtrl; +#endif + + +class Srtp { +public: + Srtp(int& err, const SrtpSecret_t *secrets, EnableSecurity part); + ~Srtp(); + + int protect(struct mbuf *mb); + int protect_ctrl(struct mbuf *mb); + int unprotect(struct mbuf *mb); + int unprotect_ctrl(struct mbuf *mb); + +private: + int protect_int(struct mbuf *mb, bool control); + int unprotect_int(struct mbuf *mb, bool control); + +#ifdef GZRTP_USE_RE_SRTP + int32_t m_auth_tag_len; + struct srtp *m_srtp; +#else + CryptoContext *m_cc; + CryptoContextCtrl *m_cc_ctrl; +#endif +}; + + +#endif // __SRTP_H + diff --git a/modules/gzrtp/stream.cpp b/modules/gzrtp/stream.cpp new file mode 100644 index 0000000..84ad5e4 --- /dev/null +++ b/modules/gzrtp/stream.cpp @@ -0,0 +1,707 @@ +/** + * @file stream.cpp GNU ZRTP: Stream class implementation + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#include <stdint.h> +#include <pthread.h> + +#include <re.h> +#include <baresip.h> + +#include <libzrtpcpp/ZRtp.h> +#include <libzrtpcpp/ZrtpStateClass.h> + +#include "session.h" +#include "stream.h" +#include "srtp.h" + + +// A burst of SRTP/SRTCP errors enough to display a warning +// Set to 1 to display all warnings +#define SRTP_ERR_BURST_THRESHOLD 20 + + +enum { + PRESZ = 36 /* Preamble size for TURN/STUN header */ +}; + + +enum pkt_type { + PKT_TYPE_UNKNOWN = 0, + PKT_TYPE_RTP = 1, + PKT_TYPE_RTCP = 2, + PKT_TYPE_ZRTP = 4 +}; + + +static enum pkt_type get_packet_type(const struct mbuf *mb) +{ + uint8_t b, pt; + uint32_t magic; + + if (mbuf_get_left(mb) < 8) + return PKT_TYPE_UNKNOWN; + + b = mbuf_buf(mb)[0]; + + if (127 < b && b < 192) { + pt = mbuf_buf(mb)[1] & 0x7f; + if (72 <= pt && pt <= 76) + return PKT_TYPE_RTCP; + else + return PKT_TYPE_RTP; + } + else { + memcpy(&magic, &mbuf_buf(mb)[4], 4); + magic = ntohl(magic); + if (magic == ZRTP_MAGIC) + return PKT_TYPE_ZRTP; + } + + return PKT_TYPE_UNKNOWN; +} + + +ZRTPConfig::ZRTPConfig(const struct conf *conf, const char *conf_dir) +{ +#ifdef GZRTP_USE_RE_SRTP + // Standard ciphers only + zrtp.clear(); + + zrtp.addAlgo(HashAlgorithm, zrtpHashes.getByName(s256)); + + zrtp.addAlgo(CipherAlgorithm, zrtpSymCiphers.getByName(aes3)); + zrtp.addAlgo(CipherAlgorithm, zrtpSymCiphers.getByName(aes1)); + + zrtp.addAlgo(PubKeyAlgorithm, zrtpPubKeys.getByName(ec25)); + zrtp.addAlgo(PubKeyAlgorithm, zrtpPubKeys.getByName(dh3k)); + zrtp.addAlgo(PubKeyAlgorithm, zrtpPubKeys.getByName(ec38)); + zrtp.addAlgo(PubKeyAlgorithm, zrtpPubKeys.getByName(dh2k)); + zrtp.addAlgo(PubKeyAlgorithm, zrtpPubKeys.getByName(mult)); + + zrtp.addAlgo(SasType, zrtpSasTypes.getByName(b32)); + + zrtp.addAlgo(AuthLength, zrtpAuthLengths.getByName(hs32)); + zrtp.addAlgo(AuthLength, zrtpAuthLengths.getByName(hs80)); +#else + zrtp.setStandardConfig(); +#endif + + str_ncpy(client_id, "baresip/gzrtp", sizeof(client_id)); + + re_snprintf(zid_filename, sizeof(zid_filename), + "%s/gzrtp.zid", conf_dir); + + start_parallel = true; + (void)conf_get_bool(conf, "zrtp_parallel", &start_parallel); +} + +SRTPStat::SRTPStat(const Stream *st, bool srtcp, uint64_t threshold) + : m_stream(st) + , m_control(srtcp) + , m_threshold(threshold) +{ + reset(); +} + + +void SRTPStat::update(int ret_code, bool quiet) +{ + const char *err_msg; + uint64_t *burst; + + // Srtp::unprotect/unprotect_ctrl return codes + switch (ret_code) { + case 0: + ++m_ok; + m_decode_burst = 0; + m_auth_burst = 0; + m_replay_burst = 0; + return; + case EBADMSG: + ++m_decode; + burst = &m_decode_burst; + err_msg = "packet decode error"; + break; + case EAUTH: + ++m_auth; + burst = &m_auth_burst; + err_msg = "authentication failed"; + break; + case EALREADY: + ++m_replay; + burst = &m_replay_burst; + err_msg = "replay check failed"; + break; + default: + warning("zrtp: %s unprotect failed: %m\n", + (m_control)? "SRTCP" : "SRTP", ret_code); + return; + } + + ++(*burst); + if (*burst == m_threshold) { + *burst = 0; + + if (!quiet) + warning("zrtp: Stream <%s>: %s %s, %d packets\n", + m_stream->media_name(), + (m_control)? "SRTCP" : "SRTP", + err_msg, + m_threshold); + } +} + + +void SRTPStat::reset() +{ + m_ok = 0; + m_decode = 0; m_auth = 0; m_replay = 0; + m_decode_burst = 0; m_auth_burst = 0; m_replay_burst = 0; +} + + +Stream::Stream(int& err, const ZRTPConfig& config, Session *session, + udp_sock *rtpsock, udp_sock *rtcpsock, + uint32_t local_ssrc, StreamMediaType media_type) + : m_session(session) + , m_zrtp(NULL) + , m_started(false) + , m_local_ssrc(local_ssrc) + , m_peer_ssrc(0) + , m_rtpsock(NULL) + , m_rtcpsock(NULL) + , m_uh_rtp(NULL) + , m_uh_rtcp(NULL) + , m_media_type(media_type) + , m_send_srtp(NULL) + , m_recv_srtp(NULL) + , m_srtp_stat(this, false, SRTP_ERR_BURST_THRESHOLD) + , m_srtcp_stat(this, true, SRTP_ERR_BURST_THRESHOLD) +{ + err = 0; + + m_zrtp_seq = 1; // TODO: randomize + sa_init(&m_raddr, AF_INET); + tmr_init(&m_zrtp_timer); + + pthread_mutexattr_t attr; + err = pthread_mutexattr_init(&attr); + err |= pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); + err |= pthread_mutex_init(&m_zrtp_mutex, &attr); + err |= pthread_mutex_init(&m_send_mutex, &attr); + if (err) + return; + + int layer = 10; // above zero + if (rtpsock) { + m_rtpsock = (struct udp_sock *)mem_ref(rtpsock); + err |= udp_register_helper(&m_uh_rtp, rtpsock, layer, + Stream::udp_helper_send_cb, + Stream::udp_helper_recv_cb, + this); + } + if (rtcpsock && (rtcpsock != rtpsock)) { + m_rtcpsock = (struct udp_sock *)mem_ref(rtcpsock); + err |= udp_register_helper(&m_uh_rtcp, rtcpsock, layer, + Stream::udp_helper_send_cb, + Stream::udp_helper_recv_cb, + this); + } + if (err) + return; + + ZIDCache* zf = getZidCacheInstance(); + if (!zf->isOpen()) { + if (zf->open((char *)config.zid_filename) == -1) { + warning("zrtp: Couldn't open/create ZID file %s\n", + config.zid_filename); + err = ENOENT; + return; + } + } + + m_zrtp = new ZRtp((uint8_t *)zf->getZid(), this, config.client_id, + (ZrtpConfigure *)&config.zrtp, false, false); + if (!m_zrtp) { + err = ENOMEM; + return; + } + + return; +} + + +Stream::~Stream() +{ + stop(); + + delete m_zrtp; + + mem_deref(m_uh_rtp); + mem_deref(m_uh_rtcp); + mem_deref(m_rtpsock); + mem_deref(m_rtcpsock); + + pthread_mutex_destroy(&m_zrtp_mutex); + pthread_mutex_destroy(&m_send_mutex); + + tmr_cancel(&m_zrtp_timer); +} + + +int Stream::start(Stream *master) +{ + if (started()) + return EPERM; + + if (master) { + ZRtp *zrtp_master; + + std::string params = + master->m_zrtp->getMultiStrParams(&zrtp_master); + if (params.empty()) + return EPROTO; + + m_zrtp->setMultiStrParams(params, zrtp_master); + } + + debug("zrtp: Starting <%s> stream%s\n", media_name(), + (m_zrtp->isMultiStream())? " (multistream)" : ""); + + m_srtp_stat.reset(); + m_srtcp_stat.reset(); + m_sas.clear(); + m_ciphers.clear(); + + m_started = true; + m_zrtp->startZrtpEngine(); + + return 0; +} + + +void Stream::stop() +{ + if (!started()) + return; + + m_started = false; + + // If we got only a small amount of valid SRTP packets after ZRTP + // negotiation then assume that our peer couldn't store the RS data, + // thus make sure we have a second retained shared secret available. + // Refer to RFC 6189bis, chapter 4.6.1 50 packets are about 1 second + // of audio data + if (!m_zrtp->isMultiStream() && m_recv_srtp && m_srtp_stat.ok() < 20) { + + debug("zrtp: Stream <%s>: received too few valid SRTP " + "packets (%u), storing RS2\n", + media_name(), m_srtp_stat.ok()); + + m_zrtp->setRs2Valid(); + } + + debug("zrtp: Stopping <%s> stream\n", media_name()); + + m_zrtp->stopZrtp(); + + pthread_mutex_lock(&m_send_mutex); + delete m_send_srtp; + m_send_srtp = NULL; + pthread_mutex_unlock(&m_send_mutex); + + delete m_recv_srtp; + m_recv_srtp = NULL; + + debug("zrtp: Stream <%s> stopped\n", media_name()); +} + + +int Stream::sdp_encode(struct sdp_media *sdpm) +{ + // TODO: signaling hash + return 0; +} + + +int Stream::sdp_decode(const struct sdp_media *sdpm) +{ + if (sa_isset(sdp_media_raddr(sdpm), SA_ALL)) { + m_raddr = *sdp_media_raddr(sdpm); + } + // TODO: signaling hash + + return 0; +} + + +bool Stream::udp_helper_send_cb(int *err, struct sa *src, struct mbuf *mb, + void *arg) +{ + Stream *st = (Stream *)arg; + + if (st) + return st->udp_helper_send(err, src, mb); + + return false; +} + + +bool Stream::udp_helper_send(int *err, struct sa *src, struct mbuf *mb) +{ + bool ret = false; + enum pkt_type ptype = get_packet_type(mb); + size_t len = mbuf_get_left(mb); + int rerr = 0; + + pthread_mutex_lock(&m_send_mutex); + + if (ptype == PKT_TYPE_RTCP && m_send_srtp && len > 8) { + + rerr = m_send_srtp->protect_ctrl(mb); + } + else if (ptype == PKT_TYPE_RTP && m_send_srtp && + len > RTP_HEADER_SIZE) { + + rerr = m_send_srtp->protect(mb); + } + else + goto out; + + if (rerr) { + warning("zrtp: protect/protect_ctrl failed (len=%u): %m\n", + len, rerr); + + if (rerr == ENOMEM) + *err = rerr; + // drop + ret = true; + } + + out: + pthread_mutex_unlock(&m_send_mutex); + + return ret; +} + + +bool Stream::udp_helper_recv_cb(struct sa *src, struct mbuf *mb, void *arg) +{ + Stream *st = (Stream *)arg; + + if (st) + return st->udp_helper_recv(src, mb); + + return false; +} + + +bool Stream::udp_helper_recv(struct sa *src, struct mbuf *mb) +{ + if (!started()) + return false; + + enum pkt_type ptype = get_packet_type(mb); + int err = 0; + + if (ptype == PKT_TYPE_RTCP && m_recv_srtp) { + + err = m_recv_srtp->unprotect_ctrl(mb); + + m_srtcp_stat.update(err); + } + else if (ptype == PKT_TYPE_RTP && m_recv_srtp) { + + err = m_recv_srtp->unprotect(mb); + + m_srtp_stat.update(err); + + if (!err) { + // Got a good SRTP, check state and if in WaitConfAck + // (an Initiator state) then simulate a conf2Ack, + // refer to RFC 6189, chapter 4.6, last paragraph + if (m_zrtp->inState(WaitConfAck)) + m_zrtp->conf2AckSecure(); + } + } + else if (ptype == PKT_TYPE_ZRTP) { + return recv_zrtp(mb); + } + else + return false; + + if (err) + // drop + return true; + + return false; +} + + +// <RTP> + <ext. header> + <ZRTP message type> + CRC32 +#define ZRTP_MIN_PACKET_LENGTH (RTP_HEADER_SIZE + 4 + 8 + 4) + +bool Stream::recv_zrtp(struct mbuf *mb) +{ + uint32_t crc32; + uint8_t *buf = mbuf_buf(mb); + size_t size = mbuf_get_left(mb); + + if (size < ZRTP_MIN_PACKET_LENGTH) { + warning("zrtp: incoming packet size (%d) is too small\n", + size); + return false; + } + + // check CRC + memcpy(&crc32, buf + size - 4, 4); + crc32 = ntohl(crc32); + if (!zrtpCheckCksum(buf, size - 4, crc32)) { + sendInfo(GnuZrtpCodes::Warning, + GnuZrtpCodes::WarningCRCmismatch); + return false; + } + + // store peer's SSRC for creating the CryptoContext + memcpy(&m_peer_ssrc, buf + 8, 4); + m_peer_ssrc = ntohl(m_peer_ssrc); + + m_zrtp->processZrtpMessage(buf + RTP_HEADER_SIZE, m_peer_ssrc, size); + + return true; +} + + +void Stream::verify_sas(bool verify) +{ + if (verify) + m_zrtp->SASVerified(); + else + m_zrtp->resetSASVerified(); +} + + +bool Stream::sas_verified() +{ + return m_zrtp->isSASVerified(); +} + + +// +// callbacks +// + + +int32_t Stream::sendDataZRTP(const uint8_t* data, int32_t length) +{ + struct mbuf *mb; + uint8_t *crc_buf; + uint32_t crc32; + size_t start_pos = PRESZ; + int err = 0; + + if (!sa_isset(&m_raddr, SA_ALL)) + return 0; + + mb = mbuf_alloc(start_pos + RTP_HEADER_SIZE + length); + if (!mb) + return 0; + + mbuf_set_end(mb, start_pos); + mbuf_set_pos(mb, start_pos); + crc_buf = mbuf_buf(mb); + + // write RTP header + err = mbuf_write_u8(mb, 0x10); + err |= mbuf_write_u8(mb, 0x00); + err |= mbuf_write_u16(mb, htons(m_zrtp_seq++)); + err |= mbuf_write_u32(mb, htonl(ZRTP_MAGIC)); + err |= mbuf_write_u32(mb, htonl(m_local_ssrc)); + + // copy ZRTP message data + err |= mbuf_write_mem(mb, data, length - 4); + + // compute CRC + crc32 = zrtpGenerateCksum(crc_buf, RTP_HEADER_SIZE + length - 4); + crc32 = zrtpEndCksum(crc32); + + // store CRC + err |= mbuf_write_u32(mb, htonl(crc32)); + if (err) + goto out; + + // send ZRTP packet using RTP socket + mbuf_set_pos(mb, start_pos); + err = udp_send_helper(m_rtpsock, &m_raddr, mb, m_uh_rtp); + if (err) + warning("zrtp: udp_send_helper: %m\n", err); + + out: + mem_deref(mb); + + return (err == 0); +} + + +void Stream::zrtp_timer_cb(void *arg) +{ + Stream *s = (Stream *)arg; + + s->m_zrtp->processTimeout(); +} + + +int32_t Stream::activateTimer(int32_t time) +{ + tmr_start(&m_zrtp_timer, time, &Stream::zrtp_timer_cb, this); + return 1; +} + + +int32_t Stream::cancelTimer() +{ + tmr_cancel(&m_zrtp_timer); + return 1; +} + + +void Stream::sendInfo(GnuZrtpCodes::MessageSeverity severity, int32_t subCode) +{ + print_message(severity, subCode); + + if (severity == GnuZrtpCodes::Info) { + if (subCode == GnuZrtpCodes::InfoSecureStateOn) { + m_session->on_secure(this); + } + else if (subCode == GnuZrtpCodes::InfoHelloReceived && + !m_zrtp->isMultiStream()) { + + m_session->request_master(this); + } + } +} + + +bool Stream::srtpSecretsReady(SrtpSecret_t* secrets, EnableSecurity part) +{ + Srtp *s; + int err = 0; + + debug("zrtp: Stream <%s>: secrets are ready for %s\n", + media_name(), + (part == ForSender)? "sender" : "receiver"); + + s = new Srtp(err, secrets, part); + if (!s || err) { + warning("zrtp: Stream <%s>: Srtp creation failed: %m\n", + media_name(), err); + delete s; + return false; + } + + if (part == ForSender) { + pthread_mutex_lock(&m_send_mutex); + m_send_srtp = s; + pthread_mutex_unlock(&m_send_mutex); + } + else if (part == ForReceiver) + m_recv_srtp = s; + else + return false; + + return true; +} + + +void Stream::srtpSecretsOff(EnableSecurity part) +{ + debug("zrtp: Stream <%s>: secrets are off for %s\n", + media_name(), + (part == ForSender)? "sender" : "receiver"); + + if (part == ForSender) { + pthread_mutex_lock(&m_send_mutex); + delete m_send_srtp; + m_send_srtp = NULL; + pthread_mutex_unlock(&m_send_mutex); + } + + if (part == ForReceiver) { + delete m_recv_srtp; + m_recv_srtp = NULL; + } +} + + +void Stream::srtpSecretsOn(std::string c, std::string s, bool verified) +{ + m_sas = s; + m_ciphers = c; + + if (s.empty()) { + info("zrtp: Stream <%s> is encrypted (%s)\n", + media_name(), c.c_str()); + } + else { + info("zrtp: Stream <%s> is encrypted (%s), " + "SAS is [%s] (%s)\n", + media_name(), c.c_str(), s.c_str(), + (verified)? "verified" : "NOT VERIFIED"); + if (!verified) + warning("zrtp: SAS is not verified, type " + "'/zrtp_verify %d' to verify\n", + m_session->id()); + } +} + + +void Stream::handleGoClear() +{ +} + + +void Stream::zrtpNegotiationFailed(GnuZrtpCodes::MessageSeverity severity, + int32_t subCode) +{ +} + + +void Stream::zrtpNotSuppOther() +{ +} + + +void Stream::synchEnter() +{ + pthread_mutex_lock(&m_zrtp_mutex); +} + + +void Stream::synchLeave() +{ + pthread_mutex_unlock(&m_zrtp_mutex); +} + + +void Stream::zrtpAskEnrollment(GnuZrtpCodes::InfoEnrollment info) +{ +} + + +void Stream::zrtpInformEnrollment(GnuZrtpCodes::InfoEnrollment info) +{ +} + + +void Stream::signSAS(uint8_t* sasHash) +{ +} + + +bool Stream::checkSASSignature(uint8_t* sasHash) +{ + return true; +} + diff --git a/modules/gzrtp/stream.h b/modules/gzrtp/stream.h new file mode 100644 index 0000000..d25a5c1 --- /dev/null +++ b/modules/gzrtp/stream.h @@ -0,0 +1,138 @@ +/** + * @file stream.h GNU ZRTP: Stream class + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#ifndef __STREAM_H +#define __STREAM_H + + +#include <libzrtpcpp/ZRtp.h> + + +enum StreamMediaType { + MT_UNKNOWN = 0, + MT_AUDIO, + MT_VIDEO, + MT_TEXT, + MT_APPLICATION, + MT_MESSAGE +}; + + +class ZRTPConfig { +public: + ZRTPConfig(const struct conf *conf, const char *conf_dir); +private: + friend class Stream; + friend class Session; + + ZrtpConfigure zrtp; + + char client_id[CLIENT_ID_SIZE + 1]; + char zid_filename[256]; + + bool start_parallel; +}; + + +class Stream; + +class SRTPStat { +public: + SRTPStat(const Stream *st, bool srtcp, uint64_t threshold); + void update(int ret_code, bool quiet = false); + void reset(); + uint64_t ok() { return m_ok; } +private: + const Stream *m_stream; + const bool m_control; + const uint64_t m_threshold; + uint64_t m_ok, m_decode, m_auth, m_replay; + uint64_t m_decode_burst, m_auth_burst, m_replay_burst; +}; + + +class Session; +class Srtp; + +class Stream : public ZrtpCallback { +public: + Stream(int& err, const ZRTPConfig& config, Session *session, + udp_sock *rtpsock, udp_sock *rtcpsock, + uint32_t local_ssrc, StreamMediaType media_type); + + virtual ~Stream(); + + int start(Stream *master); + void stop(); + bool started() { return m_started; } + + int sdp_encode(struct sdp_media *sdpm); + int sdp_decode(const struct sdp_media *sdpm); + + const char *media_name() const; + + const char *get_sas() const { return m_sas.c_str(); } + const char *get_ciphers() const { return m_ciphers.c_str(); } + bool sas_verified(); + void verify_sas(bool verify); + +private: + static void zrtp_timer_cb(void *arg); + static bool udp_helper_send_cb(int *err, struct sa *src, + struct mbuf *mb, void *arg); + static bool udp_helper_recv_cb(struct sa *src, struct mbuf *mb, + void *arg); + + bool udp_helper_send(int *err, struct sa *src, struct mbuf *mb); + bool udp_helper_recv(struct sa *src, struct mbuf *mb); + bool recv_zrtp(struct mbuf *mb); + + void print_message(GnuZrtpCodes::MessageSeverity severity, + int32_t subcode); + + Session *m_session; + ZRtp *m_zrtp; + bool m_started; + struct tmr m_zrtp_timer; + pthread_mutex_t m_zrtp_mutex; + uint16_t m_zrtp_seq; + uint32_t m_local_ssrc, m_peer_ssrc; + struct sa m_raddr; + struct udp_sock *m_rtpsock, *m_rtcpsock; + struct udp_helper *m_uh_rtp; + struct udp_helper *m_uh_rtcp; + StreamMediaType m_media_type; + Srtp *m_send_srtp, *m_recv_srtp; + pthread_mutex_t m_send_mutex; + SRTPStat m_srtp_stat, m_srtcp_stat; + std::string m_sas, m_ciphers; + +protected: + virtual int32_t sendDataZRTP(const uint8_t* data, int32_t length); + virtual int32_t activateTimer(int32_t time); + virtual int32_t cancelTimer(); + virtual void sendInfo(GnuZrtpCodes::MessageSeverity severity, + int32_t subCode); + virtual bool srtpSecretsReady(SrtpSecret_t* secrets, + EnableSecurity part); + virtual void srtpSecretsOff(EnableSecurity part); + virtual void srtpSecretsOn(std::string c, std::string s, + bool verified); + virtual void handleGoClear(); + virtual void zrtpNegotiationFailed( + GnuZrtpCodes::MessageSeverity severity, + int32_t subCode); + virtual void zrtpNotSuppOther(); + virtual void synchEnter(); + virtual void synchLeave(); + virtual void zrtpAskEnrollment(GnuZrtpCodes::InfoEnrollment info); + virtual void zrtpInformEnrollment(GnuZrtpCodes::InfoEnrollment info); + virtual void signSAS(uint8_t* sasHash); + virtual bool checkSASSignature(uint8_t* sasHash); +}; + + +#endif // __STREAM_H + diff --git a/modules/h265/fmt.c b/modules/h265/fmt.c index b30df2c..ac71b10 100644 --- a/modules/h265/fmt.c +++ b/modules/h265/fmt.c @@ -1,3 +1,8 @@ +/** + * @file h265/fmt.c H.265 Video Codec -- protocol format + * + * Copyright (C) 2010 Creytiv.com + */ #include <string.h> #include <re.h> @@ -58,11 +63,11 @@ int h265_nal_decode(struct h265_nal *nal, const uint8_t *p) nal->nuh_temporal_id_plus1 = p[1] & 0x07; if (forbidden_zero_bit) { - re_fprintf(stderr, "?!?!?!?! FORBIDDEN !!! ?!?!?!*\n"); + warning("h265: nal_decode: FORBIDDEN bit set\n"); return EBADMSG; } if (nuh_layer_id != 0) { - re_fprintf(stderr, "h265_nal_decode: LayerId MUST be zero\n"); + warning("h265: nal_decode: LayerId MUST be zero\n"); return EBADMSG; } diff --git a/modules/jack/jack_play.c b/modules/jack/jack_play.c index 4f19b68..f890ddd 100644 --- a/modules/jack/jack_play.c +++ b/modules/jack/jack_play.c @@ -201,6 +201,12 @@ int jack_play_alloc(struct auplay_st **stp, const struct auplay *ap, if (prm->ch > ARRAY_SIZE(st->portv)) return EINVAL; + if (prm->fmt != AUFMT_S16LE) { + warning("jack: playback: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + st = mem_zalloc(sizeof(*st), auplay_destructor); if (!st) return ENOMEM; diff --git a/modules/jack/jack_src.c b/modules/jack/jack_src.c index e4662c3..0ef1e42 100644 --- a/modules/jack/jack_src.c +++ b/modules/jack/jack_src.c @@ -196,6 +196,12 @@ int jack_src_alloc(struct ausrc_st **stp, const struct ausrc *as, if (prm->ch > ARRAY_SIZE(st->portv)) return EINVAL; + if (prm->fmt != AUFMT_S16LE) { + warning("jack: source: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + st = mem_zalloc(sizeof(*st), ausrc_destructor); if (!st) return ENOMEM; diff --git a/modules/menu/menu.c b/modules/menu/menu.c index 23aae45..c7f1646 100644 --- a/modules/menu/menu.c +++ b/modules/menu/menu.c @@ -37,7 +37,7 @@ static struct { struct play *play; struct message_lsnr *message; bool bell; - + bool ringback_disabled; /**< no ringback on sip 180 respons */ struct tmr tmr_redial; /**< Timer for auto-reconnect */ uint32_t redial_delay; /**< Redial delay in [seconds] */ uint32_t redial_attempts; /**< Number of re-dial attempts */ @@ -376,6 +376,8 @@ static int create_ua(struct re_printf *pf, void *arg) static int cmd_ua_next(struct re_printf *pf, void *unused) { + int err; + (void)pf; (void)unused; @@ -386,13 +388,13 @@ static int cmd_ua_next(struct re_printf *pf, void *unused) le_cur = le_cur->next ? le_cur->next : list_head(uag_list()); - (void)re_fprintf(stderr, "ua: %s\n", ua_aor(list_ledata(le_cur))); + err = re_hprintf(pf, "ua: %s\n", ua_aor(list_ledata(le_cur))); uag_current_set(list_ledata(le_cur)); update_callstatus(); - return 0; + return err; } @@ -977,7 +979,13 @@ static void ua_event_handler(struct ua *ua, enum ua_event ev, /* stop any ringtones */ menu.play = mem_deref(menu.play); - (void)play_file(&menu.play, player, "ringback.wav", -1); + if (menu.ringback_disabled) { + info("\nRingback disabled\n"); + } + else { + (void)play_file(&menu.play, player, + "ringback.wav",-1); + } break; case UA_EVENT_CALL_ESTABLISHED: @@ -1058,8 +1066,8 @@ static void message_handler(const struct pl *peer, const struct pl *ctype, (void)ctype; (void)arg; - (void)re_fprintf(stderr, "\r%r: \"%b\"\n", peer, - mbuf_buf(body), mbuf_get_left(body)); + ui_output(baresip_uis(), "\r%r: \"%b\"\n", + peer, mbuf_buf(body), mbuf_get_left(body)); (void)play_file(NULL, baresip_player(), "message.wav", 0); } @@ -1074,6 +1082,8 @@ static int module_init(void) * Read the config values */ conf_get_bool(conf_cur(), "menu_bell", &menu.bell); + conf_get_bool(conf_cur(), "ringback_disabled", + &menu.ringback_disabled); if (0 == conf_get(conf_cur(), "redial_attempts", &val) && 0 == pl_strcasecmp(&val, "inf")) { diff --git a/modules/mqtt/README.md b/modules/mqtt/README.md new file mode 100644 index 0000000..6a86b84 --- /dev/null +++ b/modules/mqtt/README.md @@ -0,0 +1,59 @@ +README +------ + + +This module implements an MQTT (Message Queue Telemetry Transport) client +for publishing and subscribing to topics. + + +The module is using libmosquitto + + +Starting the MQTT broker: + +``` +$ /usr/local/sbin/mosquitto -v +``` + + +Subscribing to all topics: + +``` +$ mosquitto_sub -t /baresip/+ +``` + + +Publishing to the topic: + +``` +$ mosquitto_pub -t /baresip/xxx -m foo=42 +``` + + +## Topic patterns + +(Outgoing direction is from baresip mqtt module to broker, + incoming direction is from broker to baresip mqtt module) + +* /baresip/event Outgoing events from ua_event +* /baresip/command Incoming long command request +* /baresip/command_resp Outgoing long command response + + +## Examples + +``` +/baresip/event sip:aeh@iptel.org,REGISTERING +/baresip/event sip:aeh@iptel.org,REGISTER_OK +/baresip/event sip:aeh@iptel.org,SHUTDOWN +``` + +``` +mosquitto_pub -t /baresip/command -m "/dial music" + +/baresip/command /dial music +/baresip/command_resp (null) +/baresip/event sip:aeh@iptel.org,CALL_ESTABLISHED +/baresip/event sip:aeh@iptel.org,CALL_CLOSED +``` + diff --git a/modules/mqtt/module.mk b/modules/mqtt/module.mk new file mode 100644 index 0000000..cb4c379 --- /dev/null +++ b/modules/mqtt/module.mk @@ -0,0 +1,14 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := mqtt +$(MOD)_SRCS += mqtt.c +$(MOD)_SRCS += publish.c +$(MOD)_SRCS += subscribe.c +$(MOD)_LFLAGS += -lmosquitto +$(MOD)_CFLAGS += + +include mk/mod.mk diff --git a/modules/mqtt/mqtt.c b/modules/mqtt/mqtt.c new file mode 100644 index 0000000..a927959 --- /dev/null +++ b/modules/mqtt/mqtt.c @@ -0,0 +1,157 @@ +/** + * @file mqtt.c Message Queue Telemetry Transport (MQTT) client + * + * Copyright (C) 2017 Creytiv.com + */ + +#include <mosquitto.h> +#include <re.h> +#include <baresip.h> +#include "mqtt.h" + + +static char broker_host[256] = "127.0.0.1"; +static uint32_t broker_port = 1883; + +static struct mqtt s_mqtt; + + +static void fd_handler(int flags, void *arg) +{ + struct mqtt *mqtt = arg; + + mosquitto_loop_read(mqtt->mosq, 1); + + mosquitto_loop_write(mqtt->mosq, 1); +} + + +/* XXX: use mosquitto_socket and fd_listen instead? */ +static void tmr_handler(void *data) +{ + struct mqtt *mqtt = data; + int ret; + + tmr_start(&mqtt->tmr, 500, tmr_handler, mqtt); + + ret = mosquitto_loop_misc(mqtt->mosq); + if (ret != MOSQ_ERR_SUCCESS) { + warning("mqtt: error in loop (%s)\n", mosquitto_strerror(ret)); + } +} + + +/* + * This is called when the broker sends a CONNACK message + * in response to a connection. + */ +static void connect_callback(struct mosquitto *mosq, void *obj, int result) +{ + struct mqtt *mqtt = obj; + int err; + (void)mqtt; + + if (result != MOSQ_ERR_SUCCESS) { + warning("mqtt: could not connect to broker (%s)\n", + mosquitto_strerror(result)); + return; + } + + info("mqtt: connected to broker at %s:%d\n", + broker_host, broker_port); + + err = mqtt_subscribe_start(mqtt); + if (err) { + warning("mqtt: subscribe_init failed (%m)\n", err); + } +} + + +static int module_init(void) +{ + const int keepalive = 60; + int ret; + int err = 0; + + tmr_init(&s_mqtt.tmr); + + mosquitto_lib_init(); + + conf_get_str(conf_cur(), "mqtt_broker_host", + broker_host, sizeof(broker_host)); + conf_get_u32(conf_cur(), "mqtt_broker_port", &broker_port); + + s_mqtt.mosq = mosquitto_new("baresip", true, &s_mqtt); + if (!s_mqtt.mosq) { + warning("mqtt: failed to create client instance\n"); + return ENOMEM; + } + + err = mqtt_subscribe_init(&s_mqtt); + if (err) + return err; + + mosquitto_connect_callback_set(s_mqtt.mosq, connect_callback); + + ret = mosquitto_connect(s_mqtt.mosq, broker_host, broker_port, + keepalive); + if (ret != MOSQ_ERR_SUCCESS) { + + err = ret == MOSQ_ERR_ERRNO ? errno : EIO; + + warning("mqtt: failed to connect to %s:%d (%s)\n", + broker_host, broker_port, + mosquitto_strerror(ret)); + return err; + } + + tmr_start(&s_mqtt.tmr, 1, tmr_handler, &s_mqtt); + + err = mqtt_publish_init(&s_mqtt); + if (err) + return err; + + s_mqtt.fd = mosquitto_socket(s_mqtt.mosq); + + err = fd_listen(s_mqtt.fd, FD_READ, fd_handler, &s_mqtt); + if (err) + return err; + + info("mqtt: module loaded\n"); + + return err; +} + + +static int module_close(void) +{ + fd_close(s_mqtt.fd); + + mqtt_publish_close(); + + mqtt_subscribe_close(); + + tmr_cancel(&s_mqtt.tmr); + + if (s_mqtt.mosq) { + + mosquitto_disconnect(s_mqtt.mosq); + + mosquitto_destroy(s_mqtt.mosq); + s_mqtt.mosq = NULL; + } + + mosquitto_lib_cleanup(); + + info("mqtt: module unloaded\n"); + + return 0; +} + + +const struct mod_export DECL_EXPORTS(mqtt) = { + "mqtt", + "application", + module_init, + module_close +}; diff --git a/modules/mqtt/mqtt.h b/modules/mqtt/mqtt.h new file mode 100644 index 0000000..e79fb6e --- /dev/null +++ b/modules/mqtt/mqtt.h @@ -0,0 +1,26 @@ + + +struct mqtt { + struct mosquitto *mosq; + struct tmr tmr; + int fd; +}; + + +/* + * Subscribe direction (incoming) + */ + +int mqtt_subscribe_init(struct mqtt *mqtt); +int mqtt_subscribe_start(struct mqtt *mqtt); +void mqtt_subscribe_close(void); + + +/* + * Publish direction (outgoing) + */ + +int mqtt_publish_init(struct mqtt *mqtt); +void mqtt_publish_close(void); +int mqtt_publish_message(struct mqtt *mqtt, const char *topic, + const char *fmt, ...); diff --git a/modules/mqtt/publish.c b/modules/mqtt/publish.c new file mode 100644 index 0000000..524b0a2 --- /dev/null +++ b/modules/mqtt/publish.c @@ -0,0 +1,104 @@ +/** + * @file publish.c MQTT client -- publish + * + * Copyright (C) 2017 Creytiv.com + */ + +#include <mosquitto.h> +#include <re.h> +#include <baresip.h> +#include "mqtt.h" + + +/* + * This file contains functions for sending outgoing messages + * from baresip to broker (publish) + */ + + +/* + * Relay UA events as publish messages to the Broker + * + * XXX: move JSON encoding to baresip core + */ +static void ua_event_handler(struct ua *ua, enum ua_event ev, + struct call *call, const char *prm, void *arg) +{ + struct mqtt *mqtt = arg; + struct odict *od = NULL; + int err; + + err = odict_alloc(&od, 8); + if (err) + return; + + err = event_encode_dict(od, ua, ev, call, prm); + if (err) + goto out; + + err = mqtt_publish_message(mqtt, "/baresip/event", "%H", + json_encode_odict, od); + if (err) { + warning("mqtt: failed to publish message (%m)\n", err); + goto out; + } + + out: + mem_deref(od); +} + + +int mqtt_publish_message(struct mqtt *mqtt, const char *topic, + const char *fmt, ...) +{ + char *message; + va_list ap; + int ret; + int err = 0; + + if (!mqtt || !topic || !fmt) + return EINVAL; + + va_start(ap, fmt); + err = re_vsdprintf(&message, fmt, ap); + va_end(ap); + + if (err) + return err; + + ret = mosquitto_publish(mqtt->mosq, + NULL, + topic, + (int)str_len(message), + message, + 0, + false); + if (ret != MOSQ_ERR_SUCCESS) { + warning("mqtt: failed to publish (%s)\n", + mosquitto_strerror(ret)); + err = EINVAL; + goto out; + } + + out: + mem_deref(message); + return err; +} + + +int mqtt_publish_init(struct mqtt *mqtt) +{ + int err; + + err = uag_event_register(ua_event_handler, mqtt); + if (err) + return err; + + return err; +} + + +void mqtt_publish_close(void) +{ + uag_event_unregister(&ua_event_handler); +} diff --git a/modules/mqtt/subscribe.c b/modules/mqtt/subscribe.c new file mode 100644 index 0000000..d36ece2 --- /dev/null +++ b/modules/mqtt/subscribe.c @@ -0,0 +1,146 @@ +/** + * @file subscribe.c MQTT client -- subscribe + * + * Copyright (C) 2017 Creytiv.com + */ + +#include <mosquitto.h> +#include <re.h> +#include <baresip.h> +#include "mqtt.h" + + +static const char *subscription_pattern = "/baresip/+"; + + +static int print_handler(const char *p, size_t size, void *arg) +{ + struct mbuf *mb = arg; + + return mbuf_write_mem(mb, (void *)p, size); +} + + +static void handle_command(struct mqtt *mqtt, const struct pl *msg) +{ + struct mbuf *resp = mbuf_alloc(1024); + struct re_printf pf = {print_handler, resp}; + struct odict *od = NULL; + const struct odict_entry *oe_cmd, *oe_prm, *oe_tok; + char buf[256], resp_topic[256]; + int err; + + /* XXX: add transaction ID ? */ + + err = json_decode_odict(&od, 32, msg->p, msg->l, 16); + if (err) { + warning("mqtt: failed to decode JSON with %zu bytes (%m)\n", + msg->l, err); + return; + } + + oe_cmd = odict_lookup(od, "command"); + oe_prm = odict_lookup(od, "params"); + oe_tok = odict_lookup(od, "token"); + if (!oe_cmd) { + warning("mqtt: missing json entries\n"); + goto out; + } + + debug("mqtt: handle_command: cmd='%s', token='%s'\n", + oe_cmd ? oe_cmd->u.str : "", + oe_tok ? oe_tok->u.str : ""); + + re_snprintf(buf, sizeof(buf), "%s%s%s", + oe_cmd->u.str, + oe_prm ? " " : "", + oe_prm ? oe_prm->u.str : ""); + + /* Relay message to long commands */ + err = cmd_process_long(baresip_commands(), + buf, + str_len(buf), + &pf, NULL); + if (err) { + warning("mqtt: error processing command (%m)\n", err); + } + + /* NOTE: the command will now write the response + to the resp mbuf, send it back to broker */ + + re_snprintf(resp_topic, sizeof(resp_topic), + "/baresip/command_resp/%s", + oe_tok ? oe_tok->u.str : "nil"); + + err = mqtt_publish_message(mqtt, resp_topic, + "%b", + resp->buf, resp->end); + if (err) { + warning("mqtt: failed to publish message (%m)\n", err); + goto out; + } + + out: + mem_deref(resp); + mem_deref(od); +} + + +/* + * This is called when a message is received from the broker. + */ +static void message_callback(struct mosquitto *mosq, void *obj, + const struct mosquitto_message *message) +{ + struct mqtt *mqtt = obj; + struct pl msg; + bool match = false; + + info("mqtt: got message '%b' for topic '%s'\n", + (char*) message->payload, (size_t)message->payloadlen, + message->topic); + + msg.p = message->payload; + msg.l = message->payloadlen; + + mosquitto_topic_matches_sub("/baresip/command", message->topic, + &match); + if (match) { + info("mqtt: got message for '%s' topic\n", message->topic); + + handle_command(mqtt, &msg); + } +} + + +int mqtt_subscribe_init(struct mqtt *mqtt) +{ + if (!mqtt) + return EINVAL; + + mosquitto_message_callback_set(mqtt->mosq, message_callback); + + return 0; +} + + +int mqtt_subscribe_start(struct mqtt *mqtt) +{ + int ret; + + ret = mosquitto_subscribe(mqtt->mosq, NULL, subscription_pattern, 0); + if (ret != MOSQ_ERR_SUCCESS) { + warning("mqtt: failed to subscribe (%s)\n", + mosquitto_strerror(ret)); + return EPROTO; + } + + info("mqtt: subscribed to pattern '%s'\n", subscription_pattern); + + return 0; +} + + +void mqtt_subscribe_close(void) +{ +} diff --git a/modules/omx/module.c b/modules/omx/module.c index e1d136e..a5b6fa8 100644 --- a/modules/omx/module.c +++ b/modules/omx/module.c @@ -10,8 +10,8 @@ #include <stdlib.h> -#include <re/re.h> -#include <rem/rem.h> +#include <re.h> +#include <rem.h> #include <baresip.h> int omx_vidisp_alloc(struct vidisp_st **vp, const struct vidisp* vd, diff --git a/modules/omx/omx.c b/modules/omx/omx.c index 7cc4a45..6b08d19 100644 --- a/modules/omx/omx.c +++ b/modules/omx/omx.c @@ -7,8 +7,8 @@ #include "omx.h" -#include <re/re.h> -#include <rem/rem.h> +#include <re.h> +#include <rem.h> #include <baresip.h> #include <stdio.h> diff --git a/modules/opensles/player.c b/modules/opensles/player.c index 347240f..59964f0 100644 --- a/modules/opensles/player.c +++ b/modules/opensles/player.c @@ -4,6 +4,7 @@ * Copyright (C) 2010 Creytiv.com */ #include <re.h> +#include <rem.h> #include <baresip.h> #include <SLES/OpenSLES.h> #include "SLES/OpenSLES_Android.h" @@ -153,6 +154,12 @@ int opensles_player_alloc(struct auplay_st **stp, const struct auplay *ap, if (!stp || !ap || !prm || !wh) return EINVAL; + if (prm->fmt != AUFMT_S16LE) { + warning("opensles: player: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + debug("opensles: opening player %uHz, %uchannels\n", prm->srate, prm->ch); diff --git a/modules/opensles/recorder.c b/modules/opensles/recorder.c index 0a4c5ef..b26190b 100644 --- a/modules/opensles/recorder.c +++ b/modules/opensles/recorder.c @@ -4,6 +4,7 @@ * Copyright (C) 2010 Creytiv.com */ #include <re.h> +#include <rem.h> #include <baresip.h> #include <string.h> #include <SLES/OpenSLES.h> @@ -165,6 +166,12 @@ int opensles_recorder_alloc(struct ausrc_st **stp, const struct ausrc *as, if (!stp || !as || !prm || !rh) return EINVAL; + if (prm->fmt != AUFMT_S16LE) { + warning("opensles: record: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + debug("opensles: opening recorder %uHz, %uchannels\n", prm->srate, prm->ch); diff --git a/modules/opus/opus.c b/modules/opus/opus.c index 881596e..7acccec 100644 --- a/modules/opus/opus.c +++ b/modules/opus/opus.c @@ -36,7 +36,7 @@ static bool opus_mirror; -static char fmtp[256] = "stereo=1;sprop-stereo=1"; +static char fmtp[256] = ""; static char fmtp_mirror[256]; @@ -88,9 +88,20 @@ static int module_init(void) struct conf *conf = conf_cur(); uint32_t value; char *p = fmtp + str_len(fmtp); - bool b; + bool b, stereo = true, sprop_stereo = true; int n = 0; + conf_get_bool(conf, "opus_stereo", &stereo); + conf_get_bool(conf, "opus_sprop_stereo", &sprop_stereo); + + /* always set stereo parameter first */ + n = re_snprintf(p, sizeof(fmtp) - str_len(p), + "stereo=%d;sprop-stereo=%d", stereo, sprop_stereo); + if (n <= 0) + return ENOMEM; + + p += n; + if (0 == conf_get_u32(conf, "opus_bitrate", &value)) { n = re_snprintf(p, sizeof(fmtp) - str_len(p), diff --git a/modules/oss/oss.c b/modules/oss/oss.c index 4a8af92..0d47dd1 100644 --- a/modules/oss/oss.c +++ b/modules/oss/oss.c @@ -226,7 +226,7 @@ static int src_alloc(struct ausrc_st **stp, const struct ausrc *as, (void)ctx; (void)errh; - if (!stp || !as || !prm || !rh) + if (!stp || !as || !prm || prm->fmt != AUFMT_S16LE || !rh) return EINVAL; st = mem_zalloc(sizeof(*st), ausrc_destructor); @@ -285,7 +285,7 @@ static int play_alloc(struct auplay_st **stp, const struct auplay *ap, struct auplay_st *st; int err; - if (!stp || !ap || !prm || !wh) + if (!stp || !ap || !prm || prm->fmt != AUFMT_S16LE || !wh) return EINVAL; st = mem_zalloc(sizeof(*st), auplay_destructor); diff --git a/modules/portaudio/portaudio.c b/modules/portaudio/portaudio.c index cd4b973..b856502 100644 --- a/modules/portaudio/portaudio.c +++ b/modules/portaudio/portaudio.c @@ -99,6 +99,17 @@ static int write_callback(const void *inputBuffer, void *outputBuffer, } +static PaSampleFormat aufmt_to_pasampleformat(enum aufmt fmt) +{ + switch (fmt) { + + case AUFMT_S16LE: return paInt16; + case AUFMT_FLOAT: return paFloat32; + default: return 0; + } +} + + static int read_stream_open(struct ausrc_st *st, const struct ausrc_prm *prm, uint32_t dev) { @@ -109,7 +120,7 @@ static int read_stream_open(struct ausrc_st *st, const struct ausrc_prm *prm, memset(&prm_in, 0, sizeof(prm_in)); prm_in.device = dev; prm_in.channelCount = prm->ch; - prm_in.sampleFormat = paInt16; + prm_in.sampleFormat = aufmt_to_pasampleformat(prm->fmt); prm_in.suggestedLatency = 0.100; st->stream_rd = NULL; @@ -142,7 +153,7 @@ static int write_stream_open(struct auplay_st *st, memset(&prm_out, 0, sizeof(prm_out)); prm_out.device = dev; prm_out.channelCount = prm->ch; - prm_out.sampleFormat = paInt16; + prm_out.sampleFormat = aufmt_to_pasampleformat(prm->fmt); prm_out.suggestedLatency = 0.100; st->stream_wr = NULL; diff --git a/modules/pulse/player.c b/modules/pulse/player.c index 480ba6a..d65e7a8 100644 --- a/modules/pulse/player.c +++ b/modules/pulse/player.c @@ -18,8 +18,9 @@ struct auplay_st { pa_simple *s; pthread_t thread; bool run; - int16_t *sampv; + void *sampv; size_t sampc; + size_t sampsz; auplay_write_h *wh; void *arg; }; @@ -46,7 +47,7 @@ static void auplay_destructor(void *arg) static void *write_thread(void *arg) { struct auplay_st *st = arg; - const size_t num_bytes = st->sampc * 2; + const size_t num_bytes = st->sampc * st->sampsz; int ret, pa_error = 0; while (st->run) { @@ -64,6 +65,17 @@ static void *write_thread(void *arg) } +static int aufmt_to_pulse_format(enum aufmt fmt) +{ + switch (fmt) { + + case AUFMT_S16LE: return PA_SAMPLE_S16NE; + case AUFMT_FLOAT: return PA_SAMPLE_FLOAT32NE; + default: return 0; + } +} + + int pulse_player_alloc(struct auplay_st **stp, const struct auplay *ap, struct auplay_prm *prm, const char *device, auplay_write_h *wh, void *arg) @@ -88,14 +100,15 @@ int pulse_player_alloc(struct auplay_st **stp, const struct auplay *ap, st->arg = arg; st->sampc = prm->srate * prm->ch * prm->ptime / 1000; + st->sampsz = aufmt_sample_size(prm->fmt); - st->sampv = mem_alloc(2 * st->sampc, NULL); + st->sampv = mem_alloc(st->sampsz * st->sampc, NULL); if (!st->sampv) { err = ENOMEM; goto out; } - ss.format = PA_SAMPLE_S16NE; + ss.format = aufmt_to_pulse_format(prm->fmt); ss.channels = prm->ch; ss.rate = prm->srate; diff --git a/modules/pulse/recorder.c b/modules/pulse/recorder.c index 64df6c4..ca6fb6a 100644 --- a/modules/pulse/recorder.c +++ b/modules/pulse/recorder.c @@ -18,8 +18,10 @@ struct ausrc_st { pa_simple *s; pthread_t thread; bool run; - int16_t *sampv; + void *sampv; size_t sampc; + size_t sampsz; + uint32_t ptime; ausrc_read_h *rh; void *arg; }; @@ -46,8 +48,18 @@ static void ausrc_destructor(void *arg) static void *read_thread(void *arg) { struct ausrc_st *st = arg; - const size_t num_bytes = st->sampc * 2; + const size_t num_bytes = st->sampc * st->sampsz; int ret, pa_error = 0; + uint64_t now, last_read, diff; + unsigned dropped = 0; + bool init = true; + + if (pa_simple_flush(st->s, &pa_error)) { + warning("pulse: pa_simple_flush error (%s)\n", + pa_strerror(pa_error)); + } + + last_read = tmr_jiffies(); while (st->run) { @@ -58,6 +70,27 @@ static void *read_thread(void *arg) continue; } + /* Some devices might send a burst of samples right after the + initialization - filter them out */ + if (init) { + now = tmr_jiffies(); + diff = (now > last_read)? now - last_read : 0; + + if (diff < st->ptime / 2) { + last_read = now; + ++dropped; + continue; + } + else { + init = false; + + if (dropped) + debug("pulse: dropped %u frames of " + "garbage at the beginning of " + "the recording\n", dropped); + } + } + st->rh(st->sampv, st->sampc, st->arg); } @@ -65,6 +98,17 @@ static void *read_thread(void *arg) } +static int aufmt_to_pulse_format(enum aufmt fmt) +{ + switch (fmt) { + + case AUFMT_S16LE: return PA_SAMPLE_S16NE; + case AUFMT_FLOAT: return PA_SAMPLE_FLOAT32NE; + default: return 0; + } +} + + int pulse_recorder_alloc(struct ausrc_st **stp, const struct ausrc *as, struct media_ctx **ctx, struct ausrc_prm *prm, const char *device, @@ -95,14 +139,16 @@ int pulse_recorder_alloc(struct ausrc_st **stp, const struct ausrc *as, st->arg = arg; st->sampc = prm->srate * prm->ch * prm->ptime / 1000; + st->sampsz = aufmt_sample_size(prm->fmt); + st->ptime = prm->ptime; - st->sampv = mem_alloc(2 * st->sampc, NULL); + st->sampv = mem_alloc(st->sampsz * st->sampc, NULL); if (!st->sampv) { err = ENOMEM; goto out; } - ss.format = PA_SAMPLE_S16NE; + ss.format = aufmt_to_pulse_format(prm->fmt); ss.channels = prm->ch; ss.rate = prm->srate; diff --git a/modules/quicktime/module.mk b/modules/quicktime/module.mk deleted file mode 100644 index af67862..0000000 --- a/modules/quicktime/module.mk +++ /dev/null @@ -1,11 +0,0 @@ -# -# module.mk -# -# Copyright (C) 2010 Creytiv.com -# - -MOD := quicktime -$(MOD)_SRCS += quicktime.c -$(MOD)_LFLAGS += -framework QuickTime -lswscale - -include mk/mod.mk diff --git a/modules/quicktime/quicktime.c b/modules/quicktime/quicktime.c deleted file mode 100644 index 3811add..0000000 --- a/modules/quicktime/quicktime.c +++ /dev/null @@ -1,323 +0,0 @@ -/** - * @file quicktime.c Quicktime video-source - * - * Copyright (C) 2010 Creytiv.com - */ -#include <pthread.h> -#include <re.h> -#include <QuickTime/QuickTimeComponents.h> -#include <libavformat/avformat.h> -#include <libswscale/swscale.h> -#include <baresip.h> - - -/* this module is deprecated, in favour of qtcapture or avcapture */ - - -struct vidsrc_st { - const struct vidsrc *vs; /* inheritance */ - pthread_t thread; - pthread_mutex_t mutex; - struct vidsz sz; - SeqGrabComponent seq_grab; - SGDataUPP upp; - SGChannel ch; - struct mbuf *buf; - struct SwsContext *sws; - vidsrc_frame_h *frameh; - void *arg; - bool run; -}; - - -static struct vidsrc *vidsrc; - - -static void destructor(void *arg) -{ - struct vidsrc_st *st = arg; - - if (st->seq_grab) { - - pthread_mutex_lock(&st->mutex); - SGStop(st->seq_grab); - pthread_mutex_unlock(&st->mutex); - - if (st->run) { - pthread_join(st->thread, NULL); - } - - if (st->upp) { - DisposeSGDataUPP(st->upp); - } - - if (st->ch) { - SGDisposeChannel(st->seq_grab, st->ch); - } - - CloseComponent(st->seq_grab); - } - - if (st->sws) - sws_freeContext(st->sws); - - mem_deref(st->buf); -} - - -static OSErr frame_handler(SGChannel c, Ptr p, long len, long *offset, - long chRefCon, TimeValue timeval, short writeType, - long refCon) -{ - struct vidsrc_st *st = (struct vidsrc_st *)refCon; - ImageDescriptionHandle imageDesc; - AVPicture pict_src, pict_dst; - struct vidframe vidframe; - ComponentResult result; - int i, ret; - int new_len; - (void)c; - (void)p; - (void)len; - (void)offset; - (void)chRefCon; - (void)timeval; - (void)writeType; - (void)refCon; - - if (!st->buf) { - - imageDesc = (ImageDescriptionHandle)NewHandle(0); - if (!imageDesc) - return noErr; - - result = SGGetChannelSampleDescription(c,(Handle)imageDesc); - if (result != noErr) { - warning("quicktime: GetChanSampDesc: %d\n", result); - DisposeHandle((Handle)imageDesc); - return noErr; - } - - st->sz.w = (*imageDesc)->width; - st->sz.h = (*imageDesc)->height; - - /* buffer size after scaling */ - new_len = avpicture_get_size(PIX_FMT_YUV420P, - st->sz.w, st->sz.h); - -#if 1 - re_fprintf(stderr, "got frame len=%u (%ux%u) [%s] depth=%u\n", - len, st->sz.w, st->sz.h, - (*imageDesc)->name, (*imageDesc)->depth); -#endif - - DisposeHandle((Handle)imageDesc); - - st->buf = mbuf_alloc(new_len); - if (!st->buf) - return noErr; - } - - if (!st->sws) { - st->sws = sws_getContext(st->sz.w, st->sz.h, PIX_FMT_YUYV422, - st->sz.w, st->sz.h, PIX_FMT_YUV420P, - SWS_BICUBIC, NULL, NULL, NULL); - if (!st->sws) - return noErr; - } - - avpicture_fill(&pict_src, (uint8_t *)p, PIX_FMT_YUYV422, - st->sz.w, st->sz.h); - - avpicture_fill(&pict_dst, st->buf->buf, PIX_FMT_YUV420P, - st->sz.w, st->sz.h); - - ret = sws_scale(st->sws, - pict_src.data, pict_src.linesize, 0, st->sz.h, - pict_dst.data, pict_dst.linesize); - - if (ret <= 0) { - re_fprintf(stderr, "scale: sws_scale: returned %d\n", ret); - return noErr; - } - - for (i=0; i<4; i++) { - vidframe.data[i] = pict_dst.data[i]; - vidframe.linesize[i] = pict_dst.linesize[i]; - } - - vidframe.size = st->sz; - vidframe.valid = true; - - st->frameh(&vidframe, st->arg); - - return noErr; -} - - -static void *read_thread(void *arg) -{ - struct vidsrc_st *st = arg; - ComponentResult result; - - for (;;) { - pthread_mutex_lock(&st->mutex); - result = SGIdle(st->seq_grab); - pthread_mutex_unlock(&st->mutex); - - if (result != noErr) - break; - - usleep(10000); - } - - return NULL; -} - - -static int alloc(struct vidsrc_st **stp, const struct vidsrc *vs, - struct media_ctx **ctx, - struct vidsrc_prm *prm, const char *fmt, - const char *dev, vidsrc_frame_h *frameh, - vidsrc_error_h *errorh, void *arg) -{ - ComponentResult result; - Rect rect; - struct vidsrc_st *st; - int err; - - (void)ctx; - (void)fmt; - (void)dev; - (void)errorh; - - st = mem_zalloc(sizeof(*st), destructor); - if (!st) - return ENOMEM; - - st->vs = vs; - st->frameh = frameh; - st->arg = arg; - - err = pthread_mutex_init(&st->mutex, NULL); - if (err) { - re_fprintf(stderr, "mutex error: %s\n", strerror(err)); - goto out; - } - - st->seq_grab = OpenDefaultComponent(SeqGrabComponentType, 0); - if (!st->seq_grab) { - re_fprintf(stderr, "Unable to open component\n"); - err = ENODEV; - goto out; - } - - result = SGInitialize(st->seq_grab); - if (result != noErr) { - re_fprintf(stderr, "Unable to initialize sequence grabber\n"); - err = ENODEV; - goto out; - } - - result = SGSetGWorld(st->seq_grab, NULL, NULL); - if (result != noErr) { - re_fprintf(stderr, "Unable to set gworld\n"); - err = ENODEV; - goto out; - } - - result = SGSetDataRef(st->seq_grab, 0, 0, seqGrabDontMakeMovie); - if (result != noErr) { - re_fprintf(stderr, "Unable to set data ref\n"); - err = ENODEV; - goto out; - } - - result = SGNewChannel(st->seq_grab, VideoMediaType, &st->ch); - if (result != noErr) { - re_fprintf(stderr, "Unable to allocate channel (result=%d)\n", - result); - err = ENOMEM; - goto out; - } - - /* XXX: check flags */ - result = SGSetChannelUsage(st->ch, - seqGrabRecord | - seqGrabLowLatencyCapture); - if (result != noErr) { - re_fprintf(stderr, "Unable to set channel usage\n"); - err = ENODEV; - goto out; - } - - rect.top = 0; - rect.left = 0; - rect.bottom = prm->size.h; - rect.right = prm->size.w; - - result = SGSetChannelBounds(st->ch, &rect); - if (result != noErr) { - re_fprintf(stderr, "Unable to set channel bounds\n"); - err = ENODEV; - goto out; - } - - st->upp = NewSGDataUPP(frame_handler); - if (!st->upp) { - re_fprintf(stderr, "Unable to allocate data upp\n"); - err = ENOMEM; - goto out; - } - - result = SGSetDataProc(st->seq_grab, st->upp, (long)st); - if (result != noErr) { - re_fprintf(stderr, "Unable to sg callback\n"); - err = ENODEV; - goto out; - } - - result = SGStartRecord(st->seq_grab); - if (result != noErr) { - re_fprintf(stderr, "Unable to start record: %d\n", result); - err = ENODEV; - goto out; - } - - err = pthread_create(&st->thread, NULL, read_thread, st); - if (err) { - re_fprintf(stderr, "thread error: %s\n", strerror(err)); - goto out; - } - - st->run = true; - - out: - if (err) - mem_deref(st); - else - *stp = st; - return err; -} - - -static int qt_init(void) -{ - return vidsrc_register(&vidsrc, baresip_vidsrcl(), - "quicktime", alloc, NULL); -} - - -static int qt_close(void) -{ - vidsrc = mem_deref(vidsrc); - return 0; -} - - -EXPORT_SYM const struct mod_export DECL_EXPORTS(quicktime) = { - "quicktime", - "videosrc", - qt_init, - qt_close -}; diff --git a/modules/rst/audio.c b/modules/rst/audio.c index 666b09a..4bb7e0d 100644 --- a/modules/rst/audio.c +++ b/modules/rst/audio.c @@ -28,6 +28,7 @@ struct ausrc_st { bool run; uint32_t ptime; size_t sampc; + size_t sampsz; }; @@ -59,9 +60,10 @@ static void *play_thread(void *arg) { uint64_t now, ts = tmr_jiffies(); struct ausrc_st *st = arg; - int16_t *sampv; + void *sampv; + size_t num_bytes = st->sampc * st->sampsz; - sampv = mem_alloc(st->sampc * 2, NULL); + sampv = mem_alloc(num_bytes, NULL); if (!sampv) return NULL; @@ -80,7 +82,7 @@ static void *play_thread(void *arg) } #endif - aubuf_read_samp(st->aubuf, sampv, st->sampc); + aubuf_read(st->aubuf, sampv, num_bytes); st->rh(sampv, st->sampc, st->arg); @@ -148,6 +150,18 @@ void rst_audio_feed(struct ausrc_st *st, const uint8_t *buf, size_t sz) } +static int aufmt_to_encoding(enum aufmt fmt) +{ + switch (fmt) { + + case AUFMT_S16LE: return MPG123_ENC_SIGNED_16; + case AUFMT_FLOAT: return MPG123_ENC_FLOAT_32; + case AUFMT_S24_3LE: return MPG123_ENC_SIGNED_24; /* NOTE: endian */ + default: return 0; + } +} + + static int alloc_handler(struct ausrc_st **stp, const struct ausrc *as, struct media_ctx **ctx, struct ausrc_prm *prm, const char *dev, @@ -184,10 +198,12 @@ static int alloc_handler(struct ausrc_st **stp, const struct ausrc *as, /* Set wanted output format */ mpg123_format_none(st->mp3); - mpg123_format(st->mp3, prm->srate, prm->ch, MPG123_ENC_SIGNED_16); + mpg123_format(st->mp3, prm->srate, prm->ch, + aufmt_to_encoding(prm->fmt)); mpg123_volume(st->mp3, 0.3); st->sampc = prm->srate * prm->ch * prm->ptime / 1000; + st->sampsz = aufmt_sample_size(prm->fmt); st->ptime = prm->ptime; @@ -198,8 +214,8 @@ static int alloc_handler(struct ausrc_st **stp, const struct ausrc *as, /* 1 - 20 seconds of audio */ err = aubuf_alloc(&st->aubuf, - prm->srate * prm->ch * 2, - prm->srate * prm->ch * 40); + prm->srate * prm->ch * st->sampsz, + prm->srate * prm->ch * st->sampsz * 20); if (err) goto out; diff --git a/modules/sndio/sndio.c b/modules/sndio/sndio.c index d3ca9d6..6ac4b67 100644 --- a/modules/sndio/sndio.c +++ b/modules/sndio/sndio.c @@ -154,6 +154,12 @@ static int src_alloc(struct ausrc_st **stp, const struct ausrc *as, if (!stp || !as || !prm) return EINVAL; + if (prm->fmt != AUFMT_S16LE) { + warning("sndio: source: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + name = (str_isset(device)) ? device : SIO_DEVANY; if ((st = mem_zalloc(sizeof(*st), ausrc_destructor)) == NULL) @@ -222,6 +228,12 @@ static int play_alloc(struct auplay_st **stp, const struct auplay *ap, if (!stp || !ap || !prm) return EINVAL; + if (prm->fmt != AUFMT_S16LE) { + warning("sndio: playback: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + name = (str_isset(device)) ? device : SIO_DEVANY; if ((st = mem_zalloc(sizeof(*st), auplay_destructor)) == NULL) diff --git a/modules/v4l2/module.mk b/modules/v4l2/module.mk index 6fc9178..8fefae4 100644 --- a/modules/v4l2/module.mk +++ b/modules/v4l2/module.mk @@ -6,7 +6,7 @@ MOD := v4l2 $(MOD)_SRCS += v4l2.c -ifeq ($(HAVE_LIBV4L2),yes) +ifneq ($(HAVE_LIBV4L2),) $(MOD)_LFLAGS += -lv4l2 $(MOD)_CFLAGS += -DHAVE_LIBV4L2 endif diff --git a/modules/v4l2/v4l2.c b/modules/v4l2/v4l2.c index e079fed..983ad53 100644 --- a/modules/v4l2/v4l2.c +++ b/modules/v4l2/v4l2.c @@ -84,7 +84,7 @@ static enum vidfmt match_fmt(u_int32_t fmt) } -static void print_video_input(struct vidsrc_st *st) +static void print_video_input(const struct vidsrc_st *st) { struct v4l2_input input; @@ -398,6 +398,8 @@ static void destructor(void *arg) { struct vidsrc_st *st = arg; + debug("v4l2: stopping video source..\n"); + if (st->run) { st->run = false; pthread_join(st->thread, NULL); diff --git a/modules/vidloop/vidloop.c b/modules/vidloop/vidloop.c index 52f4359..d99fa49 100644 --- a/modules/vidloop/vidloop.c +++ b/modules/vidloop/vidloop.c @@ -285,7 +285,7 @@ static int enable_codec(struct video_loop *vl, const char *name) static void print_status(struct video_loop *vl) { - (void)re_fprintf(stderr, + (void)re_fprintf(stdout, "\rstatus:" " [%s] [%s] intra=%zu " " EFPS=%.1f %u kbit/s \r", @@ -293,6 +293,7 @@ static void print_status(struct video_loop *vl) vl->vc_dec ? vl->vc_dec->name : "", vl->stat.n_intra, vl->stat.efps, vl->stat.bitrate); + fflush(stdout); } diff --git a/modules/winwave/play.c b/modules/winwave/play.c index b9129fa..e987fc7 100644 --- a/modules/winwave/play.c +++ b/modules/winwave/play.c @@ -202,6 +202,12 @@ int winwave_play_alloc(struct auplay_st **stp, const struct auplay *ap, if (!stp || !ap || !prm) return EINVAL; + if (prm->fmt != AUFMT_S16LE) { + warning("winwave: playback: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + st = mem_zalloc(sizeof(*st), auplay_destructor); if (!st) return ENOMEM; diff --git a/modules/winwave/src.c b/modules/winwave/src.c index e96915e..6240899 100644 --- a/modules/winwave/src.c +++ b/modules/winwave/src.c @@ -201,6 +201,12 @@ int winwave_src_alloc(struct ausrc_st **stp, const struct ausrc *as, if (!stp || !as || !prm) return EINVAL; + if (prm->fmt != AUFMT_S16LE) { + warning("winwave: source: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + st = mem_zalloc(sizeof(*st), ausrc_destructor); if (!st) return ENOMEM; diff --git a/modules/zrtp/zrtp.c b/modules/zrtp/zrtp.c index 115db1c..28ae48f 100644 --- a/modules/zrtp/zrtp.c +++ b/modules/zrtp/zrtp.c @@ -26,6 +26,13 @@ * Thanks: * * Ingo Feinerer + * + * Configuration options: + * + \verbatim + zrtp_hash {yes,no} # Enable SDP zrtp-hash (recommended) + \endverbatim + * */ @@ -35,10 +42,14 @@ enum { struct menc_sess { zrtp_session_t *zrtp_session; + menc_error_h *errorh; + void *arg; + struct tmr abort_timer; + int err; }; struct menc_media { - const struct menc_sess *sess; + struct menc_sess *sess; struct udp_helper *uh_rtp; struct udp_helper *uh_rtcp; struct sa raddr; @@ -53,6 +64,10 @@ static zrtp_config_t zrtp_config; static zrtp_zid_t zid; +/* RFC 6189, section 8.1. */ +static bool use_sig_hash = true; + + enum pkt_type { PKT_TYPE_UNKNOWN = 0, PKT_TYPE_RTP = 1, @@ -93,6 +108,8 @@ static void session_destructor(void *arg) { struct menc_sess *st = arg; + tmr_cancel(&st->abort_timer); + if (st->zrtp_session) zrtp_session_down(st->zrtp_session); } @@ -112,6 +129,32 @@ static void media_destructor(void *arg) } +static void abort_timer_h(void *arg) +{ + struct menc_sess *sess = arg; + + if (sess->errorh) { + sess->errorh(sess->err, sess->arg); + sess->errorh = NULL; + } +} + + +static void abort_call(struct menc_sess *sess) +{ + if (!sess->err) { + sess->err = EPIPE; + tmr_start(&sess->abort_timer, 0, abort_timer_h, sess); + } +} + + +static bool drop_packets(const struct menc_media *st) +{ + return (st)? st->sess->err != 0 : true; +} + + static bool udp_helper_send(int *err, struct sa *dst, struct mbuf *mb, void *arg) { @@ -121,6 +164,9 @@ static bool udp_helper_send(int *err, struct sa *dst, const char *proto_name = "rtp"; enum pkt_type ptype = get_packet_type(mb); + if (drop_packets(st)) + return true; + length = (unsigned int)mbuf_get_left(mb); /* only RTP/RTCP packets should be processed */ @@ -168,6 +214,9 @@ static bool udp_helper_recv(struct sa *src, struct mbuf *mb, void *arg) const char *proto_name = "srtp"; enum pkt_type ptype = get_packet_type(mb); + if (drop_packets(st)) + return true; + length = (unsigned int)mbuf_get_left(mb); if (ptype == PKT_TYPE_RTCP) { @@ -198,6 +247,63 @@ static bool udp_helper_recv(struct sa *src, struct mbuf *mb, void *arg) } +static int sig_hash_encode(struct zrtp_stream_t *stream, + struct sdp_media *m) +{ + char buf[ZRTP_SIGN_ZRTP_HASH_LENGTH + 1]; + zrtp_status_t s; + int err = 0; + + s = zrtp_signaling_hash_get(stream, buf, sizeof(buf)); + if (s != zrtp_status_ok) { + warning("zrtp: zrtp_signaling_hash_get: status = %d\n", s); + return EINVAL; + } + + err = sdp_media_set_lattr(m, true, "zrtp-hash", "%s %s", + ZRTP_PROTOCOL_VERSION, buf); + if (err) { + warning("zrtp: sdp_media_set_lattr: %d\n", err); + } + + return err; +} + + +static void sig_hash_decode(struct zrtp_stream_t *stream, + const struct sdp_media *m) +{ + const char *attr_val; + struct pl major, minor, hash; + uint32_t version; + int err; + zrtp_status_t s; + + attr_val = sdp_media_rattr(m, "zrtp-hash"); + if (!attr_val) + return; + + err = re_regex(attr_val, strlen(attr_val), + "[0-9]+.[0-9]2 [0-9a-f]+", + &major, &minor, &hash); + if (err || hash.l < ZRTP_SIGN_ZRTP_HASH_LENGTH) { + warning("zrtp: malformed zrtp-hash attribute, ignoring...\n"); + return; + } + + version = pl_u32(&major) * 100 + pl_u32(&minor); + /* more version checks? */ + if (version < 110) { + warning("zrtp: zrtp-hash: version (%d) is too low, " + "ignoring...", version); + } + + s = zrtp_signaling_hash_set(stream, hash.p, (uint32_t)hash.l); + if (s != zrtp_status_ok) + warning("zrtp: zrtp_signaling_hash_set: status = %d\n", s); +} + + static int session_alloc(struct menc_sess **sessp, struct sdp_session *sdp, bool offerer, menc_error_h *errorh, void *arg) { @@ -205,8 +311,6 @@ static int session_alloc(struct menc_sess **sessp, struct sdp_session *sdp, zrtp_status_t s; int err = 0; (void)offerer; - (void)errorh; - (void)arg; if (!sessp || !sdp) return EINVAL; @@ -215,6 +319,11 @@ static int session_alloc(struct menc_sess **sessp, struct sdp_session *sdp, if (!st) return ENOMEM; + st->errorh = errorh; + st->arg = arg; + st->err = 0; + tmr_init(&st->abort_timer); + s = zrtp_session_init(zrtp_global, NULL, zid, ZRTP_SIGNALING_ROLE_UNKNOWN, &st->zrtp_session); if (s != zrtp_status_ok) { @@ -277,6 +386,12 @@ static int media_alloc(struct menc_media **stp, struct menc_sess *sess, zrtp_stream_set_userdata(st->zrtp_stream, st); + if (use_sig_hash) { + err = sig_hash_encode(st->zrtp_stream, sdpm); + if (err) + goto out; + } + out: if (err) { mem_deref(st); @@ -289,6 +404,9 @@ static int media_alloc(struct menc_media **stp, struct menc_sess *sess, if (sa_isset(sdp_media_raddr(sdpm), SA_ALL)) { st->raddr = *sdp_media_raddr(sdpm); + if (use_sig_hash) + sig_hash_decode(st->zrtp_stream, sdpm); + s = zrtp_stream_start(st->zrtp_stream, rtp_sess_ssrc(rtp)); if (s != zrtp_status_ok) { warning("zrtp: zrtp_stream_start: status = %d\n", s); @@ -307,6 +425,9 @@ static int on_send_packet(const zrtp_stream_t *stream, struct mbuf *mb; int err; + if (drop_packets(st)) + return zrtp_status_ok; + if (!sa_isset(&st->raddr, SA_ALL)) return zrtp_status_ok; @@ -355,6 +476,23 @@ static void on_zrtp_secure(zrtp_stream_t *stream) } +static void on_zrtp_security_event(zrtp_stream_t *stream, + zrtp_security_event_t event) +{ + if (event == ZRTP_EVENT_WRONG_SIGNALING_HASH) { + const struct menc_media *st = zrtp_stream_get_userdata(stream); + + warning("zrtp: Attack detected!!! Signaling hash from the " + "zrtp-hash SDP attribute doesn't match the hash of " + "the Hello message. Aborting the call.\n"); + + /* As this was called from zrtp_process_xxx(), we need + a safe shutdown. */ + abort_call(st->sess); + } +} + + static struct menc menc_zrtp = { LE_INIT, "zrtp", "RTP/AVP", session_alloc, media_alloc }; @@ -428,6 +566,8 @@ static int module_init(void) FILE *f; int ret, err; + (void)conf_get_bool(conf_cur(), "zrtp_hash", &use_sig_hash); + zrtp_log_set_log_engine(zrtp_log); zrtp_config_defaults(&zrtp_config); @@ -439,6 +579,8 @@ static int module_init(void) zrtp_config.cb.misc_cb.on_send_packet = on_send_packet; zrtp_config.cb.event_cb.on_zrtp_secure = on_zrtp_secure; + zrtp_config.cb.event_cb.on_zrtp_security_event = + on_zrtp_security_event; err = conf_path_get(config_path, sizeof(config_path)); if (err) { diff --git a/rpm/baresip.spec b/rpm/baresip.spec index d614502..de6170f 100644 --- a/rpm/baresip.spec +++ b/rpm/baresip.spec @@ -1,5 +1,5 @@ %define name baresip -%define ver 0.5.6 +%define ver 0.5.7 %define rel 1 Summary: Modular SIP useragent diff --git a/src/account.c b/src/account.c index 8decb2e..2a99e58 100644 --- a/src/account.c +++ b/src/account.c @@ -362,7 +362,7 @@ int account_alloc(struct account **accp, const char *sipaddr) pl_set_str(&pl, acc->buf); err = sip_addr_decode(&acc->laddr, &pl); if (err) { - warning("account: invalid SIP address: `%r'\n", &pl); + warning("account: error parsing SIP address: '%r'\n", &pl); goto out; } diff --git a/src/audio.c b/src/audio.c index 116ffaa..17eb5f6 100644 --- a/src/audio.c +++ b/src/audio.c @@ -80,6 +80,8 @@ struct autx { const struct aucodec *ac; /**< Current audio encoder */ struct auenc_state *enc; /**< Audio encoder state (optional) */ struct aubuf *aubuf; /**< Packetize outgoing stream */ + size_t aubuf_maxsz; /**< Maximum aubuf size in [bytes] */ + volatile bool aubuf_started; struct auresamp resamp; /**< Optional resampler for DSP */ struct list filtl; /**< Audio filters in encoding order */ struct mbuf *mb; /**< Buffer for outgoing RTP packets */ @@ -87,22 +89,29 @@ struct autx { int16_t *sampv; /**< Sample buffer */ int16_t *sampv_rs; /**< Sample buffer for resampler */ uint32_t ptime; /**< Packet time for sending */ - uint32_t ts; /**< Timestamp for outgoing RTP */ + uint64_t ts_ext; /**< Ext. Timestamp for outgoing RTP */ + uint32_t ts_base; uint32_t ts_tel; /**< Timestamp for Telephony Events */ size_t psize; /**< Packet size for sending */ bool marker; /**< Marker bit for outgoing RTP */ bool muted; /**< Audio source is muted */ int cur_key; /**< Currently transmitted event */ + enum aufmt src_fmt; + bool need_conv; + + struct { + uint64_t aubuf_overrun; + uint64_t aubuf_underrun; + } stats; - union { - struct tmr tmr; /**< Timer for sending RTP packets */ #ifdef HAVE_PTHREAD + union { struct { pthread_t tid;/**< Audio transmit thread */ bool run; /**< Audio transmit thread running */ } thr; -#endif } u; +#endif }; @@ -127,6 +136,8 @@ struct aurx { const struct aucodec *ac; /**< Current audio decoder */ struct audec_state *dec; /**< Audio decoder state (optional) */ struct aubuf *aubuf; /**< Incoming audio buffer */ + size_t aubuf_maxsz; /**< Maximum aubuf size in [bytes] */ + volatile bool aubuf_started; struct auresamp resamp; /**< Optional resampler for DSP */ struct list filtl; /**< Audio filters in decoding order */ char device[64]; /**< Audio player device name */ @@ -136,6 +147,15 @@ struct aurx { int pt; /**< Payload type for incoming RTP */ double level_last; bool level_set; + enum aufmt play_fmt; + bool need_conv; + struct timestamp_recv ts_recv; + uint64_t n_discard; + + struct { + uint64_t aubuf_overrun; + uint64_t aubuf_underrun; + } stats; }; @@ -160,6 +180,43 @@ struct audio { static const char *uri_aulevel = "urn:ietf:params:rtp-hdrext:ssrc-audio-level"; +static double audio_calc_seconds(uint64_t rtp_ts, uint32_t clock_rate) +{ + double timestamp; + + /* convert from RTP clockrate to seconds */ + timestamp = (double)rtp_ts / (double)clock_rate; + + return timestamp; +} + + +static double autx_calc_seconds(const struct autx *autx) +{ + uint64_t dur; + + if (!autx->ac) + return .0; + + dur = autx->ts_ext - autx->ts_base; + + return audio_calc_seconds(dur, autx->ac->crate); +} + + +static double aurx_calc_seconds(const struct aurx *aurx) +{ + uint64_t dur; + + if (!aurx->ac) + return .0; + + dur = timestamp_duration(&aurx->ts_recv); + + return audio_calc_seconds(dur, aurx->ac->crate); +} + + static void stop_tx(struct autx *tx, struct audio *a) { if (!tx || !a) @@ -169,17 +226,12 @@ static void stop_tx(struct autx *tx, struct audio *a) #ifdef HAVE_PTHREAD case AUDIO_MODE_THREAD: - case AUDIO_MODE_THREAD_REALTIME: if (tx->u.thr.run) { tx->u.thr.run = false; pthread_join(tx->u.thr.tid, NULL); } break; #endif - case AUDIO_MODE_TMR: - tmr_cancel(&tx->u.tmr); - break; - default: break; } @@ -246,6 +298,16 @@ static inline uint32_t calc_nsamp(uint32_t srate, uint8_t channels, } +static inline double calc_ptime(size_t nsamp, uint32_t srate, uint8_t channels) +{ + double ptime; + + ptime = 1000.0 * (double)nsamp / (double)(srate * channels); + + return ptime; +} + + /** * Get the DSP samplerate for an audio-codec (exception for G.722 and MPA) */ @@ -389,7 +451,7 @@ static void encode_rtp_send(struct audio *a, struct autx *tx, err = tx->ac->ench(tx->enc, mbuf_buf(tx->mb), &len, sampv, sampc); if ((err & 0xffff0000) == 0x00010000) { /* MPA needs some special treatment here */ - tx->ts = err & 0xffff; + tx->ts_ext = err & 0xffff; err = 0; } else if (err) { @@ -403,9 +465,11 @@ static void encode_rtp_send(struct audio *a, struct autx *tx, if (mbuf_get_left(tx->mb)) { + uint32_t rtp_ts = tx->ts_ext & 0xffffffff; + if (len) { err = stream_send(a->strm, ext_len!=0, tx->marker, -1, - tx->ts, tx->mb); + rtp_ts, tx->mb); if (err) goto out; } @@ -421,7 +485,7 @@ static void encode_rtp_send(struct audio *a, struct autx *tx, */ frame_size = sampc_rtp / get_ch(tx->ac); - tx->ts += (uint32_t)frame_size; + tx->ts_ext += (uint32_t)frame_size; out: tx->marker = false; @@ -436,13 +500,46 @@ static void poll_aubuf_tx(struct audio *a) struct autx *tx = &a->tx; int16_t *sampv = tx->sampv; size_t sampc; + size_t sz; + size_t num_bytes; struct le *le; int err = 0; - sampc = tx->psize / 2; + sz = aufmt_sample_size(tx->src_fmt); + if (!sz) + return; + + num_bytes = tx->psize; + sampc = tx->psize / sz; /* timed read from audio-buffer */ - aubuf_read_samp(tx->aubuf, tx->sampv, sampc); + + if (tx->src_fmt == AUFMT_S16LE) { + + aubuf_read(tx->aubuf, (uint8_t *)tx->sampv, num_bytes); + } + else { + /* Convert from ausrc format to 16-bit format */ + + void *tmp_sampv; + + if (!tx->need_conv) { + info("audio: NOTE: source sample conversion" + " needed: %s --> %s\n", + aufmt_name(tx->src_fmt), aufmt_name(AUFMT_S16LE)); + tx->need_conv = true; + } + + tmp_sampv = mem_zalloc(num_bytes, NULL); + if (!tmp_sampv) + return; + + aubuf_read(tx->aubuf, tmp_sampv, num_bytes); + + auconv_to_s16(sampv, tx->src_fmt, tmp_sampv, sampc); + + mem_deref(tmp_sampv); + } /* optional resampler */ if (tx->resamp.resample) { @@ -487,7 +584,7 @@ static void check_telev(struct audio *a, struct autx *tx) return; if (marker) - tx->ts_tel = tx->ts; + tx->ts_tel = (uint32_t)tx->ts_ext; fmt = sdp_media_rformat(stream_sdpmedia(audio_strm(a)), telev_rtpfmt); if (!fmt) @@ -511,15 +608,26 @@ static void check_telev(struct audio *a, struct autx *tx) * * @note This function may be called from any thread * + * @note The sample format is set in rx->play_fmt + * * @param buf Buffer to fill with audio samples * @param sz Number of bytes in buffer * @param arg Handler argument */ -static void auplay_write_handler(int16_t *sampv, size_t sampc, void *arg) +static void auplay_write_handler(void *sampv, size_t sampc, void *arg) { struct aurx *rx = arg; + size_t num_bytes = sampc * aufmt_sample_size(rx->play_fmt); + + if (rx->aubuf_started && aubuf_cur_size(rx->aubuf) < num_bytes) { + + ++rx->stats.aubuf_underrun; - aubuf_read_samp(rx->aubuf, sampv, sampc); + debug("audio: rx aubuf underrun (total %llu)\n", + rx->stats.aubuf_underrun); + } + + aubuf_read(rx->aubuf, sampv, num_bytes); } @@ -534,15 +642,26 @@ static void auplay_write_handler(int16_t *sampv, size_t sampc, void *arg) * @param sz Number of bytes in buffer * @param arg Handler argument */ -static void ausrc_read_handler(const int16_t *sampv, size_t sampc, void *arg) +static void ausrc_read_handler(const void *sampv, size_t sampc, void *arg) { struct audio *a = arg; struct autx *tx = &a->tx; + size_t num_bytes = sampc * aufmt_sample_size(tx->src_fmt); if (tx->muted) - memset((void *)sampv, 0, sampc*2); + memset((void *)sampv, 0, num_bytes); + + if (aubuf_cur_size(tx->aubuf) >= tx->aubuf_maxsz) { + + ++tx->stats.aubuf_overrun; + + debug("audio: tx aubuf overrun (total %llu)\n", + tx->stats.aubuf_overrun); + } - (void)aubuf_write_samp(tx->aubuf, sampv, sampc); + (void)aubuf_write(tx->aubuf, sampv, num_bytes); + + tx->aubuf_started = true; if (a->cfg.txmode == AUDIO_MODE_POLL) { unsigned i; @@ -662,9 +781,49 @@ static int aurx_stream_decode(struct aurx *rx, struct mbuf *mb) sampc = sampc_rs; } - err = aubuf_write_samp(rx->aubuf, sampv, sampc); - if (err) - goto out; + if (aubuf_cur_size(rx->aubuf) >= rx->aubuf_maxsz) { + + ++rx->stats.aubuf_overrun; + + debug("audio: rx aubuf overrun (total %llu)\n", + rx->stats.aubuf_overrun); + } + + if (rx->play_fmt == AUFMT_S16LE) { + err = aubuf_write_samp(rx->aubuf, sampv, sampc); + if (err) + goto out; + } + else { + + /* Convert from 16-bit to auplay format */ + + void *tmp_sampv; + size_t num_bytes = sampc * aufmt_sample_size(rx->play_fmt); + + if (!rx->need_conv) { + info("audio: NOTE: playback sample conversion" + " needed: %s --> %s\n", + aufmt_name(AUFMT_S16LE), + aufmt_name(rx->play_fmt)); + rx->need_conv = true; + } + + tmp_sampv = mem_zalloc(num_bytes, NULL); + if (!tmp_sampv) + return ENOMEM; + + auconv_from_s16(rx->play_fmt, tmp_sampv, sampv, sampc); + + err = aubuf_write(rx->aubuf, tmp_sampv, num_bytes); + + mem_deref(tmp_sampv); + + if (err) + goto out; + } + + rx->aubuf_started = true; out: return err; @@ -678,7 +837,9 @@ static void stream_recv_handler(const struct rtp_header *hdr, { struct audio *a = arg; struct aurx *rx = &a->rx; + bool discard = false; size_t i; + int wrap; int err; if (!mb) @@ -723,6 +884,71 @@ static void stream_recv_handler(const struct rtp_header *hdr, } } + /* Save timestamp for incoming RTP packets */ + + if (rx->ts_recv.is_set) { + + uint64_t ext_last, ext_now; + + ext_last = calc_extended_timestamp(rx->ts_recv.num_wraps, + rx->ts_recv.last); + + ext_now = calc_extended_timestamp(rx->ts_recv.num_wraps, + hdr->ts); + + if (ext_now <= ext_last) { + uint64_t delta; + + delta = ext_last - ext_now; + + warning("audio: [time=%.3f]" + " discard old frame (%.3f seconds old)\n", + aurx_calc_seconds(rx), + audio_calc_seconds(delta, rx->ac->crate)); + + discard = true; + } + } + else { + rx->ts_recv.first = hdr->ts; + rx->ts_recv.last = hdr->ts; + rx->ts_recv.is_set = true; + } + + wrap = timestamp_wrap(hdr->ts, rx->ts_recv.last); + + switch (wrap) { + + case -1: + warning("audio: rtp timestamp wraps backwards" + " (delta = %d) -- discard\n", + (int32_t)(rx->ts_recv.last - hdr->ts)); + discard = true; + break; + + case 0: + break; + + case 1: + ++rx->ts_recv.num_wraps; + break; + + default: + break; + } + + rx->ts_recv.last = hdr->ts; + +#if 0 + re_printf("[time=%.3f] wrap=%d discard=%d\n", + aurx_calc_seconds(rx), wrap, discard); +#endif + + if (discard) { + ++a->rx.n_discard; + return; + } + out: (void)aurx_stream_decode(&a->rx, mb); } @@ -746,6 +972,60 @@ static int add_telev_codec(struct audio *a) } +/* + * EBU ACIP (Audio Contribution over IP) Profile + * + * Ref: https://tech.ebu.ch/docs/tech/tech3368.pdf + */ +static int set_ebuacip_params(struct audio *au, uint32_t ptime) +{ + struct sdp_media *sdp = stream_sdpmedia(au->strm); + const struct config_avt *avt = &au->strm->cfg; + char str[64]; + int jbvalue = 0; + int jb_id = 0; + int err = 0; + + /* set ebuacip version fixed value 0 for now. */ + err |= sdp_media_set_lattr(sdp, false, "ebuacip", "version %i", 0); + + /* set jb option, only one in our case */ + err |= sdp_media_set_lattr(sdp, false, "ebuacip", "jb %i", jb_id); + + /* define jb value in option */ + if (0 == conf_get_str(conf_cur(), "ebuacip_jb_type",str,sizeof(str))) { + + if (0 == str_cmp(str, "auto")) { + + err |= sdp_media_set_lattr(sdp, false, + "ebuacip", + "jbdef %i auto %d-%d", + jb_id, + avt->jbuf_del.min * ptime, + avt->jbuf_del.max * ptime); + } + else if (0 == str_cmp(str, "fixed")) { + + /* define jb value in option */ + jbvalue = avt->jbuf_del.max * ptime; + + err |= sdp_media_set_lattr(sdp, false, + "ebuacip", + "jbdef %i fixed %d", + jb_id, jbvalue); + } + } + + /* set QOS recomendation use tos / 4 to set DSCP value */ + err |= sdp_media_set_lattr(sdp, false, "ebuacip", "qosrec %u", + avt->rtp_tos / 4); + + /* EBU ACIP FEC:: NOT SET IN BARESIP */ + + return err; +} + + int audio_alloc(struct audio **ap, const struct stream_param *stream_prm, const struct config *cfg, struct call *call, struct sdp_session *sdp_sess, int label, @@ -774,6 +1054,9 @@ int audio_alloc(struct audio **ap, const struct stream_param *stream_prm, tx = &a->tx; rx = &a->rx; + tx->src_fmt = cfg->audio.src_fmt; + rx->play_fmt = cfg->audio.play_fmt; + err = stream_alloc(&a->strm, stream_prm, &cfg->avt, call, sdp_sess, "audio", label, mnat, mnat_sess, menc, menc_sess, @@ -803,6 +1086,13 @@ int audio_alloc(struct audio **ap, const struct stream_param *stream_prm, goto out; } + if (cfg->sdp.ebuacip) { + + err = set_ebuacip_params(a, ptime); + 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); @@ -811,8 +1101,8 @@ int audio_alloc(struct audio **ap, const struct stream_param *stream_prm, } tx->mb = mbuf_alloc(STREAM_PRESZ + 4096); - tx->sampv = mem_zalloc(AUDIO_SAMPSZ * 2, NULL); - rx->sampv = mem_zalloc(AUDIO_SAMPSZ * 2, NULL); + tx->sampv = mem_zalloc(AUDIO_SAMPSZ * sizeof(int16_t), NULL); + rx->sampv = mem_zalloc(AUDIO_SAMPSZ * sizeof(int16_t), NULL); if (!tx->mb || !tx->sampv || !rx->sampv) { err = ENOMEM; goto out; @@ -829,7 +1119,7 @@ int audio_alloc(struct audio **ap, const struct stream_param *stream_prm, auresamp_init(&tx->resamp); str_ncpy(tx->device, a->cfg.src_dev, sizeof(tx->device)); tx->ptime = ptime; - tx->ts = rand_u16(); + tx->ts_ext = tx->ts_base = rand_u16(); tx->marker = true; auresamp_init(&rx->resamp); @@ -841,9 +1131,6 @@ int audio_alloc(struct audio **ap, const struct stream_param *stream_prm, a->errh = errh; a->arg = arg; - if (a->cfg.txmode == AUDIO_MODE_TMR) - tmr_init(&tx->u.tmr); - out: if (err) mem_deref(a); @@ -859,46 +1146,46 @@ static void *tx_thread(void *arg) { struct audio *a = arg; struct autx *tx = &a->tx; - unsigned i; - - /* Enable Real-time mode for this thread, if available */ - if (a->cfg.txmode == AUDIO_MODE_THREAD_REALTIME) - (void)realtime_enable(true, 1); + uint64_t ts = 0; while (a->tx.u.thr.run) { - for (i=0; i<16; i++) { + uint64_t now; - if (aubuf_cur_size(tx->aubuf) < tx->psize) - break; + sys_msleep(4); - poll_aubuf_tx(a); - } + if (!tx->aubuf_started) + continue; - sys_msleep(5); - } + if (!a->tx.u.thr.run) + break; - return NULL; -} -#endif + now = tmr_jiffies(); + if (!ts) + ts = now; + if (ts > now) + continue; -static void timeout_tx(void *arg) -{ - struct audio *a = arg; - struct autx *tx = &a->tx; - unsigned i; + /* Now is the time to send */ - tmr_start(&a->tx.u.tmr, 5, timeout_tx, a); + if (aubuf_cur_size(tx->aubuf) >= tx->psize) { - for (i=0; i<16; i++) { + poll_aubuf_tx(a); + } + else { + ++tx->stats.aubuf_underrun; - if (aubuf_cur_size(tx->aubuf) < tx->psize) - break; + debug("audio: thread: tx aubuf underrun" + " (total %llu)\n", tx->stats.aubuf_underrun); + } - poll_aubuf_tx(a); + ts += tx->ptime; } + + return NULL; } +#endif static void aufilt_param_set(struct aufilt_prm *prm, @@ -1079,13 +1366,18 @@ static int start_player(struct aurx *rx, struct audio *a) prm.srate = srate_dsp; prm.ch = channels_dsp; prm.ptime = rx->ptime; + prm.fmt = rx->play_fmt; if (!rx->aubuf) { size_t psize; + size_t sz = aufmt_sample_size(rx->play_fmt); - psize = 2 * calc_nsamp(prm.srate, prm.ch, prm.ptime); + psize = sz * calc_nsamp(prm.srate, prm.ch, prm.ptime); - err = aubuf_alloc(&rx->aubuf, psize * 1, psize * 8); + rx->aubuf_maxsz = psize * 8; + + err = aubuf_alloc(&rx->aubuf, psize * 1, + rx->aubuf_maxsz); if (err) return err; } @@ -1101,6 +1393,9 @@ static int start_player(struct aurx *rx, struct audio *a) } rx->auplay_prm = prm; + + info("audio: player started with sample format %s\n", + aufmt_name(rx->play_fmt)); } return 0; @@ -1154,16 +1449,22 @@ static int start_source(struct autx *tx, struct audio *a) if (!tx->ausrc && ausrc_find(baresip_ausrcl(), NULL)) { struct ausrc_prm prm; + size_t sz; prm.srate = srate_dsp; prm.ch = channels_dsp; prm.ptime = tx->ptime; + prm.fmt = tx->src_fmt; + + sz = aufmt_sample_size(tx->src_fmt); - tx->psize = 2 * calc_nsamp(prm.srate, prm.ch, prm.ptime); + tx->psize = sz * calc_nsamp(prm.srate, prm.ch, prm.ptime); + + tx->aubuf_maxsz = tx->psize * 30; if (!tx->aubuf) { - err = aubuf_alloc(&tx->aubuf, tx->psize * 2, - tx->psize * 30); + err = aubuf_alloc(&tx->aubuf, tx->psize, + tx->aubuf_maxsz); if (err) return err; } @@ -1181,7 +1482,6 @@ static int start_source(struct autx *tx, struct audio *a) switch (a->cfg.txmode) { #ifdef HAVE_PTHREAD case AUDIO_MODE_THREAD: - case AUDIO_MODE_THREAD_REALTIME: if (!tx->u.thr.run) { tx->u.thr.run = true; err = pthread_create(&tx->u.thr.tid, NULL, @@ -1194,15 +1494,14 @@ static int start_source(struct autx *tx, struct audio *a) break; #endif - case AUDIO_MODE_TMR: - tmr_start(&tx->u.tmr, 1, timeout_tx, a); - break; - default: break; } tx->ausrc_prm = prm; + + info("audio: source started with sample format %s\n", + aufmt_name(tx->src_fmt)); } return 0; @@ -1578,6 +1877,7 @@ int audio_debug(struct re_printf *pf, const struct audio *a) { const struct autx *tx; const struct aurx *rx; + size_t sztx, szrx; int err; if (!a) @@ -1586,21 +1886,60 @@ int audio_debug(struct re_printf *pf, const struct audio *a) tx = &a->tx; rx = &a->rx; + sztx = aufmt_sample_size(tx->src_fmt); + szrx = aufmt_sample_size(rx->play_fmt); + err = re_hprintf(pf, "\n--- Audio stream ---\n"); - err |= re_hprintf(pf, " tx: %H %H ptime=%ums\n", + err |= re_hprintf(pf, " tx: %H ptime=%ums\n", aucodec_print, tx->ac, - aubuf_debug, tx->aubuf, tx->ptime); + err |= re_hprintf(pf, " aubuf: %H" + " (cur %.2fms, max %.2fms, or %llu, ur %llu)\n", + aubuf_debug, tx->aubuf, + calc_ptime(aubuf_cur_size(tx->aubuf)/sztx, + tx->ausrc_prm.srate, + tx->ausrc_prm.ch), + calc_ptime(tx->aubuf_maxsz/sztx, + tx->ausrc_prm.srate, + tx->ausrc_prm.ch), + tx->stats.aubuf_overrun, + tx->stats.aubuf_underrun); + + err |= re_hprintf(pf, " time = %.3f sec\n", + autx_calc_seconds(tx)); - err |= re_hprintf(pf, " rx: %H %H ptime=%ums pt=%d\n", + err |= re_hprintf(pf, + " rx: %H\n" + " ptime=%ums pt=%d\n", aucodec_print, rx->ac, - aubuf_debug, rx->aubuf, rx->ptime, rx->pt); + err |= re_hprintf(pf, " aubuf: %H" + " (cur %.2fms, max %.2fms, or %llu, ur %llu)\n", + aubuf_debug, rx->aubuf, + calc_ptime(aubuf_cur_size(rx->aubuf)/szrx, + rx->auplay_prm.srate, + rx->auplay_prm.ch), + calc_ptime(rx->aubuf_maxsz/szrx, + rx->auplay_prm.srate, + rx->auplay_prm.ch), + rx->stats.aubuf_overrun, + rx->stats.aubuf_underrun + ); + + err |= re_hprintf(pf, " n_discard:%llu\n", + rx->n_discard); if (rx->level_set) { err |= re_hprintf(pf, " level %.3f dBov\n", rx->level_last); } + if (rx->ts_recv.is_set) { + err |= re_hprintf(pf, " time = %.3f sec\n", + aurx_calc_seconds(rx)); + } + else { + err |= re_hprintf(pf, " time = (not started)\n"); + } err |= re_hprintf(pf, " %H" @@ -85,6 +85,14 @@ static int ctx_alloc(struct cmd_ctx **ctxp, const struct cmd *cmd) } +/** + * Find a command block + * + * @param commands Commands container + * @param cmdv Command vector + * + * @return Command block if found, otherwise NULL + */ struct cmds *cmds_find(const struct commands *commands, const struct cmd *cmdv) { @@ -291,6 +299,17 @@ static int cmd_report(const struct cmd *cmd, struct re_printf *pf, } +/** + * Process long commands + * + * @param commands Commands container + * @param str Input string + * @param len Length of input string + * @param pf_resp Print function for response + * @param data Application data + * + * @return 0 if success, otherwise errorcode + */ int cmd_process_long(struct commands *commands, const char *str, size_t len, struct re_printf *pf_resp, void *data) { @@ -455,6 +474,14 @@ void cmd_unregister(struct commands *commands, const struct cmd *cmdv) } +/** + * Find a long command + * + * @param commands Commands container + * @param name Name of command, excluding prefix + * + * @return Command if found, NULL if not found + */ const struct cmd *cmd_find_long(const struct commands *commands, const char *name) { @@ -715,6 +742,13 @@ int cmd_print(struct re_printf *pf, const struct commands *commands) } +/** + * Initialize the commands subsystem. + * + * @param commandsp Pointer to allocated commands + * + * @return 0 if success, otherwise errorcode + */ int cmd_init(struct commands **commandsp) { struct commands *commands; @@ -369,7 +369,7 @@ int conf_modules(void) * * @return Config object * - * @note It is only available during init + * @note It is only available after init and before conf_close() */ struct conf *conf_cur(void) { diff --git a/src/config.c b/src/config.c index eb83c67..ce748d3 100644 --- a/src/config.c +++ b/src/config.c @@ -56,6 +56,8 @@ static struct config core_config = { false, AUDIO_MODE_POLL, false, + AUFMT_S16LE, + AUFMT_S16LE, }, #ifdef USE_VIDEO @@ -95,6 +97,11 @@ static struct config core_config = { "" }, #endif + + /* SDP */ + { + false + }, }; @@ -134,11 +141,57 @@ static int dns_server_handler(const struct pl *pl, void *arg) } +static enum aufmt resolve_aufmt(const struct pl *fmt) +{ + if (0 == pl_strcasecmp(fmt, "s16")) return AUFMT_S16LE; + if (0 == pl_strcasecmp(fmt, "float")) return AUFMT_FLOAT; + if (0 == pl_strcasecmp(fmt, "s24_3le")) return AUFMT_S24_3LE; + + /* XXX remove this after librem is fixed */ + if (0 == pl_strcasecmp(fmt, "s16le")) return AUFMT_S16LE; + + return (enum aufmt)-1; +} + + +static int conf_get_aufmt(const struct conf *conf, const char *name, + int *fmtp) +{ + struct pl pl; + int fmt; + int err; + + err = conf_get(conf, name, &pl); + if (err) + return err; + + fmt = resolve_aufmt(&pl); + if (fmt == -1) { + warning("config: %s: sample format not supported" + " (%r)\n", name, &pl); + return EINVAL; + } + + *fmtp = fmt; + + return 0; +} + + +/** + * Parse the core configuration file and update baresip core config + * + * @param cfg Baresip core config to update + * @param conf Configuration file to parse + * + * @return 0 if success, otherwise errorcode + */ int config_parse_conf(struct config *cfg, const struct conf *conf) { struct pl pollm, as, ap; enum poll_method method; struct vidsz size = {0, 0}; + struct pl txmode; uint32_t v; int err = 0; @@ -202,8 +255,22 @@ 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; + if (0 == conf_get(conf, "audio_txmode", &txmode)) { + + if (0 == pl_strcasecmp(&txmode, "poll")) + cfg->audio.txmode = AUDIO_MODE_POLL; + else if (0 == pl_strcasecmp(&txmode, "thread")) + cfg->audio.txmode = AUDIO_MODE_THREAD; + else { + warning("unsupported audio txmode (%r)\n", &txmode); + } + } + (void)conf_get_bool(conf, "audio_level", &cfg->audio.level); + conf_get_aufmt(conf, "ausrc_format", &cfg->audio.src_fmt); + conf_get_aufmt(conf, "auplay_format", &cfg->audio.play_fmt); + #ifdef USE_VIDEO /* Video */ (void)conf_get_csv(conf, "video_source", @@ -254,10 +321,21 @@ int config_parse_conf(struct config *cfg, const struct conf *conf) sizeof(cfg->bfcp.proto)); #endif + /* SDP */ + (void)conf_get_bool(conf, "sdp_ebuacip", &cfg->sdp.ebuacip); + return err; } +/** + * Print the baresip core config + * + * @param pf Print function + * @param cfg Baresip core config + * + * @return 0 if success, otherwise errorcode + */ int config_print(struct re_printf *pf, const struct config *cfg) { int err; @@ -459,7 +537,10 @@ 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_txmode\t\tpoll\t\t# poll, thread\n" "audio_level\t\tno\n" + "ausrc_format\t\ts16\t\t# s16, float, ..\n" + "auplay_format\t\ts16\t\t# s16, float, ..\n" , poll_method_name(poll_method_best()), cfg->call.local_timeout, @@ -468,7 +549,8 @@ static int core_config_template(struct re_printf *pf, const struct config *cfg) default_audio_device(), default_audio_device(), cfg->audio.srate.min, cfg->audio.srate.max, - cfg->audio.channels.min, cfg->audio.channels.max); + cfg->audio.channels.min, cfg->audio.channels.max + ); #ifdef USE_VIDEO err |= re_hprintf(pf, @@ -478,7 +560,7 @@ static int core_config_template(struct re_printf *pf, const struct config *cfg) "video_size\t\t%dx%d\n" "video_bitrate\t\t%u\n" "video_fps\t\t%u\n" - "video_fullscreen\t\tyes\n", + "video_fullscreen\tyes\n", default_video_device(), default_video_display(), cfg->video.width, cfg->video.height, @@ -572,6 +654,14 @@ static const char *detect_module_path(bool *valid) } +/** + * Write the baresip core config template to a file + * + * @param file Filename of output file + * @param cfg Baresip core config + * + * @return 0 if success, otherwise errorcode + */ int config_write_template(const char *file, const struct config *cfg) { FILE *f = NULL; @@ -762,6 +852,7 @@ int config_write_template(const char *file, const struct config *cfg) (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "natbd"MOD_EXT"\n"); (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "presence"MOD_EXT"\n"); (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "syslog"MOD_EXT"\n"); + (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "mqtt" MOD_EXT "\n"); #ifdef USE_VIDEO (void)re_fprintf(f, "module_app\t\t" MOD_PRE "vidloop"MOD_EXT"\n"); #endif @@ -807,6 +898,11 @@ int config_write_template(const char *file, const struct config *cfg) "ice_mode\t\tfull\t# {full,lite}\n"); (void)re_fprintf(f, + "\n# ZRTP\n" + "#zrtp_hash\t\tno # Disable SDP zrtp-hash " + "(not recommended)\n"); + + (void)re_fprintf(f, "\n# Menu\n" "#redial_attempts\t\t3 # Num or <inf>\n" "#redial_delay\t\t5 # Delay in seconds\n"); @@ -818,6 +914,11 @@ int config_write_template(const char *file, const struct config *cfg) } +/** + * Get the baresip core config + * + * @return Core config + */ struct config *conf_config(void) { return &core_config; @@ -133,7 +133,6 @@ int audio_encoder_set(struct audio *a, const struct aucodec *ac, int pt_tx, const char *params); int audio_decoder_set(struct audio *a, const struct aucodec *ac, int pt_rx, const char *params); -struct stream *audio_strm(const struct audio *a); int audio_send_digit(struct audio *a, char key); void audio_sdp_attr_decode(struct audio *a); int audio_print_rtpstat(struct re_printf *pf, const struct audio *au); @@ -471,7 +470,72 @@ int video_encoder_set(struct video *v, struct vidcodec *vc, int pt_tx, const char *params); int video_decoder_set(struct video *v, struct vidcodec *vc, int pt_rx, const char *fmtp); -struct stream *video_strm(const struct video *v); void video_update_picture(struct video *v); void video_sdp_attr_decode(struct video *v); int video_print(struct re_printf *pf, const struct video *v); + + +/* + * Timestamp helpers + */ + + +/** + * This struct is used to keep track of timestamps for + * incoming RTP packets. + */ +struct timestamp_recv { + uint32_t first; + uint32_t last; + bool is_set; + unsigned num_wraps; +}; + + +static inline uint64_t calc_extended_timestamp(uint32_t num_wraps, uint32_t ts) +{ + uint64_t ext_ts; + + ext_ts = (uint64_t)num_wraps * 0x100000000ULL; + ext_ts += (uint64_t)ts; + + return ext_ts; +} + + +static inline uint64_t timestamp_duration(const struct timestamp_recv *ts) +{ + uint64_t last_ext; + + if (!ts || !ts->is_set) + return 0; + + last_ext = calc_extended_timestamp(ts->num_wraps, ts->last); + + return last_ext - ts->first; +} + + +/* + * -1 backwards wrap-around + * 0 no wrap-around + * 1 forward wrap-around + */ +static inline int timestamp_wrap(uint32_t ts_new, uint32_t ts_old) +{ + int32_t delta; + + if (ts_new < ts_old) { + + delta = (int32_t)ts_new - (int32_t)ts_old; + + if (delta > 0) + return 1; + } + else if ((int32_t)(ts_old - ts_new) > 0) { + + return -1; + } + + return 0; +} diff --git a/src/event.c b/src/event.c new file mode 100644 index 0000000..b29ab8b --- /dev/null +++ b/src/event.c @@ -0,0 +1,171 @@ +/** + * @file src/event.c Baresip event handling + * + * Copyright (C) 2017 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "core.h" + + +static const char *event_class_name(enum ua_event ev) +{ + switch (ev) { + + case UA_EVENT_REGISTERING: + case UA_EVENT_REGISTER_OK: + case UA_EVENT_REGISTER_FAIL: + case UA_EVENT_UNREGISTERING: + return "register"; + + case UA_EVENT_SHUTDOWN: + case UA_EVENT_EXIT: + return "application"; + + case UA_EVENT_CALL_INCOMING: + case UA_EVENT_CALL_RINGING: + case UA_EVENT_CALL_PROGRESS: + case UA_EVENT_CALL_ESTABLISHED: + case UA_EVENT_CALL_CLOSED: + case UA_EVENT_CALL_TRANSFER_FAILED: + case UA_EVENT_CALL_DTMF_START: + case UA_EVENT_CALL_DTMF_END: + case UA_EVENT_CALL_RTCP: + return "call"; + + default: + return "other"; + } +} + + +static int add_rtcp_stats(struct odict *od_parent, const struct rtcp_stats *rs) +{ + struct odict *od = NULL, *tx = NULL, *rx = NULL; + int err = 0; + + if (!od_parent || !rs) + return EINVAL; + + err = odict_alloc(&od, 8); + err |= odict_alloc(&tx, 8); + err |= odict_alloc(&rx, 8); + if (err) + goto out; + + err = odict_entry_add(tx, "sent", ODICT_INT, (int64_t)rs->tx.sent); + err |= odict_entry_add(tx, "lost", ODICT_INT, (int64_t)rs->tx.lost); + err |= odict_entry_add(tx, "jit", ODICT_INT, (int64_t)rs->tx.jit); + if (err) + goto out; + + err = odict_entry_add(rx, "sent", ODICT_INT, (int64_t)rs->rx.sent); + err |= odict_entry_add(rx, "lost", ODICT_INT, (int64_t)rs->rx.lost); + err |= odict_entry_add(rx, "jit", ODICT_INT, (int64_t)rs->rx.jit); + if (err) + goto out; + + err = odict_entry_add(od, "tx", ODICT_OBJECT, tx); + err |= odict_entry_add(od, "rx", ODICT_OBJECT, rx); + err |= odict_entry_add(od, "rtt", ODICT_INT, (int64_t)rs->rtt); + if (err) + goto out; + + /* add object to the parent */ + err = odict_entry_add(od_parent, "rtcp_stats", ODICT_OBJECT, od); + if (err) + goto out; + + out: + mem_deref(od); + + return err; +} + + +int event_encode_dict(struct odict *od, struct ua *ua, enum ua_event ev, + struct call *call, const char *prm) +{ + const char *event_str = uag_event_str(ev); + int err = 0; + + if (!od) + return EINVAL; + + err |= odict_entry_add(od, "type", ODICT_STRING, event_str); + err |= odict_entry_add(od, "class", + ODICT_STRING, event_class_name(ev)); + err |= odict_entry_add(od, "accountaor", ODICT_STRING, ua_aor(ua)); + if (err) + goto out; + + if (call) { + + const char *dir; + + dir = call_is_outgoing(call) ? "outgoing" : "incoming"; + + err |= odict_entry_add(od, "direction", ODICT_STRING, dir); + err |= odict_entry_add(od, "peeruri", + ODICT_STRING, call_peeruri(call)); + if (err) + goto out; + } + + if (str_isset(prm)) { + err = odict_entry_add(od, "param", ODICT_STRING, prm); + if (err) + goto out; + } + + if (ev == UA_EVENT_CALL_RTCP) { + struct stream *strm = NULL; + + if (0 == str_casecmp(prm, "audio")) + strm = audio_strm(call_audio(call)); +#ifdef USE_VIDEO + else if (0 == str_casecmp(prm, "video")) + strm = video_strm(call_video(call)); +#endif + + err = add_rtcp_stats(od, stream_rtcp_stats(strm)); + if (err) + goto out; + } + + out: + + return err; +} + + +/** + * Get the name of the User-Agent event + * + * @param ev User-Agent event + * + * @return Name of the event + */ +const char *uag_event_str(enum ua_event ev) +{ + switch (ev) { + + case UA_EVENT_REGISTERING: return "REGISTERING"; + case UA_EVENT_REGISTER_OK: return "REGISTER_OK"; + case UA_EVENT_REGISTER_FAIL: return "REGISTER_FAIL"; + case UA_EVENT_UNREGISTERING: return "UNREGISTERING"; + case UA_EVENT_SHUTDOWN: return "SHUTDOWN"; + case UA_EVENT_EXIT: return "EXIT"; + case UA_EVENT_CALL_INCOMING: return "CALL_INCOMING"; + case UA_EVENT_CALL_RINGING: return "CALL_RINGING"; + case UA_EVENT_CALL_PROGRESS: return "CALL_PROGRESS"; + case UA_EVENT_CALL_ESTABLISHED: return "CALL_ESTABLISHED"; + case UA_EVENT_CALL_CLOSED: return "CALL_CLOSED"; + case UA_EVENT_CALL_TRANSFER_FAILED: return "TRANSFER_FAILED"; + case UA_EVENT_CALL_DTMF_START: return "CALL_DTMF_START"; + case UA_EVENT_CALL_DTMF_END: return "CALL_DTMF_END"; + case UA_EVENT_CALL_RTCP: return "CALL_RTCP"; + default: return "?"; + } +} @@ -70,12 +70,12 @@ void vlog(enum log_level level, const char *fmt, va_list ap) bool color = level == LEVEL_WARN || level == LEVEL_ERROR; if (color) - (void)re_fprintf(stderr, "\x1b[31m"); /* Red */ + (void)re_fprintf(stdout, "\x1b[31m"); /* Red */ - (void)re_fprintf(stderr, "%s", buf); + (void)re_fprintf(stdout, "%s", buf); if (color) - (void)re_fprintf(stderr, "\x1b[;m"); + (void)re_fprintf(stdout, "\x1b[;m"); } le = lg.logl.head; @@ -77,7 +77,7 @@ int main(int argc, char *argv[]) size_t i; int err; - (void)re_fprintf(stderr, "baresip v%s" + (void)re_fprintf(stdout, "baresip v%s" " Copyright (C) 2010 - 2017" " Alfred E. Heggestad et al.\n", BARESIP_VERSION); @@ -69,7 +69,7 @@ static void tmr_polling(void *arg) /** * NOTE: DSP cannot be destroyed inside handler */ -static void write_handler(int16_t *sampv, size_t sampc, void *arg) +static void write_handler(void *sampv, size_t sampc, void *arg) { struct play *play = arg; size_t sz = sampc * 2; @@ -242,6 +242,7 @@ int play_tone(struct play **playp, struct player *player, wprm.ch = ch; wprm.srate = srate; wprm.ptime = PTIME; + wprm.fmt = AUFMT_S16LE; err = auplay_alloc(&play->auplay, baresip_auplayl(), cfg->audio.alert_mod, &wprm, diff --git a/src/srcs.mk b/src/srcs.mk index 686ca7c..a35e0d8 100644 --- a/src/srcs.mk +++ b/src/srcs.mk @@ -17,6 +17,7 @@ SRCS += cmd.c SRCS += conf.c SRCS += config.c SRCS += contact.c +SRCS += event.c SRCS += log.c SRCS += menc.c SRCS += message.c diff --git a/src/stream.c b/src/stream.c index d3d0bba..38db2d6 100644 --- a/src/stream.c +++ b/src/stream.c @@ -52,6 +52,12 @@ static void check_rtp_handler(void *arg) debug("stream: last \"%s\" RTP packet: %d milliseconds\n", sdp_media_name(strm->sdp), diff_ms); + /* check for large jumps in time */ + if (diff_ms > (3600 * 1000)) { + strm->ts_last = 0; + return; + } + if (diff_ms > (int)strm->rtp_timeout_ms) { info("stream: no %s RTP packets received for" @@ -289,6 +295,8 @@ static void rtcp_handler(const struct sa *src, struct rtcp_msg *msg, void *arg) if (s->cfg.rtp_stats) call_set_xrtpstat(s->call); + ua_event(call_get_ua(s->call), UA_EVENT_CALL_RTCP, s->call, + "%s", sdp_media_name(stream_sdpmedia(s))); break; } } @@ -717,3 +725,9 @@ int stream_print(struct re_printf *pf, const struct stream *s) s->metric_tx.cur_bitrate, s->metric_rx.cur_bitrate); } + + +const struct rtcp_stats *stream_rtcp_stats(const struct stream *strm) +{ + return strm ? &strm->rtcp_stats : NULL; +} @@ -1633,36 +1633,6 @@ struct tls *uag_tls(void) /** - * Get the name of the User-Agent event - * - * @param ev User-Agent event - * - * @return Name of the event - */ -const char *uag_event_str(enum ua_event ev) -{ - switch (ev) { - - case UA_EVENT_REGISTERING: return "REGISTERING"; - case UA_EVENT_REGISTER_OK: return "REGISTER_OK"; - case UA_EVENT_REGISTER_FAIL: return "REGISTER_FAIL"; - case UA_EVENT_UNREGISTERING: return "UNREGISTERING"; - case UA_EVENT_SHUTDOWN: return "SHUTDOWN"; - case UA_EVENT_EXIT: return "EXIT"; - case UA_EVENT_CALL_INCOMING: return "CALL_INCOMING"; - case UA_EVENT_CALL_RINGING: return "CALL_RINGING"; - case UA_EVENT_CALL_PROGRESS: return "CALL_PROGRESS"; - case UA_EVENT_CALL_ESTABLISHED: return "CALL_ESTABLISHED"; - case UA_EVENT_CALL_CLOSED: return "CALL_CLOSED"; - case UA_EVENT_CALL_TRANSFER_FAILED: return "TRANSFER_FAILED"; - case UA_EVENT_CALL_DTMF_START: return "CALL_DTMF_START"; - case UA_EVENT_CALL_DTMF_END: return "CALL_DTMF_END"; - default: return "?"; - } -} - - -/** * Find the correct UA from the contact user * * @param cuser Contact username diff --git a/src/video.c b/src/video.c index 43f7c9d..f191215 100644 --- a/src/video.c +++ b/src/video.c @@ -97,7 +97,7 @@ struct vtx { struct tmr tmr_rtp; /**< Timer for sending RTP */ unsigned skipc; /**< Number of frames skipped */ struct list filtl; /**< Filters in encoding order */ - char device[64]; /**< Source device name */ + char device[128]; /**< Source device name */ int muted_frames; /**< # of muted frames sent */ uint32_t ts_offset; /**< Random timestamp offset */ bool picup; /**< Send picture update */ @@ -136,7 +136,7 @@ struct vrx { struct tmr tmr_picup; /**< Picture update timer */ struct vidsz size; /**< Incoming video resolution */ enum vidorient orient; /**< Display orientation */ - char device[64]; /**< Display device name */ + char device[128]; /**< Display device name */ int pt_rx; /**< Incoming RTP payload type */ int frames; /**< Number of frames received */ int efps; /**< Estimated frame-rate */ diff --git a/test/call.c b/test/call.c index 0cd0394..34c5fd5 100644 --- a/test/call.c +++ b/test/call.c @@ -5,6 +5,7 @@ */ #include <string.h> #include <re.h> +#include <rem.h> #include <baresip.h> #include "test.h" @@ -721,7 +722,7 @@ int test_call_video(void) #endif -static void mock_sample_handler(const int16_t *sampv, size_t sampc, void *arg) +static void mock_sample_handler(const void *sampv, size_t sampc, void *arg) { struct fixture *fix = arg; bool got_aulevel; @@ -819,3 +820,87 @@ int test_call_progress(void) return err; } + + +static void float_sample_handler(const void *sampv, size_t sampc, void *arg) +{ + struct fixture *fix = arg; + (void)sampv; + (void)sampc; + + if (sampc && fix->a.n_established && fix->b.n_established) + re_cancel(); +} + + +static int test_media_base(enum audio_mode txmode) +{ + struct fixture fix, *f = &fix; + struct ausrc *ausrc = NULL; + struct auplay *auplay = NULL; + int err = 0; + + fixture_init(f); + + conf_config()->audio.txmode = txmode; + + conf_config()->audio.src_fmt = AUFMT_FLOAT; + conf_config()->audio.play_fmt = AUFMT_FLOAT; + + err = mock_ausrc_register(&ausrc); + TEST_ERR(err); + err = mock_auplay_register(&auplay, float_sample_handler, f); + TEST_ERR(err); + + f->estab_action = ACTION_NOTHING; + + f->behaviour = BEHAVIOUR_ANSWER; + + /* 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); + + ASSERT_EQ(0, fix.a.n_incoming); + ASSERT_EQ(1, fix.a.n_established); + ASSERT_EQ(0, fix.a.n_closed); + ASSERT_EQ(0, fix.a.close_scode); + + ASSERT_EQ(1, fix.b.n_incoming); + ASSERT_EQ(1, fix.b.n_established); + ASSERT_EQ(0, fix.b.n_closed); + + out: + conf_config()->audio.src_fmt = AUFMT_S16LE; + conf_config()->audio.play_fmt = AUFMT_S16LE; + + fixture_close(f); + mem_deref(auplay); + mem_deref(ausrc); + + if (fix.err) + return fix.err; + + return err; +} + + +int test_call_format_float(void) +{ + int err; + + err = test_media_base(AUDIO_MODE_POLL); + ASSERT_EQ(0, err); + + err = test_media_base(AUDIO_MODE_THREAD); + ASSERT_EQ(0, err); + + conf_config()->audio.txmode = AUDIO_MODE_POLL; + + out: + return err; +} diff --git a/test/main.c b/test/main.c index 497d347..e917f2d 100644 --- a/test/main.c +++ b/test/main.c @@ -35,6 +35,7 @@ static const struct test tests[] = { TEST(test_call_dtmf), TEST(test_call_aulevel), TEST(test_call_progress), + TEST(test_call_format_float), #ifdef USE_VIDEO TEST(test_call_video), TEST(test_video), diff --git a/test/mock/mock_auplay.c b/test/mock/mock_auplay.c index 9857d9f..2a4ffcc 100644 --- a/test/mock/mock_auplay.c +++ b/test/mock/mock_auplay.c @@ -4,6 +4,7 @@ * Copyright (C) 2010 - 2016 Creytiv.com */ #include <re.h> +#include <rem.h> #include <baresip.h> #include "../test.h" @@ -13,7 +14,7 @@ struct auplay_st { struct tmr tmr; struct auplay_prm prm; - int16_t *sampv; + void *sampv; size_t sampc; auplay_write_h *wh; void *arg; @@ -72,7 +73,7 @@ static int mock_auplay_alloc(struct auplay_st **stp, const struct auplay *ap, st->sampc = prm->srate * prm->ch * prm->ptime / 1000; - st->sampv = mem_zalloc(2 * st->sampc, NULL); + st->sampv = mem_zalloc(aufmt_sample_size(prm->fmt) * st->sampc, NULL); if (!st->sampv) { err = ENOMEM; goto out; diff --git a/test/mock/mock_ausrc.c b/test/mock/mock_ausrc.c index 39512f1..070cbfc 100644 --- a/test/mock/mock_ausrc.c +++ b/test/mock/mock_ausrc.c @@ -4,6 +4,7 @@ * Copyright (C) 2010 - 2016 Creytiv.com */ #include <re.h> +#include <rem.h> #include <baresip.h> #include "../test.h" @@ -13,7 +14,7 @@ struct ausrc_st { struct tmr tmr; struct ausrc_prm prm; - int16_t *sampv; + void *sampv; size_t sampc; ausrc_read_h *rh; void *arg; @@ -65,7 +66,7 @@ static int mock_ausrc_alloc(struct ausrc_st **stp, const struct ausrc *as, st->sampc = prm->srate * prm->ch * prm->ptime / 1000; - st->sampv = mem_zalloc(2 * st->sampc, NULL); + st->sampv = mem_zalloc(aufmt_sample_size(prm->fmt) * st->sampc, NULL); if (!st->sampv) { err = ENOMEM; goto out; diff --git a/test/play.c b/test/play.c index 9e8c09c..82c8813 100644 --- a/test/play.c +++ b/test/play.c @@ -39,7 +39,7 @@ static struct mbuf *generate_tone(void) } -static void sample_handler(const int16_t *sampv, size_t sampc, void *arg) +static void sample_handler(const void *sampv, size_t sampc, void *arg) { struct test *test = arg; size_t bytec = sampc * 2; diff --git a/test/test.h b/test/test.h index 572e179..f276a8d 100644 --- a/test/test.h +++ b/test/test.h @@ -143,7 +143,7 @@ int mock_ausrc_register(struct ausrc **ausrcp); struct auplay; -typedef void (mock_sample_h)(const int16_t *sampv, size_t sampc, void *arg); +typedef void (mock_sample_h)(const void *sampv, size_t sampc, void *arg); int mock_auplay_register(struct auplay **auplayp, mock_sample_h *sampleh, void *arg); @@ -206,6 +206,7 @@ int test_call_dtmf(void); int test_call_video(void); int test_call_aulevel(void); int test_call_progress(void); +int test_call_format_float(void); #ifdef USE_VIDEO int test_video(void); |