diff --git a/build_inplace b/build_inplace index cfbaa33d..a5197604 100755 --- a/build_inplace +++ b/build_inplace @@ -1,8 +1,8 @@ #!/bin/sh -./get_dependencies.sh +#./get_dependencies.sh chmod +x deps/pjsip/configure chmod +x deps/pjsip/aconfigure python3 setup.py build_ext --inplace "$@" test -d build && python3 setup.py clean diff --git a/deps/patches/005_fix_ffmpeg.patch b/deps/patches/005_fix_ffmpeg.patch deleted file mode 100644 index c3eed149..00000000 --- a/deps/patches/005_fix_ffmpeg.patch +++ /dev/null @@ -1,418 +0,0 @@ -diff --git a/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c b/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c -index 2ea34386c5..652839e900 100644 ---- pjproject-2.10/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c -+++ pjsip/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c -@@ -41,10 +41,6 @@ - - #define THIS_FILE "ffmpeg_vid_codecs.c" - --#define LIBAVCODEC_VER_AT_LEAST(major,minor) (LIBAVCODEC_VERSION_MAJOR > major || \ -- (LIBAVCODEC_VERSION_MAJOR == major && \ -- LIBAVCODEC_VERSION_MINOR >= minor)) -- - #include "../pjmedia/ffmpeg_util.h" - #include - #include -@@ -80,7 +76,7 @@ - # define AV_OPT_SET(obj,name,val,opt) (av_set_string3(obj,name,val,opt,NULL)==0) - # define AV_OPT_SET_INT(obj,name,val) (av_set_int(obj,name,val)!=NULL) - #endif --#define AVCODEC_HAS_DECODE(c) (c->decode) -+#define AVCODEC_HAS_DECODE(c) (c->decode) - - /* AVCodec H264 default PT */ - #define AVC_H264_PT PJMEDIA_RTP_PT_H264_RSV3 -@@ -732,6 +728,141 @@ static int find_codec_idx_by_fmt_id(pjmedia_format_id fmt_id) - return -1; - } - -+static void init_codec(const AVCodec *c, pj_bool_t is_encoder, -+ pj_bool_t is_decoder) -+{ -+ pj_status_t status; -+ ffmpeg_codec_desc *desc; -+ pjmedia_format_id fmt_id; -+ int codec_info_idx; -+ -+#if LIBAVCODEC_VERSION_MAJOR <= 52 -+# define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO -+#endif -+ if (c->type != AVMEDIA_TYPE_VIDEO) -+ return; -+ -+ /* Video encoder and decoder are usually implemented in separate -+ * AVCodec instances. While the codec attributes (e.g: raw formats, -+ * supported fps) are in the encoder. -+ */ -+ -+ status = CodecID_to_pjmedia_format_id(c->id, &fmt_id); -+ /* Skip if format ID is unknown */ -+ if (status != PJ_SUCCESS) -+ return; -+ -+ codec_info_idx = find_codec_idx_by_fmt_id(fmt_id); -+ /* Skip if codec is unwanted by this wrapper (not listed in -+ * the codec info array) -+ */ -+ if (codec_info_idx < 0) -+ return; -+ -+ desc = &codec_desc[codec_info_idx]; -+ -+ /* Skip duplicated codec implementation */ -+ if ((is_encoder && (desc->info.dir & PJMEDIA_DIR_ENCODING)) -+ || -+ (is_decoder && (desc->info.dir & PJMEDIA_DIR_DECODING))) -+ { -+ return; -+ } -+ -+ /* Get raw/decoded format ids in the encoder */ -+ if (c->pix_fmts && is_encoder) { -+ pjmedia_format_id raw_fmt[PJMEDIA_VID_CODEC_MAX_DEC_FMT_CNT]; -+ unsigned raw_fmt_cnt = 0; -+ unsigned raw_fmt_cnt_should_be = 0; -+ const enum AVPixelFormat *p = c->pix_fmts; -+ -+ for(;(p && *p != -1) && -+ (raw_fmt_cnt < PJMEDIA_VID_CODEC_MAX_DEC_FMT_CNT); -+ ++p) -+ { -+ pjmedia_format_id fmt_id; -+ -+ raw_fmt_cnt_should_be++; -+ status = PixelFormat_to_pjmedia_format_id(*p, &fmt_id); -+ if (status != PJ_SUCCESS) { -+ PJ_PERROR(6, (THIS_FILE, status, -+ "Unrecognized ffmpeg pixel format %d", *p)); -+ continue; -+ } -+ -+ //raw_fmt[raw_fmt_cnt++] = fmt_id; -+ /* Disable some formats due to H.264 error: -+ * x264 [error]: baseline profile doesn't support 4:4:4 -+ */ -+ if (desc->info.pt != PJMEDIA_RTP_PT_H264 || -+ fmt_id != PJMEDIA_FORMAT_RGB24) -+ { -+ raw_fmt[raw_fmt_cnt++] = fmt_id; -+ } -+ } -+ -+ if (raw_fmt_cnt == 0) { -+ PJ_LOG(5, (THIS_FILE, "No recognized raw format " -+ "for codec [%s/%s], codec ignored", -+ c->name, c->long_name)); -+ /* Skip this encoder */ -+ return; -+ } -+ -+ if (raw_fmt_cnt < raw_fmt_cnt_should_be) { -+ PJ_LOG(6, (THIS_FILE, "Codec [%s/%s] have %d raw formats, " -+ "recognized only %d raw formats", -+ c->name, c->long_name, -+ raw_fmt_cnt_should_be, raw_fmt_cnt)); -+ } -+ -+ desc->info.dec_fmt_id_cnt = raw_fmt_cnt; -+ pj_memcpy(desc->info.dec_fmt_id, raw_fmt, -+ sizeof(raw_fmt[0])*raw_fmt_cnt); -+ } -+ -+ /* Get supported framerates */ -+ if (c->supported_framerates) { -+ const AVRational *fr = c->supported_framerates; -+ while ((fr->num != 0 || fr->den != 0) && -+ desc->info.fps_cnt < PJMEDIA_VID_CODEC_MAX_FPS_CNT) -+ { -+ desc->info.fps[desc->info.fps_cnt].num = fr->num; -+ desc->info.fps[desc->info.fps_cnt].denum = fr->den; -+ ++desc->info.fps_cnt; -+ ++fr; -+ } -+ } -+ -+ /* Get ffmpeg encoder instance */ -+ if (is_encoder && !desc->enc) { -+ desc->info.dir |= PJMEDIA_DIR_ENCODING; -+ desc->enc = c; -+ } -+ -+ /* Get ffmpeg decoder instance */ -+ if (is_decoder && !desc->dec) { -+ desc->info.dir |= PJMEDIA_DIR_DECODING; -+ desc->dec = c; -+ } -+ -+ /* Enable this codec when any ffmpeg codec instance are recognized -+ * and the supported raw formats info has been collected. -+ */ -+ if ((desc->dec || desc->enc) && desc->info.dec_fmt_id_cnt) -+ { -+ desc->enabled = PJ_TRUE; -+ } -+ -+ /* Normalize default value of clock rate */ -+ if (desc->info.clock_rate == 0) -+ desc->info.clock_rate = 90000; -+ -+ /* Set supported packings */ -+ desc->info.packings |= PJMEDIA_VID_PACKING_WHOLE; -+ if (desc->packetize && desc->unpacketize) -+ desc->info.packings |= PJMEDIA_VID_PACKING_PACKETS; -+} - - /* - * Initialize and register FFMPEG codec factory to pjmedia endpoint. -@@ -775,144 +906,33 @@ PJ_DEF(pj_status_t) pjmedia_codec_ffmpeg_vid_init(pjmedia_vid_codec_mgr *mgr, - */ - avcodec_init(); - #endif -- avcodec_register_all(); -- -- /* Enum FFMPEG codecs */ -- for (c=av_codec_next(NULL); c; c=av_codec_next(c)) { -- ffmpeg_codec_desc *desc; -- pjmedia_format_id fmt_id; -- int codec_info_idx; -- --#if LIBAVCODEC_VERSION_MAJOR <= 52 --# define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO --#endif -- if (c->type != AVMEDIA_TYPE_VIDEO) -- continue; - -- /* Video encoder and decoder are usually implemented in separate -- * AVCodec instances. While the codec attributes (e.g: raw formats, -- * supported fps) are in the encoder. -- */ -- -- //PJ_LOG(3, (THIS_FILE, "%s", c->name)); -- status = CodecID_to_pjmedia_format_id(c->id, &fmt_id); -- /* Skip if format ID is unknown */ -- if (status != PJ_SUCCESS) -- continue; -- -- codec_info_idx = find_codec_idx_by_fmt_id(fmt_id); -- /* Skip if codec is unwanted by this wrapper (not listed in -- * the codec info array) -- */ -- if (codec_info_idx < 0) -- continue; -- -- desc = &codec_desc[codec_info_idx]; -- -- /* Skip duplicated codec implementation */ -- if ((AVCODEC_HAS_ENCODE(c) && (desc->info.dir & PJMEDIA_DIR_ENCODING)) -- || -- (AVCODEC_HAS_DECODE(c) && (desc->info.dir & PJMEDIA_DIR_DECODING))) -- { -- continue; -- } -- -- /* Get raw/decoded format ids in the encoder */ -- if (c->pix_fmts && AVCODEC_HAS_ENCODE(c)) { -- pjmedia_format_id raw_fmt[PJMEDIA_VID_CODEC_MAX_DEC_FMT_CNT]; -- unsigned raw_fmt_cnt = 0; -- unsigned raw_fmt_cnt_should_be = 0; -- const enum AVPixelFormat *p = c->pix_fmts; -- -- for(;(p && *p != -1) && -- (raw_fmt_cnt < PJMEDIA_VID_CODEC_MAX_DEC_FMT_CNT); -- ++p) -- { -- pjmedia_format_id fmt_id; -- -- raw_fmt_cnt_should_be++; -- status = PixelFormat_to_pjmedia_format_id(*p, &fmt_id); -- if (status != PJ_SUCCESS) { -- PJ_PERROR(6, (THIS_FILE, status, -- "Unrecognized ffmpeg pixel format %d", *p)); -- continue; -- } -- -- //raw_fmt[raw_fmt_cnt++] = fmt_id; -- /* Disable some formats due to H.264 error: -- * x264 [error]: baseline profile doesn't support 4:4:4 -- */ -- if (desc->info.pt != PJMEDIA_RTP_PT_H264 || -- fmt_id != PJMEDIA_FORMAT_RGB24) -- { -- raw_fmt[raw_fmt_cnt++] = fmt_id; -- } -- } -- -- if (raw_fmt_cnt == 0) { -- PJ_LOG(5, (THIS_FILE, "No recognized raw format " -- "for codec [%s/%s], codec ignored", -- c->name, c->long_name)); -- /* Skip this encoder */ -- continue; -- } -- -- if (raw_fmt_cnt < raw_fmt_cnt_should_be) { -- PJ_LOG(6, (THIS_FILE, "Codec [%s/%s] have %d raw formats, " -- "recognized only %d raw formats", -- c->name, c->long_name, -- raw_fmt_cnt_should_be, raw_fmt_cnt)); -- } -+#if LIBAVCODEC_VER_AT_LEAST(58,137) -+ -+ for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) { -+ unsigned codec_id; - -- desc->info.dec_fmt_id_cnt = raw_fmt_cnt; -- pj_memcpy(desc->info.dec_fmt_id, raw_fmt, -- sizeof(raw_fmt[0])*raw_fmt_cnt); -- } -+ pjmedia_format_id_to_CodecID(codec_desc[i].info.fmt_id, &codec_id); - -- /* Get supported framerates */ -- if (c->supported_framerates) { -- const AVRational *fr = c->supported_framerates; -- while ((fr->num != 0 || fr->den != 0) && -- desc->info.fps_cnt < PJMEDIA_VID_CODEC_MAX_FPS_CNT) -- { -- desc->info.fps[desc->info.fps_cnt].num = fr->num; -- desc->info.fps[desc->info.fps_cnt].denum = fr->den; -- ++desc->info.fps_cnt; -- ++fr; -- } -- } -+ c = avcodec_find_encoder(codec_id); -+ if (c) -+ init_codec(c, PJ_TRUE, PJ_FALSE); - -- /* Get ffmpeg encoder instance */ -- if (AVCODEC_HAS_ENCODE(c) && !desc->enc) { -- desc->info.dir |= PJMEDIA_DIR_ENCODING; -- desc->enc = c; -- } -- -- /* Get ffmpeg decoder instance */ -- if (AVCODEC_HAS_DECODE(c) && !desc->dec) { -- desc->info.dir |= PJMEDIA_DIR_DECODING; -- desc->dec = c; -- } -- -- /* Enable this codec when any ffmpeg codec instance are recognized -- * and the supported raw formats info has been collected. -- */ -- if ((desc->dec || desc->enc) && desc->info.dec_fmt_id_cnt) -- { -- desc->enabled = PJ_TRUE; -- } -- -- /* Normalize default value of clock rate */ -- if (desc->info.clock_rate == 0) -- desc->info.clock_rate = 90000; -+ c = avcodec_find_decoder(codec_id); -+ if (c) -+ init_codec(c, PJ_FALSE, PJ_TRUE); -+ } -+#else - -- /* Set supported packings */ -- desc->info.packings |= PJMEDIA_VID_PACKING_WHOLE; -- if (desc->packetize && desc->unpacketize) -- desc->info.packings |= PJMEDIA_VID_PACKING_PACKETS; -+ avcodec_register_all(); - -+ /* Enum FFMPEG codecs */ -+ for (c=av_codec_next(NULL); c; c=av_codec_next(c)) { -+ init_codec(c, AVCODEC_HAS_ENCODE(c), AVCODEC_HAS_DECODE(c)); - } - -+#endif -+ - /* Review all codecs for applying base format, registering format match for - * SDP negotiation, etc. - */ -@@ -1618,7 +1638,34 @@ static pj_status_t ffmpeg_codec_encode_whole(pjmedia_vid_codec *codec, - avpacket.data = (pj_uint8_t*)output->buf; - avpacket.size = output_buf_len; - --#if LIBAVCODEC_VER_AT_LEAST(54,15) -+#if LIBAVCODEC_VER_AT_LEAST(58,137) -+ PJ_UNUSED_ARG(got_packet); -+ err = avcodec_send_frame(ff->enc_ctx, &avframe); -+ if (err >= 0) { -+ AVPacket *pkt = NULL; -+ pj_uint8_t *bits_out = (pj_uint8_t*) output->buf; -+ unsigned out_size = 0; -+ pkt = av_packet_alloc(); -+ if (pkt) { -+ while (err >= 0) { -+ err = avcodec_receive_packet(ff->enc_ctx, pkt); -+ if (err == AVERROR(EAGAIN) || err == AVERROR_EOF) { -+ err = out_size; -+ break; -+ } -+ if (err >= 0) { -+ pj_memcpy(bits_out, pkt->data, pkt->size); -+ bits_out += pkt->size; -+ out_size += pkt->size; -+ av_packet_unref(&avpacket); -+ } -+ } -+ av_packet_free(&pkt); -+ } -+ } -+ -+#elif LIBAVCODEC_VER_AT_LEAST(54,15) -+ - err = avcodec_encode_video2(ff->enc_ctx, &avpacket, &avframe, &got_packet); - if (!err && got_packet) - err = avpacket.size; -@@ -1861,7 +1908,18 @@ static pj_status_t ffmpeg_codec_decode_whole(pjmedia_vid_codec *codec, - avpacket.flags = 0; - #endif - --#if LIBAVCODEC_VER_AT_LEAST(52,72) -+#if LIBAVCODEC_VER_AT_LEAST(58,137) -+ err = avcodec_send_packet(ff->dec_ctx, &avpacket); -+ if (err >= 0) { -+ err = avcodec_receive_frame(ff->dec_ctx, &avframe); -+ if (err == AVERROR_EOF) -+ err = 0; -+ -+ if (err >= 0) { -+ got_picture = PJ_TRUE; -+ } -+ } -+#elif LIBAVCODEC_VER_AT_LEAST(52,72) - err = avcodec_decode_video2(ff->dec_ctx, &avframe, - &got_picture, &avpacket); - #else -diff --git a/pjmedia/src/pjmedia/ffmpeg_util.c b/pjmedia/src/pjmedia/ffmpeg_util.c -index 8451f22008..9de668519d 100644 ---- pjproject-2.10/pjmedia/src/pjmedia/ffmpeg_util.c -+++ pjsip/pjmedia/src/pjmedia/ffmpeg_util.c -@@ -79,7 +79,9 @@ void pjmedia_ffmpeg_add_ref() - if (pjmedia_ffmpeg_ref_cnt++ == 0) { - av_log_set_level(AV_LOG_ERROR); - av_log_set_callback(&ffmpeg_log_cb); -+#if LIBAVCODEC_VER_AT_MAX(58,137) - av_register_all(); -+#endif - } - } - -diff --git a/pjmedia/src/pjmedia/ffmpeg_util.h b/pjmedia/src/pjmedia/ffmpeg_util.h -index cd8283edb8..dc9bedbd5d 100644 ---- pjproject-2.10/pjmedia/src/pjmedia/ffmpeg_util.h -+++ pjsip/pjmedia/src/pjmedia/ffmpeg_util.h -@@ -46,6 +46,13 @@ - # define AV(str) AV_ ## str - //#endif - -+#define LIBAVCODEC_VER_AT_LEAST(major,minor) (LIBAVCODEC_VERSION_MAJOR > major || \ -+ (LIBAVCODEC_VERSION_MAJOR == major && \ -+ LIBAVCODEC_VERSION_MINOR >= minor)) -+ -+#define LIBAVCODEC_VER_AT_MAX(major,minor) (LIBAVCODEC_VERSION_MAJOR < major || \ -+ (LIBAVCODEC_VERSION_MAJOR == major && \ -+ LIBAVCODEC_VERSION_MINOR <= minor)) - - void pjmedia_ffmpeg_add_ref(); - void pjmedia_ffmpeg_dec_ref(); diff --git a/deps/patches/007_video_support_dshow_mingw.patch b/deps/patches/007_video_support_dshow_mingw.patch deleted file mode 100644 index e40ec3b5..00000000 --- a/deps/patches/007_video_support_dshow_mingw.patch +++ /dev/null @@ -1,480 +0,0 @@ -diff -ruN pjsip/aconfigure patch/aconfigure ---- pjsip/aconfigure 2024-09-03 11:24:53 -+++ patch/aconfigure 2024-09-03 11:27:36 -@@ -681,6 +681,9 @@ - ac_pjmedia_video_has_ios_opengl - ac_pjmedia_video_has_vtoolbox - ac_pjmedia_video_has_darwin -+ac_dshow_ldflags -+ac_dshow_cflags -+ac_pjmedia_video_dev_has_dshow - ac_android_cflags - ac_pjmedia_video_has_android - ac_pjmedia_video -@@ -6471,10 +6474,12 @@ - - - --# Disable video on mingw -+# Disable video on mingw by default (but respect --enable-video=yes) - case $target in - *mingw*) -+ if test ! "$enable_video" = "yes"; then - enable_video="no" -+ fi - ;; - esac - -@@ -6545,6 +6550,18 @@ - fi - ac_android_cflags="$ac_android_cflags -DPJMEDIA_VIDEO_DEV_HAS_ANDROID=1" - ;; -+ *mingw*) -+ if test "$enable_video" = "yes"; then -+ ac_pjmedia_video=windows_os -+ -+ ac_pjmedia_video_dev_has_dshow=yes -+ -+ ac_dshow_cflags="-DPJMEDIA_HAS_VIDEO=1 -DPJMEDIA_VIDEO_DEV_HAS_DSHOW=1" -+ -+ ac_dshow_ldflags=" -lstdc++ -lquartz -lole32 -loleaut32 -lrpcrt4 -lwinmm -luuid -lmingwex -lstrmiids " -+ LIBS="$LIBS -lstdc++ -lquartz -lole32 -loleaut32 -lrpcrt4 -lwinmm -luuid -lmingwex -lstrmiids " -+ fi -+ ;; - *darwin*) - ac_pjmedia_video=darwin_os - -@@ -7053,6 +7070,7 @@ - ac_sdl_cflags=`$SDL_CONFIG --cflags` - ac_sdl_cflags="-DPJMEDIA_VIDEO_DEV_HAS_SDL=1 $ac_sdl_cflags" - ac_sdl_ldflags=`$SDL_CONFIG --libs` -+ ac_sdl_ldflags=${ac_sdl_ldflags//-mwindows/} - LIBS="$LIBS $ac_sdl_ldflags" - else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: Unsupported SDL version" >&5 -diff -ruN pjsip/aconfigure.ac patch/aconfigure.ac ---- pjsip/aconfigure.ac 2024-09-03 11:24:53 -+++ patch/aconfigure.ac 2024-09-03 11:27:36 -@@ -763,10 +763,12 @@ - - AC_SUBST(ac_pjmedia_video) - --# Disable video on mingw -+# Disable video on mingw by default (but respect --enable-video=yes) - case $target in - *mingw*) -+ if test ! "$enable_video" = "yes"; then - enable_video="no" -+ fi - ;; - esac - -@@ -818,6 +820,18 @@ - fi - ac_android_cflags="$ac_android_cflags -DPJMEDIA_VIDEO_DEV_HAS_ANDROID=1" - ;; -+ *mingw*) -+ if test "$enable_video" = "yes"; then -+ ac_pjmedia_video=windows_os -+ AC_SUBST(ac_pjmedia_video_dev_has_dshow) -+ ac_pjmedia_video_dev_has_dshow=yes -+ AC_SUBST(ac_dshow_cflags) -+ ac_dshow_cflags="-DPJMEDIA_HAS_VIDEO=1 -DPJMEDIA_VIDEO_DEV_HAS_DSHOW=1" -+ AC_SUBST(ac_dshow_ldflags) -+ ac_dshow_ldflags=" -lstdc++ -lquartz -lole32 -loleaut32 -lrpcrt4 -lwinmm -luuid -lmingwex -lstrmiids " -+ LIBS="$LIBS -lstdc++ -lquartz -lole32 -loleaut32 -lrpcrt4 -lwinmm -luuid -lmingwex -lstrmiids " -+ fi -+ ;; - *darwin*) - ac_pjmedia_video=darwin_os - AC_SUBST(ac_pjmedia_video_has_darwin) -@@ -1072,6 +1086,7 @@ - ac_sdl_cflags=`$SDL_CONFIG --cflags` - ac_sdl_cflags="-DPJMEDIA_VIDEO_DEV_HAS_SDL=1 $ac_sdl_cflags" - ac_sdl_ldflags=`$SDL_CONFIG --libs` -+ ac_sdl_ldflags=${ac_sdl_ldflags//-mwindows/} - LIBS="$LIBS $ac_sdl_ldflags" - else - AC_MSG_RESULT([Unsupported SDL version]) -diff -ruN pjsip/build.mak.in patch/build.mak.in ---- pjsip/build.mak.in 2024-09-03 11:24:53 -+++ patch/build.mak.in 2024-09-03 11:27:36 -@@ -200,13 +200,22 @@ - AC_PJMEDIA_VIDEO_HAS_IOS_OPENGL = @ac_pjmedia_video_has_ios_opengl@ - DARWIN_CFLAGS = @ac_darwin_cflags@ - -+# mingw -+AC_PJMEDIA_VIDEO_DEV_HAS_DSHOW = @ac_pjmedia_video_dev_has_dshow@ -+ifeq (@ac_pjmedia_video_dev_has_dshow@,yes) -+DSHOW_CFLAGS = @ac_dshow_cflags@ -+DSHOW_LDFLAGS = @ac_dshow_ldflags@ -+APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libbaseclasses-$(LIB_SUFFIX) -+APP_THIRD_PARTY_LIBS += -lbaseclasses-$(TARGET_NAME) -+endif -+ - # Android - ANDROID_CFLAGS = @ac_android_cflags@ - - # PJMEDIA features exclusion --PJ_VIDEO_CFLAGS += $(SDL_CFLAGS) $(FFMPEG_CFLAGS) $(V4L2_CFLAGS) $(QT_CFLAGS) \ -+PJ_VIDEO_CFLAGS += $(SDL_CFLAGS) $(FFMPEG_CFLAGS) $(V4L2_CFLAGS) $(DSHOW_CFLAGS) $(QT_CFLAGS) \ - $(OPENH264_CFLAGS) $(VPX_CFLAGS) $(DARWIN_CFLAGS) --PJ_VIDEO_LDFLAGS += $(SDL_LDFLAGS) $(FFMPEG_LDFLAGS) $(V4L2_LDFLAGS) \ -+PJ_VIDEO_LDFLAGS += $(SDL_LDFLAGS) $(FFMPEG_LDFLAGS) $(V4L2_LDFLAGS) $(DSHOW_LDFLAGS) \ - $(OPENH264_LDFLAGS) $(VPX_LDFLAGS) - - # CFLAGS, LDFLAGS, and LIBS to be used by applications -diff -ruN pjsip/pjmedia/build/os-auto.mak.in patch/pjmedia/build/os-auto.mak.in ---- pjsip/pjmedia/build/os-auto.mak.in 2024-09-03 11:24:53 -+++ patch/pjmedia/build/os-auto.mak.in 2024-09-03 11:27:36 -@@ -18,6 +18,10 @@ - V4L2_CFLAGS = @ac_v4l2_cflags@ - V4L2_LDFLAGS = @ac_v4l2_ldflags@ - -+# Directshow -+DSHOW_CFLAGS = @ac_dshow_cflags@ -+DSHOW_LDFLAGS = @ac_dshow_ldflags@ -+ - # QT - AC_PJMEDIA_VIDEO_HAS_QT = @ac_pjmedia_video_has_qt@ - # QT_CFLAGS = @ac_qt_cflags@ -@@ -39,10 +43,10 @@ - - # PJMEDIA features exclusion - export CFLAGS += @ac_no_small_filter@ @ac_no_large_filter@ @ac_no_speex_aec@ \ -- $(SDL_CFLAGS) $(FFMPEG_CFLAGS) $(V4L2_CFLAGS) $(QT_CFLAGS) \ -+ $(SDL_CFLAGS) $(FFMPEG_CFLAGS) $(V4L2_CFLAGS) $(DSHOW_CFLAGS) $(QT_CFLAGS) \ - $(DARWIN_CFLAGS) $(ANDROID_CFLAGS) \ - $(OPENH264_CFLAGS) $(VPX_CFLAGS) --export LDFLAGS += $(SDL_LDFLAGS) $(FFMPEG_LDFLAGS) $(V4L2_LDFLAGS) \ -+export LDFLAGS += $(SDL_LDFLAGS) $(FFMPEG_LDFLAGS) $(V4L2_LDFLAGS) $(DSHOW_LDFLAGS) \ - $(OPENH264_LDFLAGS) $(VPX_LDFLAGS) - - # Define the desired sound device backend -@@ -287,6 +291,14 @@ - # - ifeq ($(AC_PJMEDIA_VIDEO_HAS_IOS_OPENGL),yes) - export PJMEDIA_VIDEODEV_OBJS += ios_opengl_dev.o -+endif -+ -+# -+# Directshow video device -+# -+ifeq ($(AC_PJMEDIA_VIDEO_DEV_HAS_DSHOW),yes) -+export PJMEDIA_VIDEODEV_OBJS += dshow_dev.o dshowclasses.o -+export CFLAGS += -I$(THIRD_PARTY)/BaseClasses - endif - - # -diff -ruN pjsip/pjmedia/src/pjmedia-videodev/dshow_dev.c patch/pjmedia/src/pjmedia-videodev/dshow_dev.c ---- pjsip/pjmedia/src/pjmedia-videodev/dshow_dev.c 2024-09-03 11:24:53 -+++ patch/pjmedia/src/pjmedia-videodev/dshow_dev.c 2024-09-03 11:20:53 -@@ -38,6 +38,12 @@ - # define DIBSIZE(BI) ((BI).biHeight < 0 ? (-1)*(_DIBSIZE(BI)) : _DIBSIZE(BI)) - #endif - -+#ifdef _MSC_VER -+# pragma warning(pop) -+#else -+#include -+#endif -+ - #define THIS_FILE "dshow_dev.c" - #define DEFAULT_CLOCK_RATE 90000 - #define DEFAULT_WIDTH 640 -diff -ruN pjsip/pjsip/build/Makefile patch/pjsip/build/Makefile ---- pjsip/pjsip/build/Makefile 2020-02-14 10:48:27 -+++ patch/pjsip/build/Makefile 2024-09-03 11:27:49 -@@ -163,7 +163,7 @@ - export PJSUA2_TEST_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \ - main.o - export PJSUA2_TEST_CFLAGS += $(_CFLAGS) $(PJ_VIDEO_CFLAGS) --export PJSUA2_TEST_CXXFLAGS = $(_CXXFLAGS) $(PJSUA2_LIB_CFLAGS) -+export PJSUA2_TEST_CXXFLAGS = $(_CXXFLAGS) $(PJSUA2_LIB_CFLAGS) $(PJ_VIDEO_CFLAGS) - export PJSUA2_TEST_LDFLAGS += $(PJ_LDXXFLAGS) $(PJ_LDXXLIBS) $(LDFLAGS) - ifeq ($(EXCLUDE_APP),0) - export PJSUA2_TEST_EXE := pjsua2-test-$(TARGET_NAME)$(HOST_EXE) -@@ -182,7 +182,7 @@ - tsx_basic_test.o tsx_bench.o tsx_uac_test.o \ - tsx_uas_test.o txdata_test.o uri_test.o \ - inv_offer_answer_test.o --export TEST_CFLAGS += $(_CFLAGS) -+export TEST_CFLAGS += $(_CFLAGS) $(PJ_VIDEO_CFLAGS) - export TEST_CXXFLAGS += $(_CXXFLAGS) - export TEST_LDFLAGS += $(PJSIP_LDLIB) \ - $(PJSIP_UA_LDLIB) \ -diff -ruN pjsip/pjsip/src/pjsua2-test/main.cpp patch/pjsip/src/pjsua2-test/main.cpp ---- pjsip/pjsip/src/pjsua2-test/main.cpp 2020-02-14 10:48:27 -+++ patch/pjsip/src/pjsua2-test/main.cpp 2024-09-03 11:27:49 -@@ -20,6 +20,7 @@ - - using namespace pj; - -+extern "C" - int main(int argc, char *argv[]) - { - Endpoint ep; -diff -ruN pjsip/pjsip-apps/build/Samples.mak patch/pjsip-apps/build/Samples.mak ---- pjsip/pjsip-apps/build/Samples.mak 2020-02-14 10:48:27 -+++ patch/pjsip-apps/build/Samples.mak 2024-09-03 11:27:49 -@@ -7,8 +7,8 @@ - ############################################################################### - # Gather all flags. - # --export _CFLAGS := $(PJ_CFLAGS) $(CFLAGS) --export _CXXFLAGS:= $(PJ_CXXFLAGS) $(CFLAGS) -+export _CFLAGS := $(PJ_CFLAGS) $(CFLAGS) $(PJ_VIDEO_CFLAGS) -+export _CXXFLAGS:= $(PJ_CXXFLAGS) $(CFLAGS) $(PJ_VIDEO_CFLAGS) - export _LDFLAGS := $(PJ_LDFLAGS) $(PJ_LDLIBS) $(LDFLAGS) - export _LDXXFLAGS := $(PJ_LDXXFLAGS) $(PJ_LDXXLIBS) $(LDFLAGS) - -diff -ruN pjsip/pjsip-apps/src/samples/pjsua2_demo.cpp patch/pjsip-apps/src/samples/pjsua2_demo.cpp ---- pjsip/pjsip-apps/src/samples/pjsua2_demo.cpp 2020-02-14 10:48:27 -+++ patch/pjsip-apps/src/samples/pjsua2_demo.cpp 2024-09-03 11:27:49 -@@ -410,6 +410,7 @@ - } - - -+extern "C" - int main() - { - int ret = 0; -diff -ruN pjsip/third_party/BaseClasses/amvideo2.h patch/third_party/BaseClasses/amvideo2.h ---- pjsip/third_party/BaseClasses/amvideo2.h 1970-01-01 01:00:00 -+++ patch/third_party/BaseClasses/amvideo2.h 2024-09-03 11:27:49 -@@ -0,0 +1,26 @@ -+/* -+ Some necessary macros are missing from mingw version. They are here. -+ See https://github.com/ofTheo/videoInput/blob/master/videoInputSrcAndDemos/libs/DShow/Include/amvideo.h -+*/ -+//------------------------------------------------------------------------------ -+// File: AMVideo.h -+// -+// Desc: Video related definitions and interfaces for ActiveMovie. -+// -+// Copyright (c) 1992 - 2001, Microsoft Corporation. All rights reserved. -+//------------------------------------------------------------------------------ -+ -+#define TRUECOLOR(pbmi) ((TRUECOLORINFO *)(((LPBYTE)&((pbmi)->bmiHeader)) \ -+ + (pbmi)->bmiHeader.biSize)) -+#define COLORS(pbmi) ((RGBQUAD *)(((LPBYTE)&((pbmi)->bmiHeader)) \ -+ + (pbmi)->bmiHeader.biSize)) -+ -+#define SIZE_MASKS (iMASK_COLORS * sizeof(DWORD)) -+#define SIZE_PREHEADER (FIELD_OFFSET(VIDEOINFOHEADER,bmiHeader)) -+ -+#define WIDTHBYTES(bits) ((DWORD)(((bits)+31) & (~31)) / 8) -+#define DIBWIDTHBYTES(bi) (DWORD)WIDTHBYTES((DWORD)(bi).biWidth * (DWORD)(bi).biBitCount) -+#define _DIBSIZE(bi) (DIBWIDTHBYTES(bi) * (DWORD)(bi).biHeight) -+#define DIBSIZE(bi) ((bi).biHeight < 0 ? (-1)*(_DIBSIZE(bi)) : _DIBSIZE(bi)) -+ -+#define PALETTISED(pbmi) ((pbmi)->bmiHeader.biBitCount <= iPALETTE) -diff -ruN pjsip/third_party/BaseClasses/arithutil.cpp patch/third_party/BaseClasses/arithutil.cpp ---- pjsip/third_party/BaseClasses/arithutil.cpp 2020-02-14 10:48:27 -+++ patch/third_party/BaseClasses/arithutil.cpp 2024-09-03 11:27:49 -@@ -21,7 +21,7 @@ - // Enlarged integer divide - 64-bits / 32-bits > 32-bits - // - --#ifndef _X86_ -+#if !defined(_X86_) || !defined(_MSC_VER) - - #define LLtoU64(x) (*(unsigned __int64*)(void*)(&(x))) - -diff -ruN pjsip/third_party/BaseClasses/sal2.h patch/third_party/BaseClasses/sal2.h ---- pjsip/third_party/BaseClasses/sal2.h 1970-01-01 01:00:00 -+++ patch/third_party/BaseClasses/sal2.h 2024-09-03 11:27:49 -@@ -0,0 +1,19 @@ -+/* -+ See https://github.com/RobertBeckebans/RBDOOM-3-BFG/blob/master/neo/libs/mingw-hacks/sal.h -+*/ -+/* From PortAudio, which is under MIT license: -+ * https://www.assembla.com/code/portaudio/subversion/nodes/portaudio/trunk/src/hostapi/wasapi/mingw-include/sal.h -+ */ -+#pragma once -+ -+#if __GNUC__ >=3 -+#pragma GCC system_header -+#endif -+ -+#define __in -+#define __out -+#define __deref_in -+#define __deref_inout_opt -+#define __field_ecount_opt(x) -+#define __in_bcount_opt(size) -+ -diff -ruN pjsip/third_party/BaseClasses/streams.h patch/third_party/BaseClasses/streams.h ---- pjsip/third_party/BaseClasses/streams.h 2020-02-14 10:48:27 -+++ patch/third_party/BaseClasses/streams.h 2024-09-03 11:27:49 -@@ -23,6 +23,11 @@ - #else - #define AM_NOVTABLE - #endif -+#else -+ #include -+ #include -+ #define AM_NOVTABLE -+ #pragma GCC diagnostic ignored "-Wwrite-strings" - #endif // MSC_VER - - -@@ -193,6 +198,13 @@ - //#include // External device control interface defines - //#include // audio filter device error event codes - -+#ifndef _MSC_VER -+ -+#define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) -+#include -+ -+#endif -+ - - #else - #ifdef DEBUG -diff -ruN pjsip/third_party/BaseClasses/wxutil.cpp patch/third_party/BaseClasses/wxutil.cpp ---- pjsip/third_party/BaseClasses/wxutil.cpp 2020-02-14 10:48:27 -+++ patch/third_party/BaseClasses/wxutil.cpp 2024-09-03 11:27:49 -@@ -384,7 +384,7 @@ - { - void * ret = dst; - --#ifdef _X86_ -+#if defined(_X86_) && defined(_MSC_VER) - if (dst <= src || (char *)dst >= ((char *)src + count)) { - - /* -@@ -432,6 +432,7 @@ - return ret; - } - -+#ifdef _MSC_VER - HRESULT AMSafeMemMoveOffset( - __in_bcount(dst_size) void * dst, - __in size_t dst_size, -@@ -458,6 +459,7 @@ - memmoveInternal( (BYTE *)dst+cb_dst_offset, (BYTE *)src+cb_src_offset, count); - return S_OK; - } -+#endif - - - #ifdef DEBUG -diff -ruN pjsip/third_party/BaseClasses/wxutil.h patch/third_party/BaseClasses/wxutil.h ---- pjsip/third_party/BaseClasses/wxutil.h 2020-02-14 10:48:27 -+++ patch/third_party/BaseClasses/wxutil.h 2024-09-03 11:27:49 -@@ -337,6 +337,7 @@ - } - }; - -+#ifdef _MSC_VER - // Ensures that memory is not read past the length source buffer - // and that memory is not written past the length of the dst buffer - // dst - buffer to copy to -@@ -358,13 +359,14 @@ - __in size_t src_size, - __in DWORD cb_src_offset, - __in size_t count); -+#endif - - extern "C" - void * __stdcall memmoveInternal(void *, const void *, size_t); - - inline void * __cdecl memchrInternal(const void *buf, int chr, size_t cnt) - { --#ifdef _X86_ -+#if defined(_X86_) && defined(_MSC_VER) - void *pRet = NULL; - - _asm { -diff -ruN pjsip/third_party/build/baseclasses/Makefile patch/third_party/build/baseclasses/Makefile ---- pjsip/third_party/build/baseclasses/Makefile 1970-01-01 01:00:00 -+++ patch/third_party/build/baseclasses/Makefile 2024-09-03 11:27:49 -@@ -0,0 +1,72 @@ -+include ../../../build.mak -+include ../../../build/common.mak -+ -+export LIBDIR := ../../lib -+ -+RULES_MAK := $(PJDIR)/build/rules.mak -+ -+export BASECLASSES_LIB := libbaseclasses-$(TARGET_NAME)$(LIBEXT) -+ -+ifeq ($(PJ_SHARED_LIBRARIES),) -+else -+export BASECLASSES_SONAME := libbaseclasses.$(SHLIB_SUFFIX) -+export BASECLASSES_SHLIB := $(BASECLASSES_SONAME).$(PJ_VERSION_MAJOR) -+endif -+ -+############################################################################### -+# Gather all flags. -+# -+export _CFLAGS := $(CC_CFLAGS) $(OS_CFLAGS) $(HOST_CFLAGS) $(M_CFLAGS) \ -+ $(CFLAGS) $(CC_INC)../../BaseClasses \ -+ $(CC_INC)../../../pjmedia/include $(CC_INC)../../../pjlib/include -+export _CXXFLAGS:= $(_CFLAGS) $(CC_CXXFLAGS) $(OS_CXXFLAGS) $(M_CXXFLAGS) \ -+ $(HOST_CXXFLAGS) $(CXXFLAGS) -+export _LDFLAGS := $(CC_LDFLAGS) $(OS_LDFLAGS) $(M_LDFLAGS) $(HOST_LDFLAGS) \ -+ $(LDFLAGS) -+ -+export BASECLASSES_SRCDIR = ../../BaseClasses -+export BASECLASSES_OBJS = amfilter.o amvideo.o arithutil.o combase.o ctlutil.o \ -+ mtype.o renbase.o wxdebug.o wxlist.o \ -+ wxutil.o -+ -+export BASECLASSES_CFLAGS = $(_CFLAGS) -+export BASECLASSES_CXXFLAGS = -fpermissive -D__STRSAFE__NO_INLINE=1 -DPJMEDIA_VIDEO_DEV_HAS_DSHOW=1 $(_CFLAGS) -+ -+ -+export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT -+############################################################################### -+# Main entry -+# -+# $(TARGET) is defined in os-$(OS_NAME).mak file in current directory. -+# -+TARGETS := $(BASECLASSES_LIB) $(BASECLASSES_SONAME) -+ -+all: $(TARGETS) -+ -+doc: -+ cd .. && doxygen docs/doxygen.cfg -+ -+dep: depend -+distclean: realclean -+ -+.PHONY: all dep depend clean realclean distclean -+.PHONY: $(TARGETS) -+.PHONY: $(BASECLASSES_LIB) $(BASECLASSES_SONAME) -+ -+libbaseclasses: $(BASECLASSES_LIB) -+$(BASECLASSES_SONAME): $(BASECLASSES_LIB) -+$(BASECLASSES_LIB) $(BASECLASSES_SONAME): -+ $(MAKE) -f $(RULES_MAK) APP=BASECLASSES app=baseclasses $(subst /,$(HOST_PSEP),$(LIBDIR)/$@) -+ -+clean print_lib: -+ $(MAKE) -f $(RULES_MAK) APP=BASECLASSES app=baseclasses $@ -+ -+realclean: -+ $(subst @@,$(subst /,$(HOST_PSEP),.ilbc-$(TARGET_NAME).depend),$(HOST_RMR)) -+ -+ $(MAKE) -f $(RULES_MAK) APP=BASECLASSES app=baseclasses $@ -+ -+depend: -+ $(MAKE) -f $(RULES_MAK) APP=BASECLASSES app=baseclasses $@ -+ -+ -diff -ruN pjsip/third_party/build/os-auto.mak.in patch/third_party/build/os-auto.mak.in ---- pjsip/third_party/build/os-auto.mak.in 2024-09-03 11:24:53 -+++ patch/third_party/build/os-auto.mak.in 2024-09-03 11:27:49 -@@ -39,6 +39,10 @@ - endif - endif - -+ifneq ($(findstring windows_os,@ac_pjmedia_video@),) -+DIRS += baseclasses -+endif -+ - ifneq (@ac_external_srtp@,0) - # External SRTP - else diff --git a/deps/patches/008_support_mingw_w64.patch b/deps/patches/008_support_mingw_w64.patch deleted file mode 100644 index 294a8d5d..00000000 --- a/deps/patches/008_support_mingw_w64.patch +++ /dev/null @@ -1,70 +0,0 @@ -diff -ruN pjsip/Makefile pjsip-patch/Makefile ---- pjsip/Makefile 2020-02-14 10:48:27.000000000 +0100 -+++ pjsip-patch/Makefile 2024-09-03 10:32:23.926160100 +0200 -@@ -129,10 +129,9 @@ - sed -e "s!@INCLUDEDIR@!$(includedir)!" | \ - sed -e "s!@LIBDIR@!$(libdir)!" | \ - sed -e "s/@PJ_VERSION@/$(PJ_VERSION)/" | \ -- sed -e "s!@PJ_LDLIBS@!!" | \ -- sed -e "s!@PJ_LDXXLIBS@!$(PJ_LDXXLIBS)!" | \ -- sed -e "s!@PJ_INSTALL_CFLAGS@!!" | \ -- sed -e "s!@PJ_INSTALL_CXXFLAGS@!$(PJ_INSTALL_CXXFLAGS)!" > $(DESTDIR)/$(libdir)/pkgconfig/libpjproject.pc -+ sed -e "s!@PJ_INSTALL_LDFLAGS@!$(PJ_INSTALL_LDFLAGS)!" | \ -+ sed -e "s!@PJ_INSTALL_LDFLAGS_PRIVATE@!$(PJ_INSTALL_LDFLAGS_PRIVATE)!" | \ -+ sed -e "s!@PJ_INSTALL_CFLAGS@!$(PJ_INSTALL_CFLAGS)!" > $(DESTDIR)$(libdir)/pkgconfig/libpjproject.pc - - uninstall: - $(RM) $(DESTDIR)$(libdir)/pkgconfig/libpjproject.pc -diff -ruN pjsip/aconfigure pjsip-patch/aconfigure ---- pjsip/aconfigure 2024-09-03 10:10:13.926165600 +0200 -+++ pjsip-patch/aconfigure 2024-09-03 10:32:23.941785600 +0200 -@@ -5156,6 +5156,12 @@ - - $as_echo "#define WIN32_LEAN_AND_MEAN 1" >>confdefs.h - -+ case $target in -+ *_64-w64-mingw* ) -+ $as_echo "#define PJ_WIN64 1" >>confdefs.h -+ -+ ;; -+ esac - ;; - *darwin*) - $as_echo "#define PJ_DARWINOS 1" >>confdefs.h -diff -ruN pjsip/aconfigure.ac pjsip-patch/aconfigure.ac ---- pjsip/aconfigure.ac 2020-02-14 10:48:27.000000000 +0100 -+++ pjsip-patch/aconfigure.ac 2024-09-03 10:32:23.973036300 +0200 -@@ -161,6 +161,11 @@ - AC_DEFINE(PJ_WIN32,1) - AC_DEFINE(PJ_WIN32_WINNT,0x0400) - AC_DEFINE(WIN32_LEAN_AND_MEAN) -+ case $target in -+ *_64-w64-mingw* ) -+ AC_DEFINE(PJ_WIN64,1) -+ ;; -+ esac - ;; - *darwin*) - AC_DEFINE(PJ_DARWINOS,1) -diff -ruN pjsip/pjlib/include/pj/compat/os_auto.h.in pjsip-patch/pjlib/include/pj/compat/os_auto.h.in ---- pjsip/pjlib/include/pj/compat/os_auto.h.in 2020-02-14 10:48:27.000000000 +0100 -+++ pjsip-patch/pjlib/include/pj/compat/os_auto.h.in 2024-09-03 10:32:23.988659900 +0200 -@@ -30,6 +30,7 @@ - #undef PJ_OS_NAME - - /* Legacy macros */ -+#undef PJ_WIN64 - #undef PJ_WIN32 - #undef PJ_WIN32_WINNT - #undef WIN32_LEAN_AND_MEAN -diff -ruN pjsip/pjnath/src/pjnath-test/stun_sock_test.c pjsip-patch/pjnath/src/pjnath-test/stun_sock_test.c ---- pjsip/pjnath/src/pjnath-test/stun_sock_test.c 2020-02-14 10:48:27.000000000 +0100 -+++ pjsip-patch/pjnath/src/pjnath-test/stun_sock_test.c 2024-09-03 10:32:23.988659900 +0200 -@@ -25,7 +25,6 @@ - RESPOND_STUN = 1, - WITH_MAPPED = 2, - WITH_XOR_MAPPED = 4, -- - ECHO = 8 - }; - diff --git a/deps/patches/001_pjsip_210.patch b/deps/patches/2.10/001_pjsip_210.patch similarity index 100% copy from deps/patches/001_pjsip_210.patch copy to deps/patches/2.10/001_pjsip_210.patch diff --git a/deps/patches/002_zsrtp.patch b/deps/patches/2.10/002_zsrtp.patch similarity index 100% copy from deps/patches/002_zsrtp.patch copy to deps/patches/2.10/002_zsrtp.patch diff --git a/deps/patches/003_pjsip_tls_log_fix.patch b/deps/patches/2.10/003_pjsip_tls_log_fix.patch similarity index 100% copy from deps/patches/003_pjsip_tls_log_fix.patch copy to deps/patches/2.10/003_pjsip_tls_log_fix.patch diff --git a/deps/patches/003_vpx.patch b/deps/patches/2.10/003_vpx.patch similarity index 100% copy from deps/patches/003_vpx.patch copy to deps/patches/2.10/003_vpx.patch diff --git a/deps/patches/004_mac_audio_device_fix.patch b/deps/patches/2.10/004_mac_audio_device_fix.patch similarity index 100% copy from deps/patches/004_mac_audio_device_fix.patch copy to deps/patches/2.10/004_mac_audio_device_fix.patch diff --git a/deps/patches/006_fix_uint_zrtp.patch b/deps/patches/2.10/006_fix_uint_zrtp.patch similarity index 100% copy from deps/patches/006_fix_uint_zrtp.patch copy to deps/patches/2.10/006_fix_uint_zrtp.patch diff --git a/deps/patches/001_pjsip_210.patch b/deps/patches/2.11/001_pjsip_210.patch similarity index 97% rename from deps/patches/001_pjsip_210.patch rename to deps/patches/2.11/001_pjsip_210.patch index 02de08a5..4791e56b 100644 --- a/deps/patches/001_pjsip_210.patch +++ b/deps/patches/2.11/001_pjsip_210.patch @@ -1,8258 +1,8078 @@ diff -ruN pjproject-2.10/base_rev pjsip/base_rev --- pjproject-2.10/base_rev 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/base_rev 2021-02-07 00:21:58.163902742 +0100 @@ -0,0 +1 @@ -+210 ++211 diff -ruN pjproject-2.10/pjlib/src/pj/os_core_unix.c pjsip/pjlib/src/pj/os_core_unix.c --- pjproject-2.10/pjlib/src/pj/os_core_unix.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjlib/src/pj/os_core_unix.c 2021-02-06 23:59:38.145916811 +0100 @@ -37,6 +37,11 @@ #if defined(PJ_HAS_SEMAPHORE_H) && PJ_HAS_SEMAPHORE_H != 0 # include +# if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 +# include +# include +# include +# endif #endif #include // getpid() @@ -107,7 +112,11 @@ #if defined(PJ_HAS_SEMAPHORE) && PJ_HAS_SEMAPHORE != 0 struct pj_sem_t { +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + semaphore_t *sem; +#else sem_t *sem; +#endif char obj_name[PJ_MAX_OBJ_NAME]; }; #endif /* PJ_HAS_SEMAPHORE */ @@ -1569,35 +1578,16 @@ PJ_ASSERT_RETURN(sem, PJ_ENOMEM); #if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 - /* MacOS X doesn't support anonymous semaphore */ { - char sem_name[PJ_GUID_MAX_LENGTH+1]; - pj_str_t nam; - - /* We should use SEM_NAME_LEN, but this doesn't seem to be - * declared anywhere? The value here is just from trial and error - * to get the longest name supported. - */ -# define MAX_SEM_NAME_LEN 23 - - /* Create a unique name for the semaphore. */ - if (PJ_GUID_STRING_LENGTH <= MAX_SEM_NAME_LEN) { - nam.ptr = sem_name; - pj_generate_unique_string(&nam); - sem_name[nam.slen] = '\0'; - } else { - pj_create_random_string(sem_name, MAX_SEM_NAME_LEN); - sem_name[MAX_SEM_NAME_LEN] = '\0'; - } - - /* Create semaphore */ - sem->sem = sem_open(sem_name, O_CREAT|O_EXCL, S_IRUSR|S_IWUSR, - initial); - if (sem->sem == SEM_FAILED) - return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); - - /* And immediately release the name as we don't need it */ - sem_unlink(sem_name); + kern_return_t err; + sem->sem = PJ_POOL_ALLOC_T(pool, semaphore_t); + err = semaphore_create(mach_task_self(), sem->sem, SYNC_POLICY_FIFO, initial); + if (err != KERN_SUCCESS) { + if (err == KERN_RESOURCE_SHORTAGE) + return PJ_RETURN_OS_ERROR(ENOMEM); + else + return PJ_RETURN_OS_ERROR(EINVAL); + } } #else sem->sem = PJ_POOL_ALLOC_T(pool, sem_t); @@ -1633,6 +1623,7 @@ { #if PJ_HAS_THREADS int result; -+ int error; ++ //int error; PJ_CHECK_STACK(); PJ_ASSERT_RETURN(sem, PJ_EINVAL); @@ -1640,6 +1631,20 @@ PJ_LOG(6, (sem->obj_name, "Semaphore: thread %s is waiting", pj_thread_this()->obj_name)); +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + { + do + result = semaphore_wait(*(sem->sem)); + while (result == KERN_ABORTED); + + if (result == KERN_SUCCESS) { + result = error = 0; + } else { + result = -1; + error = EINVAL; + } + } +#else result = sem_wait( sem->sem ); if (result == 0) { @@ -1648,12 +1653,14 @@ } else { PJ_LOG(6, (sem->obj_name, "Semaphore: thread %s FAILED to acquire", pj_thread_this()->obj_name)); + error = pj_get_native_os_error(); } +#endif if (result == 0) return PJ_SUCCESS; else - return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + return PJ_RETURN_OS_ERROR(error); #else pj_assert( sem == (pj_sem_t*) 1 ); return PJ_SUCCESS; @@ -1667,20 +1674,45 @@ { #if PJ_HAS_THREADS int result; + int error; PJ_CHECK_STACK(); PJ_ASSERT_RETURN(sem, PJ_EINVAL); +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + { + mach_timespec_t interval; + kern_return_t err; + + interval.tv_sec = 0; + interval.tv_nsec = 0; + + err = semaphore_timedwait(*(sem->sem), interval); + if (err == KERN_SUCCESS) { + result = error = 0; + } else if (err == KERN_OPERATION_TIMED_OUT) { + result = -1; + error = EAGAIN; + } else { + result = -1; + error = EINVAL; + } + } +#else result = sem_trywait( sem->sem ); if (result == 0) { PJ_LOG(6, (sem->obj_name, "Semaphore acquired by thread %s", pj_thread_this()->obj_name)); + } else { + error = pj_get_native_os_error(); } +#endif + if (result == 0) return PJ_SUCCESS; else - return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + return PJ_RETURN_OS_ERROR(error); #else pj_assert( sem == (pj_sem_t*)1 ); return PJ_SUCCESS; @@ -1694,14 +1726,30 @@ { #if PJ_HAS_THREADS int result; + int error; PJ_LOG(6, (sem->obj_name, "Semaphore released by thread %s", pj_thread_this()->obj_name)); +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + { + kern_return_t err; + err = semaphore_signal(*(sem->sem)); + if (err == KERN_SUCCESS) { + result = error = 0; + } else { + result = -1; + error = EINVAL; + } + } +#else result = sem_post( sem->sem ); + if (result != 0) + error = pj_get_native_os_error(); +#endif if (result == 0) return PJ_SUCCESS; else - return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + return PJ_RETURN_OS_ERROR(error); #else pj_assert( sem == (pj_sem_t*) 1); return PJ_SUCCESS; @@ -1715,6 +1763,7 @@ { #if PJ_HAS_THREADS int result; + int error; PJ_CHECK_STACK(); PJ_ASSERT_RETURN(sem, PJ_EINVAL); @@ -1722,15 +1771,26 @@ PJ_LOG(6, (sem->obj_name, "Semaphore destroyed by thread %s", pj_thread_this()->obj_name)); #if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 - result = sem_close( sem->sem ); + { + kern_return_t err; + err = semaphore_destroy(mach_task_self(), *(sem->sem)); + if (err == KERN_SUCCESS) { + result = error = -1; + } else { + result = -1; + error = EINVAL; + } + } #else result = sem_destroy( sem->sem ); + if (result != 0) + error = pj_get_native_os_error(); #endif if (result == 0) return PJ_SUCCESS; else - return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + return PJ_RETURN_OS_ERROR(error); #else pj_assert( sem == (pj_sem_t*) 1 ); return PJ_SUCCESS; --- pjproject-2.10/pjmedia/include/pjmedia/config_auto.h.in 2021-02-05 09:00:45.241108718 +0100 +++ pjsip/pjmedia/include/pjmedia/config_auto.h.in 2021-02-22 21:46:52.232215546 +0100 @@ -36,6 +36,22 @@ #undef PJMEDIA_HAS_G711_CODEC #endif +#define PJMEDIA_HAS_VIDEO 1 +#define PJMEDIA_HAS_OPUS_CODEC 1 +#define PJMEDIA_HAS_VPX_CODEC_VP9 1 +#define PJMEDIA_HAS_FFMPEG_VID_CODEC 1 +#define PJMEDIA_HAS_LIBWEBRTC 1 +#define PJMEDIA_HAS_WEBRTC_AEC 1 + +#if defined(PJMEDIA_VIDEO_DEV_HAS_DARWIN) && (PJMEDIA_VIDEO_DEV_HAS_DARWIN != 0) +#define PJMEDIA_VIDEO_DEV_HAS_AVF 1 +#define PJMEDIA_VIDEO_HAS_VTOOLBOX 1 +#define PJMEDIA_HAS_VID_TOOLBOX_CODEC 1 +#endif + +#ifdef PJMEDIA_USE_OLD_FFMPEG +#undef PJMEDIA_USE_OLD_FFMPEG +#endif #endif /* __PJMEDIA_CONFIG_AUTO_H_ */ diff -ruN pjproject-2.10/pjmedia/build/Makefile pjproject-2.10/pjmedia/build/Makefile --- pjproject-2.10/pjmedia/build/Makefile 2021-02-05 09:00:45.241108718 +0100 +++ pjsip/pjmedia/build/Makefile 2021-02-22 21:46:52.232215546 +0100 @@ -72,8 +72,8 @@ sound_legacy.o sound_port.o stereo_port.o stream_common.o \ stream.o stream_info.o tonegen.o transport_adapter_sample.o \ transport_ice.o transport_loop.o transport_srtp.o transport_udp.o \ - types.o vid_codec.o vid_codec_util.o \ - vid_port.o vid_stream.o vid_stream_info.o vid_conf.o \ + transport_zrtp.o types.o vid_codec.o vid_codec_util.o mixer_port.o \ + vid_port.o vid_stream.o vid_tee.o vid_stream_info.o vid_conf.o \ wav_player.o wav_playlist.o wav_writer.o wave.o \ wsola.o audiodev.o videodev.o @@ -106,7 +106,7 @@ export PJMEDIA_VIDEODEV_SRCDIR = ../src/pjmedia-videodev export PJMEDIA_VIDEODEV_OBJS += videodev.o errno.o avi_dev.o ffmpeg_dev.o \ colorbar_dev.o v4l2_dev.o opengl_dev.o \ - util.o + util.o fb_dev.o null_dev.o export PJMEDIA_VIDEODEV_CFLAGS += $(_CFLAGS) export PJMEDIA_VIDEODEV_CXXFLAGS += $(_CXXFLAGS) export PJMEDIA_VIDEODEV_LDFLAGS += $(PJMEDIA_LDLIB) \ diff -ruN pjproject-2.10/pjmedia/include/pjmedia/event.h pjsip/pjmedia/include/pjmedia/event.h --- pjproject-2.10/pjmedia/include/pjmedia/event.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia/event.h 2021-02-06 16:57:17.374166159 +0100 @@ -83,6 +83,11 @@ PJMEDIA_EVENT_KEYFRAME_MISSING = PJMEDIA_FOURCC('I', 'F', 'R', 'M'), /** + * Remote video decoder asked for a keyframe. + */ + PJMEDIA_EVENT_KEYFRAME_REQUESTED = PJMEDIA_FOURCC('I', 'F', 'R', 'R'), + + /** * Video orientation has been changed event. */ PJMEDIA_EVENT_ORIENT_CHANGED = PJMEDIA_FOURCC('O', 'R', 'N', 'T'), diff -ruN pjproject-2.10/pjmedia/include/pjmedia/format.h pjsip/pjmedia/include/pjmedia/format.h --- pjproject-2.10/pjmedia/include/pjmedia/format.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia/format.h 2021-02-06 18:30:18.321176790 +0100 @@ -97,6 +97,7 @@ /** * 32bit RGB with alpha channel */ + PJMEDIA_FORMAT_ARGB = PJMEDIA_FORMAT_PACK('A', 'R', 'G', 'B'), PJMEDIA_FORMAT_RGBA = PJMEDIA_FORMAT_PACK('R', 'G', 'B', 'A'), PJMEDIA_FORMAT_BGRA = PJMEDIA_FORMAT_PACK('B', 'G', 'R', 'A'), diff -ruN pjproject-2.10/pjmedia/include/pjmedia/mixer_port.h pjsip/pjmedia/include/pjmedia/mixer_port.h --- pjproject-2.10/pjmedia/include/pjmedia/mixer_port.h 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia/mixer_port.h 2021-02-06 18:42:19.161906996 +0100 @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010 AG Projects + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __PJMEDIA_MIXER_PORT_H__ +#define __PJMEDIA_MIXER_PORT_H__ + +/** + * @file mixer_port.h + * @brief Mixer media port. + */ +#include + + + +/** + * @defgroup PJMEDIA_MIXER_PORT Mixer Port + * @ingroup PJMEDIA_PORT + * @brief The second simplest type of media port which forwards the frames it + * gets unchanged. + * @{ + */ + + +PJ_BEGIN_DECL + + +/** + * Create Mixer port. + * + * @param pool Pool to allocate memory. + * @param sampling_rate Sampling rate of the port. + * @param channel_count Number of channels. + * @param samples_per_frame Number of samples per frame. + * @param bits_per_sample Number of bits per sample. + * @param p_port Pointer to receive the port instance. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_mixer_port_create(pj_pool_t *pool, + unsigned sampling_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + pjmedia_port **p_port); + + +PJ_END_DECL + +/** + * @} + */ + + +#endif /* __PJMEDIA_MIXER_PORT_H__ */ diff -ruN pjproject-2.10/pjmedia/include/pjmedia/rtcp.h pjsip/pjmedia/include/pjmedia/rtcp.h --- pjproject-2.10/pjmedia/include/pjmedia/rtcp.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia/rtcp.h 2021-02-06 18:32:46.933482520 +0100 @@ -256,6 +256,8 @@ pjmedia_rtcp_stat stat; /**< Bidirectional stream stat. */ + pj_bool_t keyframe_requested; /** Set to true when RTCP PLI is received */ + #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) /** * Specify whether RTCP XR processing is enabled on this session. @@ -462,6 +464,23 @@ pj_size_t *length, const pj_str_t *reason); +/** + * Build an RTCP PLI packet. This packet can be appended to other RTCP + * packets, e.g: RTCP RR/SR, to compose a compound RTCP packet. + * + * @param session The RTCP session. + * @param buf The buffer to receive RTCP PLI packet. + * @param length On input, it will contain the buffer length. + * On output, it will contain the generated RTCP PLI + * packet length. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_rtcp_build_rtcp_pli( + pjmedia_rtcp_session *session, + void *buf, + pj_size_t *length); + /** * Call this function if RTCP XR needs to be enabled/disabled in the diff -ruN pjproject-2.10/pjmedia/include/pjmedia/signatures.h pjsip/pjmedia/include/pjmedia/signatures.h --- pjproject-2.10/pjmedia/include/pjmedia/signatures.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia/signatures.h 2021-02-06 18:33:45.139162846 +0100 @@ -153,6 +153,7 @@ #define PJMEDIA_SIG_PORT_ECHO PJMEDIA_SIG_CLASS_PORT_AUD('E','C') #define PJMEDIA_SIG_PORT_MEM_CAPTURE PJMEDIA_SIG_CLASS_PORT_AUD('M','C') #define PJMEDIA_SIG_PORT_MEM_PLAYER PJMEDIA_SIG_CLASS_PORT_AUD('M','P') +#define PJMEDIA_SIG_PORT_MIXER PJMEDIA_SIG_CLASS_PORT_AUD('M','X') #define PJMEDIA_SIG_PORT_NULL PJMEDIA_SIG_CLASS_PORT_AUD('N','U') #define PJMEDIA_SIG_PORT_RESAMPLE PJMEDIA_SIG_CLASS_PORT_AUD('R','E') #define PJMEDIA_SIG_PORT_SPLIT_COMB PJMEDIA_SIG_CLASS_PORT_AUD('S','C') diff -ruN pjproject-2.10/pjmedia/include/pjmedia/sound_port.h pjsip/pjmedia/include/pjmedia/sound_port.h --- pjproject-2.10/pjmedia/include/pjmedia/sound_port.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia/sound_port.h 2021-02-06 18:34:38.880711750 +0100 @@ -344,6 +344,16 @@ /** + * Reset the EC state in the sound port. + * + * @param snd_port The sound device port. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_snd_port_reset_ec_state(pjmedia_snd_port *snd_port); + + +/** * Connect a port to the sound device port. If the sound device port has a * sound recorder device, then this will start periodic function call to * the port's put_frame() function. If the sound device has a sound player diff -ruN pjproject-2.10/pjmedia/include/pjmedia/transport_ice.h pjsip/pjmedia/include/pjmedia/transport_ice.h --- pjproject-2.10/pjmedia/include/pjmedia/transport_ice.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia/transport_ice.h 2021-02-06 17:09:28.151078775 +0100 @@ -74,6 +74,28 @@ pj_status_t status, void *user_data); + /** + * This callback will be called when ICE state changes. + * + * @param tp PJMEDIA ICE transport. + * @param prev Previous state. + * @param curr Current state. + */ + void (*on_ice_state)(pjmedia_transport *tp, + pj_ice_strans_state prev, + pj_ice_strans_state curr); + + /** + * This callback will be called when ICE is stopped. + * + * @param tp PJMEDIA ICE transport. + * @param reason Reason for stopping ICE. + * @param err Error code + */ + void (*on_ice_stop)(pjmedia_transport *tp, + char *reason, + pj_status_t err); + } pjmedia_ice_cb; @@ -237,6 +259,17 @@ pjmedia_transport **p_tp); /** + * Return the ICE stream transport associated with this PJMEDIA transport + * + * @param tp Media transport instance. + * + * @return Pointer to the pj_ice_strans instance associated with this + * media transport. + */ +PJ_DECL(pj_ice_strans*) pjmedia_ice_get_strans(pjmedia_transport *tp); + + +/** * Get the group lock for the ICE media transport. * * @param tp The ICE media transport. diff -ruN pjproject-2.10/pjmedia/include/pjmedia/transport_zrtp.h pjsip/pjmedia/include/pjmedia/transport_zrtp.h --- pjproject-2.10/pjmedia/include/pjmedia/transport_zrtp.h 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia/transport_zrtp.h 2021-02-06 16:42:58.084103561 +0100 @@ -0,0 +1,647 @@ +/* $Id$ */ +/* + Copyright (C) 2010 Werner Dittmann + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef __PJMEDIA_TRANSPORT_ZRTP_H__ +#define __PJMEDIA_TRANSPORT_ZRTP_H__ + +/** + * @file transport_zrtp.h + * @brief ZRTP Media Transport Adapter + */ + +/* transport.h includes types.h -> config.h -> config_auto.h */ +#include + +#include "../../third_party/zsrtp/zrtp/zrtp/libzrtpcpp/ZrtpCWrapper.h" + +/** + * @defgroup PJMEDIA_TRANSPORT_ZRTP ZRTP Transport Adapter + * @brief This the ZRTP transport adapter. + * @{ + * + * PJMEDIA extension to support GNU ZRTP. + * + * ZRTP was developed by Phil Zimmermann and provides functions to + * negotiate keys and other necessary data (crypto data) to set-up + * the Secure RTP (SRTP) crypto context. Refer to Phil's ZRTP + * specification at his Zfone + * project site to get more detailed information about the + * capabilities of ZRTP. + * + * Short overview of the ZRTP implementation + * + * ZRTP is a specific protocol to negotiate encryption algorithms + * and the required key material. ZRTP uses a RTP session to + * exchange its protocol messages. Thus ZRTP is independent of any + * signaling protocol like SIP, XMPP and alike. + * + * A complete GNU ZRTP implementation consists of two parts, the + * GNU ZRTP core and some specific code that binds the GNU ZRTP core to + * the underlying RTP/SRTP stack and the operating system: + *
    + *
  • + * The GNU ZRTP core is independent of a specific RTP/SRTP + * stack and the operationg system and consists of the ZRTP + * protocol state engine, the ZRTP protocol messages, and the + * GNU ZRTP engine. The GNU ZRTP engine provides methods to + * setup ZRTP message and to analyze received ZRTP messages, + * to compute the crypto data required for SRTP, and to + * maintain the required hashes and HMAC. + *
  • + *
  • + * The second part of an implementation is specific + * glue code the binds the GNU ZRTP core to the + * actual RTP/SRTP implementation and other operating system + * specific services such as timers, mutexes. + *
  • + *
+ * + * The GNU ZRTP core uses callback methods (refer to + * zrtp_Callback) to access RTP/SRTP or operating specific methods, + * for example to send data via the RTP stack, to access + * timers, provide mutex handling, and to report events to the + * application. + * + * The PJMEDIA ZRTP transport + * + * ZRTP transport implements code that is specific to the pjmedia + * implementation. ZRTP transport also implements the specific code to + * provide the mutex and timeout handling to the GNU ZRTP + * core. Both, the mutex and the timeout handling, use the pjlib + * library to stay independent of the operating + * seystem. + * + * To perform its tasks ZRTP transport + *
    + *
  • implements the pjmedia transport functions and callbacks. + *
  • + *
  • implements the zrtp_Callbacks methods to provide + * access and other specific services (timer, mutex) to GNU + * ZRTP + *
  • + *
  • provides ZRTP specific methods that applications may use + * to control and setup GNU ZRTP + *
  • + *
  • can register and use an application specific callback + * class (refer to zrtp_UserCallbacks) + *
  • + *
+ * + * After instantiating a GNU ZRTP session (see below for a short + * example) applications may use the methods of + * ZRTP transport and the ZRTP engine to control and setup GNU ZRTP, + * for example enable or disable ZRTP processing or getting ZRTP status + * information. + * + * GNU ZRTP defines zrtp_UserCallback methods structure that an application + * may use and register with ZRTP transport. GNU ZRTP and ZRTP transport + * use the zrtp_UserCallback methods to report ZRTP events to the + * application. The application may display this information to + * the user or act otherwise. + * + * The following figure depicts the relationships between + * ZRTP transport, pjmedia RTP implementation, the GNU ZRTP core, + * SRTP and an application that provides zrtp_UserCallback methods. + * + @verbatim + +-----------+ + | | + | SRTP-ZRTP | + | | + +-----------+ + |C Wrapper | + +-----+-----+ + | + | uses + | + +-----------------+ +-------+--------+ +-+-----------------+ + | App (pjsua) | | | |C| | + | creates a | uses | transport_zrtp | uses | | GNU ZRTP | + | ZRTP transport +------+ implements +------+W| core | + | and implements | | zrtp_Callback | |r| implementation | + |zrtp_UserCallback| | | |a| (ZRtp et al) | + +-----------------+ +----------------+ |p| | + +-+-----------------+ + +@endverbatim + * + * The following short code snippet shows how to use ZRTP transport + * + * @code + * + * #include + * ... + * // Create media transport + * status = pjmedia_transport_udp_create(med_endpt, NULL, local_port, + * 0, &transport); + * if (status != PJ_SUCCESS) + * return status; + * + * status = pjmedia_transport_zrtp_create(med_endpt, NULL, transport, + * &zrtp_tp); + * app_perror(THIS_FILE, "Error creating zrtp", status); + * transport = zrtp_tp; + * if (dir == PJMEDIA_DIR_ENCODING) + * pjmedia_transport_zrtp_initialize(transport, "testenc.zid", 1, NULL); + * else + * pjmedia_transport_zrtp_initialize(transport, "testdec.zid", 1, NULL); + * ... + * @endcode + * + */ + +#define PJMEDIA_TRANSPORT_TYPE_ZRTP PJMEDIA_TRANSPORT_TYPE_USER+2 + +PJ_BEGIN_DECL + +/** + * ZRTP option. + */ +typedef enum pjmedia_zrtp_use +{ + /** When this flag is specified, ZRTP will be disabled. */ + PJMEDIA_NO_ZRTP = 1, + + /** When this flag is specified, PJSUA-LIB creates a ZRTP transport + * call calls back the applicaion for further process if callback is + * set. + */ + PJMEDIA_CREATE_ZRTP = 2 + +} pjmedia_zrtp_use; + +/** + * This structure specifies ZRTP transport specific info. This will fit + * into \a buffer field of pjmedia_transport_specific_info. + */ +typedef struct pjmedia_zrtp_info +{ + /** + * Specify whether the ZRTP transport is active for this session. + */ + pj_bool_t active; + + /** + * Specify the cipher being used. + */ + char cipher[128]; + +} pjmedia_zrtp_info; + +/** + * Application callback methods. + * + * The RTP stack specific part of GNU ZRTP uses these callback methods + * to report ZRTP events to the application. Thus the application that + * instantiates the RTP stack shall implement these methods and show these + * inforemation to the user. + * + * CAVEAT
+ * All user callback methods run in the context of the RTP thread. Thus + * it is of paramount importance to keep the execution time of the methods + * as short as possible. + * + * @author Werner Dittmann + */ +typedef struct pjmedia_zrtp_cb +{ + /** + * Inform user interface that security is active now. + * + * ZRTP calls this method if the sender and the receiver are + * in secure mode now. + * + * @param cipher + * Name and mode of cipher used to encrypt the SRTP stream + */ + void (*secure_on)(pjmedia_transport *tp, char* cipher); + + /** + * Inform user interface that security is not active any more. + * + * ZRTP calls this method if either the sender or the receiver + * left secure mode. + * + */ + void (*secure_off)(pjmedia_transport *tp); + + /** + * Show the Short Authentication String (SAS) on user interface. + * + * ZRTP calls this method to display the SAS and inform about the SAS + * verification status. The user interface shall enable a SAS verfication + * button (or similar UI element). The user shall click on this UI + * element after he/she confirmed the SAS code with the partner. + * + * @param sas + * The string containing the SAS. + * @param verified + * If verified is true then SAS was verified by both + * parties during a previous call, otherwise it is set to false. + */ + void (*show_sas)(pjmedia_transport *tp, char* sas, int32_t verified); + + /** + * Inform the user that ZRTP received "go clear" message from its peer. + * + * On receipt of a go clear message the user is requested to confirm + * a switch to unsecure (clear) modus. Until the user confirms ZRTP + * (and the underlying RTP) does not send any data. + * + */ + void (*confirm_go_clear)(pjmedia_transport *tp); + + /** + * Show some information to user. + * + * ZRTP calls this method to display some information to the user. + * Along with the message ZRTP provides a severity indicator that + * defines: Info, Warning, Error, and Alert. Refer to the + * MessageSeverity enum in ZrtpCodes.h. The + * UI may use this indicator to highlight messages or alike. + * + * @param sev + * Severity of the message. + * @param subCode + * The subcode identifying the reason. + */ + void (*show_message)(pjmedia_transport *tp, int32_t sev, int32_t subCode); + + /** + * ZRTP transport calls this if the negotiation failed. + * + * ZRTPQueue calls this method in case ZRTP negotiation failed. The + * parameters show the severity as well as some explanatory text. + * Refer to the MessageSeverity enum above. + * + * @param severity + * This defines the message's severity + * @param subCode + * The subcode identifying the reason. + */ + void (*negotiation_failed)(pjmedia_transport *tp, int32_t severity, int32_t subCode); + + /** + * ZRTP transport calls this method if the other side does not support ZRTP. + * + * If the other side does not answer the ZRTP Hello packets then + * ZRTP calls this method. + * + */ + void (*not_supported_by_other)(pjmedia_transport *tp); + + /** + * ZRTP transport calls this method to inform about a PBX enrollment request. + * + * Please refer to chapter 8.3 ff to get more details about PBX enrollment + * and SAS relay. + * + * @param info + * Give some information to the user about the PBX requesting an + * enrollment. + */ + void (*ask_enrollment)(pjmedia_transport *tp, int32_t info); + + /** + * ZRTP transport calls this method to inform about PBX enrollment result. + * + * Informs the use about the acceptance or denial of an PBX enrollment + * request + * + * @param info + * Give some information to the user about the result of an + * enrollment. + */ + void (*inform_enrollment)(pjmedia_transport *tp, int32_t info); + + /** + * ZRTP transport calls this method to request a SAS signature. + * + * After ZRTP core was able to compute the Short Authentication String + * (SAS) it calls this method. The client may now use an approriate + * method to sign the SAS. The client may use + * setSignatureData() of ZrtpQueue to store the signature + * data an enable signature transmission to the other peer. Refer + * to chapter 8.2 of ZRTP specification. + * + * @param sas + * The SAS string to sign. + * @see ZrtpQueue#setSignatureData + * + */ + void (*sign_sas)(pjmedia_transport *tp, uint8_t* sas); + + /** + * ZRTP transport calls this method to request a SAS signature check. + * + * After ZRTP received a SAS signature in one of the Confirm packets it + * call this method. The client may use getSignatureLength() + * and getSignatureData()of ZrtpQueue to get the signature + * data and perform the signature check. Refer to chapter 8.2 of ZRTP + * specification. + * + * If the signature check fails the client may return false to ZRTP. In + * this case ZRTP signals an error to the other peer and terminates + * the ZRTP handshake. + * + * @param sas + * The SAS string that was signed by the other peer. + * @return + * true if the signature was ok, false otherwise. + * + */ + int32_t (*check_sas_signature)(pjmedia_transport *tp, uint8_t* sas); +} pjmedia_zrtp_cb; + + +/** + * Create the transport adapter, specifying the underlying transport to be + * used to send and receive RTP/RTCP packets. + * + * @param endpt The media endpoint. + * @param timer_heap The heap where timers will be scheduled. + * @param transport The underlying media transport to send and receive + * RTP/RTCP packets. + * @param p_tp Pointer to receive the media transport instance. + * + * @param close_slave + * Close the slave transport on transport_destroy. PJSUA-LIB + * sets this to PJ_FALSE because it takes care of this. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjmedia_transport_zrtp_create( pjmedia_endpt *endpt, + pj_timer_heap_t *timer_heap, + pjmedia_transport *transport, + pjmedia_transport **p_tp, + pj_bool_t close_slave); + +/* + * Implement the specific ZRTP transport functions + */ + +/** + * Initialize the ZRTP transport. + * + * Before an application can use ZRTP it has to initialize the + * ZRTP implementation. This method opens a file that contains ZRTP specific + * information such as the applications ZID (ZRTP id) and its + * retained shared secrets. + * + * Before an application initializes the ZRTP it may use ZRTP functions + * to set specific configuration data. See the relevant documentation + * in @c ZrtpCWrapper.h . The application can peform this after + * it created transport_zrtp. + * + * If one application requires several ZRTP sessions all + * sessions use the same timeout thread and use the same ZID + * file. Therefore an application does not need to do any + * synchronisation regading ZID files or timeouts. This is + * managed by the ZRTP implementation. + * + * The current implementation of ZRTP transport does not support + * different ZID files for one application instance. This + * restriction may be removed in later versions. + * + * The application may specify its own ZID file name. If no + * ZID file name is specified it defaults to + * $HOME/.GNUccRTP.zid if the HOME + * environment variable is set. If it is not set the current + * directory is used. + * + * If the method could set up the timeout thread and open the ZID + * file then it enables ZRTP processing and returns. + * + * @param tp + * Pointer to the ZRTP transport data as returned by + * @c pjmedia_transport_zrtp_create. + * + * @param zidFilename + * The name of the ZID file, can be a relative or absolut + * filename. + * + * @param autoEnable + * if set to true the method automatically sets enableZrtp to + * true. This enables the ZRTP auto-sense mode. + * + * @param zrtp_cb + * Pointer the application's ZRTP callbacks structure. Setting + * a NULL switches off the user callbacks + * @return + * PJ_SUCCESS on success, ZRTP processing enabled, other codes + * leave ZRTP processing disabled. + * + */ +PJ_DECL(pj_status_t) pjmedia_transport_zrtp_initialize(pjmedia_transport *tp, + const char *zidFilename, + pj_bool_t autoEnable, + pjmedia_zrtp_cb *zrtp_cb); +/** + * Enable or disable ZRTP processing. + * + * Call this method to enable or disable ZRTP processing after + * calling pjmedia_transport_zrtp_initialize with the + * parameter @c autoEnable set to false. This can be done before + * using a RTP session or at any time during a RTP session. + * + * Existing SRTP sessions or currently active ZRTP processing will + * not be stopped or disconnected. + * + * If the application enables ZRTP then: + *
    + *
  • ZRTP transport starts to send ZRTP Hello packets after at least + * one RTP packet was sent and received on the associated RTP + * session. Thus if an application enables ZRTP and ZRTP transport + * detects traffic on the RTP session then ZRTP transport automatically + * starts the ZRTP protocol. This automatic start is convenient + * for applications that negotiate RTP parameters and set up RTP + * sessions but the actual RTP traffic starts some time later. + *
  • + *
  • ZRTP transport analyses incoming packets to detect ZRTP + * messages. If ZRTP was started, either via automatic start (see + * above) or explicitly via @c zrtp_startZrtp, then ZrtpQueue + * forwards ZRTP packets to the GNU ZRTP core. + *
+ * + * @param tp + * Pointer to the ZRTP transport data as returned by + * @c pjmedia_transport_zrtp_create. + * + * @param onOff + * @c 1 to enable ZRTP, @c 0 to disable ZRTP + */ +PJ_DECL(void) pjmedia_transport_zrtp_setEnableZrtp(pjmedia_transport *tp, pj_bool_t onOff); + +/** + * Return the state of ZRTP enable state. + * + * @param tp + * Pointer to the ZRTP transport data as returned by + * @c pjmedia_transport_zrtp_create. + * + * @return @c true if ZRTP processing is enabled, @c false + * otherwise. + */ +PJ_DECL(pj_bool_t) pjmedia_transport_zrtp_isEnableZrtp(pjmedia_transport *tp); + +/** + * Starts the ZRTP protocol engine. + * + * Applications may call this method to immediatly start the ZRTP protocol + * engine any time after initializing ZRTP and setting optinal parameters, + * for example client id or multi-stream parameters. + * + * If the application does not call this method but sucessfully initialized + * the ZRTP engine using @c pjmedia_transport_zrtp_initialize then ZRTP may + * also start, depending on the autoEnable parameter. + * + * @param tp + * Pointer to the ZRTP transport data as returned by + * @c pjmedia_transport_zrtp_create. + * + * @see pjmedia_transport_zrtp_initialize + */ +PJ_DECL(void) pjmedia_transport_zrtp_startZrtp(pjmedia_transport *tp); + +/** + * Stops the ZRTP protocol engine. + * + * Applications call this method to stop the ZRTP protocol + * engine. The ZRTP transport can not start or process any ZRTP + * negotiations. + * + * This call does not deactivate SRTP processing of ZRTP transport, thus + * the ZRTP transport still encrypts/decrypts data via SRTP. + * + * @param tp + * Pointer to the ZRTP transport data as returned by + * @c pjmedia_transport_zrtp_create. + * + */ +PJ_DECL(void) pjmedia_transport_zrtp_stopZrtp(pjmedia_transport *tp); + +/** + * Set the local SSRC in case of receive-only sessions. + * + * Receiver-only RTP sessions never send RTP packets, thus ZRTP cannot learn + * the local (sender) SSRC. ZRTP requires the SSRC to bind the RTP session + * to the SRTP and its handshake. In this case the application shall generate + * a SSRC value and set it. + * + * Usually an application knows if a specific RTP session is receive-only, for + * example by inspecting and parsing the SDP data. + * + * If the application later decides to switch this RTP session to full-duplex + * mode (send and receive) it shall use the generated SSRC to intialize the + * RTP session. Then the outgoing packets are encrypted by SRTP. + * + * @param tp + * Pointer to the ZRTP transport data as returned by + * @c pjmedia_transport_zrtp_create. + * + * @param ssrc + * The local ssrc value in host order. + */ +PJ_DECL(void) pjmedia_transport_zrtp_setLocalSSRC(pjmedia_transport *tp, uint32_t ssrc); + +/** + * Check the state of the MitM mode flag. + * + * If true then this ZRTP session acts as MitM, usually enabled by a PBX + * client (user agent) + * + * @return state of mitmMode + */ +PJ_DECL(pj_bool_t) pjmedia_transport_zrtp_isMitmMode(pjmedia_transport *tp); + +/** + * Set the state of the MitM mode flag. + * + * If MitM mode is set to true this ZRTP session acts as MitM, usually + * enabled by a PBX client (user agent). + * + * @param mitmMode defines the new state of the mitmMode flag + */ +PJ_DECL(void) pjmedia_transport_zrtp_setMitmMode(pjmedia_transport *tp, pj_bool_t mitmMode); + +/** + * Set / reset the SAS verification flag. + * + */ +PJ_DECL(void) pjmedia_transport_zrtp_setSASVerified(pjmedia_transport *tp, pj_bool_t verified); + +/** + * Get the peer's ZID. + * + */ +PJ_DECL(int) pjmedia_transport_zrtp_getPeerZid(pjmedia_transport *tp, unsigned char* data); + +/** + * Get the peer's name. + * + */ +PJ_DECL(char*) pjmedia_transport_zrtp_getPeerName(pjmedia_transport *tp); + +/** + * Set the peer's name. + * + */ +PJ_DECL(void) pjmedia_transport_zrtp_putPeerName(pjmedia_transport *tp, const char *name); + + +PJ_DECL(char*) pjmedia_transport_zrtp_getMultiStreamParameters(pjmedia_transport *tp, pj_int32_t *length); + +PJ_DECL(void) pjmedia_transport_zrtp_setMultiStreamParameters(pjmedia_transport *tp, const char *parameters, pj_int32_t length, pjmedia_transport *master_tp); + +/** + * Get the ZRTP context pointer. + * + * Appplications need the ZRTP context pointer if they call ZRTP specific + * methods. The ZRTP specific include file @c ZrtpCWrapper contains the + * descriptions of the ZRTP methods. + * + * @return Pointer to ZRTP context + * + * @see zrtp_setAuxSecret() + * @see zrtp_setPbxSecret() + * @see zrtp_inState() + * @see zrtp_SASVerified() + * @see zrtp_resetSASVerified() + * @see zrtp_getHelloHash() + * @see zrtp_getMultiStrParams() + * @see zrtp_setMultiStrParams() + * @see zrtp_isMultiStream() + * @see zrtp_isMultiStreamAvailable() + * @see zrtp_acceptEnrollment() + * @see zrtp_setSignatureData() + * @see zrtp_getSignatureData() + * @see zrtp_getSignatureLength() + * @see zrtp_getZid(); + */ +PJ_DECL(ZrtpContext*) pjmedia_transport_zrtp_getZrtpContext(pjmedia_transport *tp); + +PJ_END_DECL + + +/** + * @} + */ + +#endif /* __PJMEDIA_TRANSPORT_ADAPTER_SAMPLE_H__ */ + + -diff -ruN pjproject-2.10/pjmedia/include/pjmedia/vid_stream.h pjsip/pjmedia/include/pjmedia/vid_stream.h ---- pjproject-2.10/pjmedia/include/pjmedia/vid_stream.h 2020-02-14 10:48:27.000000000 +0100 -+++ pjsip/pjmedia/include/pjmedia/vid_stream.h 2021-02-06 18:35:36.234362341 +0100 -@@ -471,6 +471,16 @@ - pjmedia_vid_stream_get_rtp_session_info(pjmedia_vid_stream *stream, - pjmedia_stream_rtp_sess_info *session_info); - -+/** -+ * Send RTCP PLI for the media stream. -+ * -+ * @param stream The media stream. -+ * -+ * @return PJ_SUCCESS on success. -+ */ -+PJ_DECL(pj_status_t) pjmedia_vid_stream_send_rtcp_pli( -+ pjmedia_vid_stream *stream); -+ - - /** - * @} --- pjproject-2.10/pjmedia/include/pjmedia/audiodev.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia/audiodev.h 2021-02-06 16:53:28.395408793 +0100 @@ -24,6 +24,7 @@ * @file audiodev.h * @brief Audio device API. */ +#include #include #include #include @@ -93,6 +94,38 @@ typedef pjmedia_aud_dev_factory* (*pjmedia_aud_dev_factory_create_func_ptr)(pj_pool_factory*); +typedef enum pjmedia_aud_dev_event { + PJMEDIA_AUD_DEV_DEFAULT_INPUT_CHANGED, + PJMEDIA_AUD_DEV_DEFAULT_OUTPUT_CHANGED, + PJMEDIA_AUD_DEV_LIST_WILL_REFRESH, + PJMEDIA_AUD_DEV_LIST_DID_REFRESH +} pjmedia_aud_dev_event; + + +typedef void (*pjmedia_aud_dev_observer_callback)(pjmedia_aud_dev_event event); + +/** + * This structure specifies the parameters to set an audio device observer + */ +typedef struct pjmedia_aud_dev_observer { + pjmedia_aud_dev_observer_callback cb; + pj_pool_t *pool; + pj_mutex_t *lock; + pj_thread_t *thread; + pj_thread_desc thread_desc; +} pjmedia_aud_dev_observer; + +/** + * Set an audio device observer callback. + * + * @param cb The callback that needs to be registred, or NULL in + * in case it needs to be unregistered. Only one callback + * can be registered. + * + * @return PJ_SUCCESS on successful operation or the appropriate + * error code. + */ +PJ_DECL(pj_status_t) pjmedia_aud_dev_set_observer_cb(pjmedia_aud_dev_observer_callback cb); /* Audio driver structure */ typedef struct pjmedia_aud_driver @@ -120,6 +150,8 @@ unsigned dev_cnt; /* Total number of devices. */ pj_uint32_t dev_list[PJMEDIA_AUD_MAX_DEVS];/* Array of dev IDs. */ + pjmedia_aud_dev_observer dev_observer; + } pjmedia_aud_subsys; diff -ruN pjproject-2.10/pjmedia/include/pjmedia-audiodev/audiodev_imp.h pjsip/pjmedia/include/pjmedia-audiodev/audiodev_imp.h --- pjproject-2.10/pjmedia/include/pjmedia-audiodev/audiodev_imp.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia-audiodev/audiodev_imp.h 2021-02-06 18:37:31.685678395 +0100 @@ -29,6 +29,15 @@ * @{ */ +typedef enum pjmedia_aud_dev_change_event { + DEFAULT_INPUT_CHANGED = 1, + DEFAULT_OUTPUT_CHANGED, + DEVICE_LIST_CHANGED +} pjmedia_aud_dev_change_event; + +typedef void (*pjmedia_aud_dev_change_callback)(pjmedia_aud_dev_change_event event); + + /** * Sound device factory operations. */ @@ -99,6 +108,30 @@ */ pj_status_t (*refresh)(pjmedia_aud_dev_factory *f); + /** + * Set audio device change callback + * + * @param f The audio device factory. + * @param cb The audio device change callback. + */ + void (*set_dev_change_cb)(pjmedia_aud_dev_factory *f, + pjmedia_aud_dev_change_callback cb); + + /** + * Get default recording device index + * + * @param f The audio device factory. + */ + int (*get_default_rec_dev)(pjmedia_aud_dev_factory *f); + + /** + * Get default playback device index + * + * @param f The audio device factory. + */ + int (*get_default_play_dev)(pjmedia_aud_dev_factory *f); + + } pjmedia_aud_dev_factory_op; diff -ruN pjproject-2.10/pjmedia/include/pjmedia.h pjsip/pjmedia/include/pjmedia.h --- pjproject-2.10/pjmedia/include/pjmedia.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia.h 2021-02-06 18:40:23.766607985 +0100 @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -67,6 +68,7 @@ #include #include #include +#include #include #include #include @@ -72,7 +74,7 @@ #include #include #include -//#include +#include #include #include #include diff -ruN pjproject-2.10/pjmedia/include/pjmedia-videodev/fb_dev.h pjsip/pjmedia/include/pjmedia-videodev/fb_dev.h --- pjproject-2.10/pjmedia/include/pjmedia-videodev/fb_dev.h 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia-videodev/fb_dev.h 2021-02-06 18:43:39.220193284 +0100 @@ -0,0 +1,32 @@ +/* $Id$ */ +/* + * Copyright (C) 2014-present AG Projects + * Copyright (C) 2013-2014 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef PJMEDIA_VIDEODEV_FB_DEV_H__ +#define PJMEDIA_VIDEODEV_FB_DEV_H__ + +#include + +typedef void (*pjmedia_vid_dev_fb_frame_cb)(const pjmedia_frame *frame, const pjmedia_rect_size size, void *user_data); + +pj_status_t +pjmedia_vid_dev_fb_set_callback(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_fb_frame_cb cb, + void *user_data); + +#endif /* PJMEDIA_VIDEODEV_FB_DEV_H__ */ diff -ruN pjproject-2.10/pjmedia/include/pjmedia_videodev.h pjsip/pjmedia/include/pjmedia_videodev.h --- pjproject-2.10/pjmedia/include/pjmedia_videodev.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia_videodev.h 2021-02-06 18:39:51.429682633 +0100 @@ -27,5 +27,6 @@ #include #include #include +#include #endif /* __PJMEDIA_VIDEODEV_H__ */ diff -ruN pjproject-2.10/pjmedia/src/pjmedia/converter.c pjsip/pjmedia/src/pjmedia/converter.c --- pjproject-2.10/pjmedia/src/pjmedia/converter.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/converter.c 2021-02-06 20:19:28.491163002 +0100 @@ -19,6 +19,7 @@ #include #include #include +#include #define THIS_FILE "converter.c" @@ -174,6 +175,24 @@ if (status != PJ_SUCCESS) return status; + if (param->src.type == PJMEDIA_TYPE_VIDEO) { + char src_fourcc_name[5]; + char dst_fourcc_name[5]; + PJ_LOG(4, (THIS_FILE, "Converter %p (%s) created for video: %dx%d %s -> %dx%d %s", + cv, + f->name, + param->src.det.vid.size.w, + param->src.det.vid.size.h, + pjmedia_fourcc_name(param->src.id, src_fourcc_name), + param->dst.det.vid.size.w, + param->dst.det.vid.size.h, + pjmedia_fourcc_name(param->dst.id, dst_fourcc_name))); + } else if (param->src.type == PJMEDIA_TYPE_AUDIO) { + PJ_LOG(4, (THIS_FILE, "Converter %p created for audio", cv)); + } else { + PJ_LOG(4, (THIS_FILE, "Converter %p created for unknown", cv)); + } + *p_cv = cv; return PJ_SUCCESS; @@ -188,6 +207,7 @@ PJ_DEF(void) pjmedia_converter_destroy(pjmedia_converter *cv) { + PJ_LOG(4, (THIS_FILE, "Converter %p destroyed", cv)); (*cv->op->destroy)(cv); } diff -ruN pjproject-2.10/pjmedia/src/pjmedia/echo_common.c pjsip/pjmedia/src/pjmedia/echo_common.c --- pjproject-2.10/pjmedia/src/pjmedia/echo_common.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/echo_common.c 2021-02-06 20:22:22.888063103 +0100 @@ -135,7 +135,7 @@ #if defined(PJMEDIA_HAS_WEBRTC_AEC) && PJMEDIA_HAS_WEBRTC_AEC!=0 static struct ec_operations webrtc_aec_op = { - "WebRTC AEC", + "WEBRTC AEC", &webrtc_aec_create, &webrtc_aec_destroy, &webrtc_aec_reset, diff -ruN pjproject-2.10/pjmedia/src/pjmedia/echo_webrtc_aec.c pjsip/pjmedia/src/pjmedia/echo_webrtc_aec.c --- pjproject-2.10/pjmedia/src/pjmedia/echo_webrtc_aec.c 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/echo_webrtc_aec.c 2021-02-06 18:45:18.907037776 +0100 @@ -0,0 +1,643 @@ +/** + * Copyright (C) 2011-2013 AG Projects + * Copyright (C) 2010 Regis Montoya (aka r3gis - www.r3gis.fr) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include +#include +#include +#include +#include +#include + + +#if defined(PJMEDIA_HAS_WEBRTC_AEC) && PJMEDIA_HAS_WEBRTC_AEC != 0 + +/* 0: conservative, 1: moderate, 2: aggresive */ +#ifndef PJMEDIA_WEBRTC_AEC_AGGRESSIVENESS + #define PJMEDIA_WEBRTC_AEC_AGGRESSIVENESS 2 +#endif + +/* 0: mild, 1: mediumn, 2: aggressive */ +#ifndef PJMEDIA_WEBRTC_NS_POLICY + #define PJMEDIA_WEBRTC_NS_POLICY 0 +#endif + +#define THIS_FILE "echo_webrtc_aec.c" + +#include +#include +#include +#include + +#include "echo_internal.h" + + +/* + * This file contains the implementation of an echo canceller and noise suppressor for PJSIP which uses components + * from the WebRTC project. Things to take into account: + * + * - The WebRTC engine works with 10ms frames, while in PJSIP we use 20ms frames mostly, all data fed to WebRTC elements needs + * to be chunked in 10ms chunks. + * - When a 32kHz sampling rate is used, the WebRTC engine needs frames to be passed split into low and high frequencies. PJSIP + * will give us a frame with all frequencies, so the signal processing library in WebRTC must be used to split frames into low + * and high frequencies, and combine them later. + */ + + +typedef struct AudioBuffer +{ + int samples_per_channel; + pj_bool_t is_split; + + WebRtc_Word16* data; + WebRtc_Word16 low_pass_data[160]; + WebRtc_Word16 high_pass_data[160]; + + WebRtc_Word32 analysis_filter_state1[6]; + WebRtc_Word32 analysis_filter_state2[6]; + WebRtc_Word32 synthesis_filter_state1[6]; + WebRtc_Word32 synthesis_filter_state2[6]; +} AudioBuffer; + +static WebRtc_Word16* AudioBuffer_GetData(AudioBuffer *ab); +static WebRtc_Word16* AudioBuffer_GetLowPassData(AudioBuffer *ab); +static WebRtc_Word16* AudioBuffer_GetHighPassData(AudioBuffer *ab); +static void AudioBuffer_SetData(AudioBuffer *ab, WebRtc_Word16 *data); +static void AudioBuffer_Initialize(AudioBuffer *ab, int sample_rate); +static int AudioBuffer_SamplesPerChannel(AudioBuffer *ab); + + +static WebRtc_Word16* AudioBuffer_GetData(AudioBuffer *ab) +{ + pj_assert(ab->data); + + if (ab->is_split) { + WebRtcSpl_SynthesisQMF(ab->low_pass_data, + ab->high_pass_data, + ab->data, + ab->synthesis_filter_state1, + ab->synthesis_filter_state2); + } + return ab->data; +} + + +static WebRtc_Word16* AudioBuffer_GetLowPassData(AudioBuffer *ab) +{ + if (!ab->is_split) { + return ab->data; + } else { + return ab->low_pass_data; + } +} + + +static WebRtc_Word16* AudioBuffer_GetHighPassData(AudioBuffer *ab) +{ + if (!ab->is_split) { + return ab->data; + } else { + return ab->high_pass_data; + } +} + + +static void AudioBuffer_Initialize(AudioBuffer *ab, int sample_rate) +{ + pj_bzero(ab, sizeof(AudioBuffer)); + if (sample_rate == 32000) { + ab->is_split = PJ_TRUE; + ab->samples_per_channel = 160; + } else { + ab->is_split = PJ_FALSE; + ab->samples_per_channel = sample_rate / 100; + } +} + + +static void AudioBuffer_SetData(AudioBuffer *ab, WebRtc_Word16 *data) +{ + ab->data = data; + if (ab->is_split) { + /* split data into low and high bands */ + WebRtcSpl_AnalysisQMF(ab->data, /* input data */ + ab->low_pass_data, /* pointer to low pass data storage*/ + ab->high_pass_data, /* pointer to high pass data storage*/ + ab->analysis_filter_state1, + ab->analysis_filter_state2); + } +} + + +static int AudioBuffer_SamplesPerChannel(AudioBuffer *ab) +{ + return ab->samples_per_channel; +} + + +const WebRtc_Word16 kFilterCoefficients8kHz[5] = + {3798, -7596, 3798, 7807, -3733}; + +const WebRtc_Word16 kFilterCoefficients[5] = + {4012, -8024, 4012, 8002, -3913}; + +typedef struct { + WebRtc_Word16 y[4]; + WebRtc_Word16 x[2]; + const WebRtc_Word16* ba; +} HighPassFilterState; + + +static int HighPassFilter_Initialize(HighPassFilterState* hpf, int sample_rate) { + assert(hpf != NULL); + + if (sample_rate == 8000) { + hpf->ba = kFilterCoefficients8kHz; + } else { + hpf->ba = kFilterCoefficients; + } + + WebRtcSpl_MemSetW16(hpf->x, 0, 2); + WebRtcSpl_MemSetW16(hpf->y, 0, 4); + + return 0; +} + + +static int HighPassFilter_Process(HighPassFilterState* hpf, WebRtc_Word16* data, int length) { + assert(hpf != NULL); + + int i; + WebRtc_Word32 tmp_int32 = 0; + WebRtc_Word16* y = hpf->y; + WebRtc_Word16* x = hpf->x; + const WebRtc_Word16* ba = hpf->ba; + + for (i = 0; i < length; i++) { + // y[i] = b[0] * x[i] + b[1] * x[i-1] + b[2] * x[i-2] + // + -a[1] * y[i-1] + -a[2] * y[i-2]; + + tmp_int32 = WEBRTC_SPL_MUL_16_16(y[1], ba[3]); // -a[1] * y[i-1] (low part) + tmp_int32 += WEBRTC_SPL_MUL_16_16(y[3], ba[4]); // -a[2] * y[i-2] (low part) + tmp_int32 = (tmp_int32 >> 15); + tmp_int32 += WEBRTC_SPL_MUL_16_16(y[0], ba[3]); // -a[1] * y[i-1] (high part) + tmp_int32 += WEBRTC_SPL_MUL_16_16(y[2], ba[4]); // -a[2] * y[i-2] (high part) + tmp_int32 = (tmp_int32 << 1); + + tmp_int32 += WEBRTC_SPL_MUL_16_16(data[i], ba[0]); // b[0]*x[0] + tmp_int32 += WEBRTC_SPL_MUL_16_16(x[0], ba[1]); // b[1]*x[i-1] + tmp_int32 += WEBRTC_SPL_MUL_16_16(x[1], ba[2]); // b[2]*x[i-2] + + // Update state (input part) + x[1] = x[0]; + x[0] = data[i]; + + // Update state (filtered part) + y[2] = y[0]; + y[3] = y[1]; + y[0] = (WebRtc_Word16)(tmp_int32 >> 13); + y[1] = (WebRtc_Word16)((tmp_int32 - WEBRTC_SPL_LSHIFT_W32((WebRtc_Word32)(y[0]), 13)) << 2); + + // Rounding in Q12, i.e. add 2^11 + tmp_int32 += 2048; + + // Saturate (to 2^27) so that the HP filtered signal does not overflow + tmp_int32 = WEBRTC_SPL_SAT((WebRtc_Word32)(134217727), tmp_int32, (WebRtc_Word32)(-134217728)); + + // Convert back to Q0 and use rounding + data[i] = (WebRtc_Word16)WEBRTC_SPL_RSHIFT_W32(tmp_int32, 12); + + } + + return 0; +} + + +typedef struct webrtc_ec +{ + void *AEC_inst; + void *AGC_inst; + NsHandle *NS_inst; + + pj_bool_t needs_reset; + unsigned skip_frames; + unsigned silence_frames; + + unsigned clock_rate; + unsigned echo_tail; + unsigned samples_per_frame; + unsigned samples_per_10ms_frame; + + WebRtc_Word32 mic_capture_level; + WebRtc_Word16 has_echo; + WebRtc_UWord8 is_saturated; + + HighPassFilterState hpf; + AudioBuffer capture_audio_buffer; + AudioBuffer playback_audio_buffer; + + pj_int16_t *tmp_frame; + pj_int16_t *empty_frame; +} webrtc_ec; + + +#define WEBRTC_AEC_ERROR(aec_inst, tag) \ + do { \ + unsigned status = WebRtcAec_get_error_code(aec_inst); \ + PJ_LOG(4, (THIS_FILE, "WebRTC AEC ERROR (%s) %d", tag, status)); \ + } while (0) \ + + +#define WEBRTC_AGC_ERROR(ns_inst, text) \ + do { \ + PJ_LOG(4, (THIS_FILE, "WebRTC AGC ERROR (%s)", text)); \ + } while (0) \ + + +#define WEBRTC_NS_ERROR(ns_inst, text) \ + do { \ + PJ_LOG(4, (THIS_FILE, "WebRTC NS ERROR (%s)", text)); \ + } while (0) \ + + +PJ_DEF(pj_status_t) webrtc_aec_create(pj_pool_t *pool, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned tail_ms, + unsigned options, + void **p_echo ) +{ + webrtc_ec *echo; + int status; + + *p_echo = NULL; + + if (clock_rate != 16000 && clock_rate != 32000) { + PJ_LOG(4, (THIS_FILE, "Unsupported sample rate: %d", clock_rate)); + return PJ_EINVAL; + } + + echo = PJ_POOL_ZALLOC_T(pool, webrtc_ec); + PJ_ASSERT_RETURN(echo != NULL, PJ_ENOMEM); + + status = WebRtcAec_Create(&echo->AEC_inst); + if(status != 0) { + PJ_LOG(4, (THIS_FILE, "Couldn't allocate memory for WebRTC AEC")); + goto error; + } + + status = WebRtcAec_Init(echo->AEC_inst, clock_rate, clock_rate); + if(status != 0) { + WEBRTC_AEC_ERROR(echo->AEC_inst, "initialization"); + goto error; + } + + AecConfig aec_config; + aec_config.nlpMode = PJMEDIA_WEBRTC_AEC_AGGRESSIVENESS; + aec_config.skewMode = kAecFalse; + aec_config.metricsMode = kAecFalse; + + status = WebRtcAec_set_config(echo->AEC_inst, aec_config); + if(status != 0) { + WEBRTC_AEC_ERROR(echo->AEC_inst, "config initialization"); + goto error; + } + + status = WebRtcAgc_Create(&echo->AGC_inst); + if(status != 0) { + PJ_LOG(4, (THIS_FILE, "Couldn't allocate memory for WebRTC AGC")); + goto error; + } + + status = WebRtcAgc_Init(echo->AGC_inst, 0, 255, kAgcModeAdaptiveAnalog, clock_rate); + if(status != 0) { + WEBRTC_AGC_ERROR(echo->AGC_inst, "initialization"); + goto error; + } + + WebRtcAgc_config_t agc_config; + agc_config.targetLevelDbfs = 7; + agc_config.compressionGaindB = 0; + agc_config.limiterEnable = kAgcFalse; + + status = WebRtcAgc_set_config(echo->AGC_inst, agc_config); + if(status != 0) { + WEBRTC_AGC_ERROR(echo->AGC_inst, "config initialization"); + goto error; + } + + status = WebRtcNs_Create(&echo->NS_inst); + if(status != 0) { + PJ_LOG(4, (THIS_FILE, "Couldn't allocate memory for WebRTC NS")); + goto error; + } + + status = WebRtcNs_Init(echo->NS_inst, clock_rate); + if(status != 0) { + WEBRTC_NS_ERROR(echo->NS_inst, "initialization"); + goto error; + } + + status = WebRtcNs_set_policy(echo->NS_inst, PJMEDIA_WEBRTC_NS_POLICY); + if (status != 0) { + WEBRTC_NS_ERROR(echo->NS_inst, "failed to set policy"); + } + + echo->clock_rate = clock_rate; + echo->samples_per_frame = samples_per_frame; + echo->samples_per_10ms_frame = clock_rate / 100; /* the WebRTC engine works with 10ms frames */ + echo->echo_tail = tail_ms; + echo->needs_reset = PJ_TRUE; + echo->skip_frames = 0; + echo->silence_frames = 0; + echo->mic_capture_level = 255; /* initial mic capture level, maximum */ + + /* Allocate temporary frames for echo cancellation */ + echo->tmp_frame = (pj_int16_t*) pj_pool_zalloc(pool, sizeof(pj_int16_t)*samples_per_frame); + PJ_ASSERT_RETURN(echo->tmp_frame, PJ_ENOMEM); + + echo->empty_frame = (pj_int16_t*) pj_pool_zalloc(pool, sizeof(pj_int16_t)*samples_per_frame); + PJ_ASSERT_RETURN(echo->empty_frame, PJ_ENOMEM); + + /* Initialize audio buffers */ + AudioBuffer_Initialize(&echo->capture_audio_buffer, clock_rate); + AudioBuffer_Initialize(&echo->playback_audio_buffer, clock_rate); + + /* Initialize high pass filter */ + HighPassFilter_Initialize(&echo->hpf, clock_rate); + + PJ_LOG(4, (THIS_FILE, "WebRTC AEC and NS initialized")); + *p_echo = echo; + return PJ_SUCCESS; + +error: + if (echo->AEC_inst) + WebRtcAec_Free(echo->AEC_inst); + if (echo->AGC_inst) + WebRtcAgc_Free(echo->AGC_inst); + if (echo->NS_inst) + WebRtcNs_Free(echo->NS_inst); + return PJ_EBUG; +} + + +PJ_DEF(pj_status_t) webrtc_aec_destroy(void *state ) +{ + webrtc_ec *echo = (webrtc_ec*) state; + PJ_ASSERT_RETURN(echo, PJ_EINVAL); + + if (echo->AEC_inst) { + WebRtcAec_Free(echo->AEC_inst); + echo->AEC_inst = NULL; + } + if (echo->AGC_inst) { + WebRtcAgc_Free(echo->AGC_inst); + echo->AGC_inst = NULL; + } + if (echo->NS_inst) { + WebRtcNs_Free(echo->NS_inst); + echo->NS_inst = NULL; + } + + return PJ_SUCCESS; +} + + +PJ_DEF(void) webrtc_aec_reset(void *state) +{ + /* Synchronously reset later, before processing the next frame, to avoid race conditions */ + ((webrtc_ec*)state)->needs_reset = PJ_TRUE; +} + + +static void aec_reset(webrtc_ec *echo) +{ + PJ_ASSERT_ON_FAIL(echo && echo->AEC_inst && echo->AGC_inst && echo->NS_inst, {return;}); + + int status = 0; + + /* re-initialize the AEC */ + status = WebRtcAec_Init(echo->AEC_inst, echo->clock_rate, echo->clock_rate); + if(status != 0) { + WEBRTC_AEC_ERROR(echo->AEC_inst, "re-initialization"); + return; + } + + AecConfig aec_config; + aec_config.nlpMode = PJMEDIA_WEBRTC_AEC_AGGRESSIVENESS; + aec_config.skewMode = kAecFalse; + aec_config.metricsMode = kAecFalse; + + status = WebRtcAec_set_config(echo->AEC_inst, aec_config); + if(status != 0) { + WEBRTC_AEC_ERROR(echo->AEC_inst, "configuration re-initialization"); + return; + } + + /* re-initialize the AGC */ + status = WebRtcAgc_Init(echo->AGC_inst, 0, 255, kAgcModeAdaptiveAnalog, echo->clock_rate); + if(status != 0) { + WEBRTC_AGC_ERROR(echo->AGC_inst, "initialization"); + return; + } + + WebRtcAgc_config_t agc_config; + agc_config.targetLevelDbfs = 7; + agc_config.compressionGaindB = 0; + agc_config.limiterEnable = kAgcFalse; + + status = WebRtcAgc_set_config(echo->AGC_inst, agc_config); + if(status != 0) { + WEBRTC_AGC_ERROR(echo->AGC_inst, "config initialization"); + return; + } + + /* re-initialize the NS */ + status = WebRtcNs_Init(echo->NS_inst, echo->clock_rate); + if(status != 0) { + WEBRTC_NS_ERROR(echo->NS_inst, "re-initialization"); + return; + } + + status = WebRtcNs_set_policy(echo->NS_inst, PJMEDIA_WEBRTC_NS_POLICY); + if (status != 0) { + WEBRTC_NS_ERROR(echo->NS_inst, "configuration re-initialization"); + return; + } + + /* re-initialize audio buffers */ + AudioBuffer_Initialize(&echo->capture_audio_buffer, echo->clock_rate); + AudioBuffer_Initialize(&echo->playback_audio_buffer, echo->clock_rate); + + /* re-initialize high pass filter state */ + HighPassFilter_Initialize(&echo->hpf, echo->clock_rate); + + /* re-initialize mic level */ + echo->mic_capture_level = 255; + + PJ_LOG(4, (THIS_FILE, "WebRTC AEC reset succeeded")); +} + + +/* + * Perform echo cancellation. + */ +PJ_DEF(pj_status_t) webrtc_aec_cancel_echo(void *state, + pj_int16_t *rec_frm, + const pj_int16_t *play_frm, + unsigned options, + void *reserved) +{ + webrtc_ec *echo = (webrtc_ec*) state; + pj_int16_t *capture_frame, *result_frame; + int i, status; + + /* Sanity checks */ + PJ_ASSERT_RETURN(echo && echo->AEC_inst && echo->AGC_inst && echo->NS_inst, PJ_EINVAL); + PJ_ASSERT_RETURN(rec_frm && play_frm && options==0 && reserved==NULL, PJ_EINVAL); + + /* Check if a reset is needed */ + if (echo->needs_reset) { + aec_reset(echo); + echo->needs_reset = PJ_FALSE; + echo->skip_frames = 15; + echo->silence_frames = 10; + } + + if (echo->skip_frames) { + echo->skip_frames--; + capture_frame = echo->empty_frame; + result_frame = echo->empty_frame; + } else if (echo->silence_frames) { + echo->silence_frames--; + capture_frame = rec_frm; + result_frame = echo->empty_frame; + } else { + capture_frame = rec_frm; + result_frame = echo->tmp_frame; + } + + /* Copy record frame to a temporary buffer, in case things go wrong audio will be returned unchanged */ + pjmedia_copy_samples(echo->tmp_frame, capture_frame, echo->samples_per_frame); + + for(i=0; i < echo->samples_per_frame; i+= echo->samples_per_10ms_frame) { + /* feed a 10ms frame into the audio buffers */ + AudioBuffer_SetData(&echo->capture_audio_buffer, (WebRtc_Word16 *) (&echo->tmp_frame[i])); + AudioBuffer_SetData(&echo->playback_audio_buffer, (WebRtc_Word16 *) (&play_frm[i])); + + /* Apply high pass filer */ + HighPassFilter_Process(&echo->hpf, + AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), + AudioBuffer_SamplesPerChannel(&echo->capture_audio_buffer)); + + /* Analyze capture data gain + * NOTE: if we used kAgcModeAdaptiveDigital we'd use WebRtcAgc_VirtualMic instead + */ + status = WebRtcAgc_AddMic(echo->AGC_inst, + AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), + AudioBuffer_GetHighPassData(&echo->capture_audio_buffer), + AudioBuffer_SamplesPerChannel(&echo->capture_audio_buffer)); + if(status != 0) { + WEBRTC_AGC_ERROR(echo->AGC_inst, "gain analysis"); + return PJ_EBUG; + } + + /* Feed farend buffer to AGC */ + status = WebRtcAgc_AddFarend(echo->AGC_inst, + AudioBuffer_GetLowPassData(&echo->playback_audio_buffer), + AudioBuffer_SamplesPerChannel(&echo->playback_audio_buffer)); + if(status != 0) { + WEBRTC_AGC_ERROR(echo->AGC_inst, "farend buffering"); + return PJ_EBUG; + } + + /* Feed farend buffer to AEC */ + status = WebRtcAec_BufferFarend(echo->AEC_inst, + AudioBuffer_GetLowPassData(&echo->playback_audio_buffer), + AudioBuffer_SamplesPerChannel(&echo->playback_audio_buffer)); + if(status != 0) { + WEBRTC_AEC_ERROR(echo->AEC_inst, "farend buffering"); + return PJ_EBUG; + } + + /* Noise suppression */ + status = WebRtcNs_Process(echo->NS_inst, + AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), + AudioBuffer_GetHighPassData(&echo->capture_audio_buffer), + AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), + AudioBuffer_GetHighPassData(&echo->capture_audio_buffer)); + if (status != 0) { + WEBRTC_NS_ERROR(echo->NS_inst, "ns processing"); + return PJ_EBUG; + } + + /* Process echo cancellation */ + status = WebRtcAec_Process(echo->AEC_inst, + AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), + AudioBuffer_GetHighPassData(&echo->capture_audio_buffer), + AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), + AudioBuffer_GetHighPassData(&echo->capture_audio_buffer), + AudioBuffer_SamplesPerChannel(&echo->capture_audio_buffer), + echo->echo_tail, + 0); + if(status != 0) { + WEBRTC_AEC_ERROR(echo->AEC_inst, "echo processing"); + return PJ_EBUG; + } + + WebRtcAec_get_echo_status(echo->AEC_inst, &echo->has_echo); +#if 0 + if (echo->has_echo) { + PJ_LOG(4, (THIS_FILE, "Sound might have echo")); + } +#endif + + /* Process gain control */ + status = WebRtcAgc_Process(echo->AGC_inst, + AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), + AudioBuffer_GetHighPassData(&echo->capture_audio_buffer), + AudioBuffer_SamplesPerChannel(&echo->capture_audio_buffer), + AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), + AudioBuffer_GetHighPassData(&echo->capture_audio_buffer), + echo->mic_capture_level, + &echo->mic_capture_level, + echo->has_echo, + &echo->is_saturated); + if (status != 0) { + WEBRTC_AGC_ERROR(echo->AGC_inst, "agc processing"); + return PJ_EBUG; + } +#if 0 + if (echo->is_saturated) { + PJ_LOG(4, (THIS_FILE, "Sound might be saturated")); + } +#endif + + /* finish frame processing, in case we are working at 32kHz low and high bands will be combined */ + AudioBuffer_GetData(&echo->capture_audio_buffer); + } + + /* Copy temporary buffer back to original rec_frm */ + pjmedia_copy_samples(rec_frm, result_frame, echo->samples_per_frame); + + return PJ_SUCCESS; + +} + + +#endif diff -ruN pjproject-2.10/pjmedia/src/pjmedia/endpoint.c pjsip/pjmedia/src/pjmedia/endpoint.c --- pjproject-2.10/pjmedia/src/pjmedia/endpoint.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/endpoint.c 2021-02-06 20:23:26.353848918 +0100 @@ -688,6 +688,7 @@ /* Put bandwidth info in media level using bandwidth modifier "TIAS" * (RFC3890). */ +#if 0 if (max_bitrate && pjmedia_add_bandwidth_tias_in_sdp) { const pj_str_t STR_BANDW_MODIFIER = { "TIAS", 4 }; pjmedia_sdp_bandw *b; @@ -697,6 +698,7 @@ b->value = max_bitrate; m->bandw[m->bandw_count++] = b; } +#endif *p_m = m; return PJ_SUCCESS; --- pjproject-2.10/pjmedia/src/pjmedia/ffmpeg_util.h 2021-03-19 16:51:27.589562664 +0100 +++ pjsip/pjmedia/src/pjmedia/ffmpeg_util.h 2021-03-19 17:00:32.821251088 +0100 @@ -38,13 +38,13 @@ #include -#ifdef PJMEDIA_USE_OLD_FFMPEG -# define AVPixelFormat PixelFormat -# define AV(str) str -# define PIX_FMT_GBRP PIX_FMT_GBR24P -#else +//#ifdef PJMEDIA_USE_OLD_FFMPEG +//# define AVPixelFormat PixelFormat +//# define AV(str) str +//# define PIX_FMT_GBRP PIX_FMT_GBR24P +//#else # define AV(str) AV_ ## str -#endif +//#endif void pjmedia_ffmpeg_add_ref(); diff -ruN pjproject-2.10/pjmedia/src/pjmedia/ffmpeg_util.c pjsip/pjmedia/src/pjmedia/ffmpeg_util.c --- pjproject-2.10/pjmedia/src/pjmedia/ffmpeg_util.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/ffmpeg_util.c 2021-02-06 20:25:00.472499296 +0100 @@ -34,10 +34,11 @@ enum AVPixelFormat pf; } ffmpeg_fmt_table[] = { + { PJMEDIA_FORMAT_ARGB, AV(PIX_FMT_ARGB)}, { PJMEDIA_FORMAT_RGBA, AV(PIX_FMT_RGBA)}, { PJMEDIA_FORMAT_RGB24,AV(PIX_FMT_BGR24)}, { PJMEDIA_FORMAT_BGRA, AV(PIX_FMT_BGRA)}, - { PJMEDIA_FORMAT_GBRP, AV(PIX_FMT_GBRP)}, + { PJMEDIA_FORMAT_GBRP, AV(PIX_FMT_GBR24P)}, { PJMEDIA_FORMAT_AYUV, AV(PIX_FMT_NONE)}, { PJMEDIA_FORMAT_YUY2, AV(PIX_FMT_YUYV422)}, diff -ruN pjproject-2.10/pjmedia/src/pjmedia/format.c pjsip/pjmedia/src/pjmedia/format.c --- pjproject-2.10/pjmedia/src/pjmedia/format.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/format.c 2021-02-06 20:25:25.677209428 +0100 @@ -77,6 +77,7 @@ static pjmedia_video_format_info built_in_vid_fmt_info[] = { {PJMEDIA_FORMAT_RGB24, "RGB24", PJMEDIA_COLOR_MODEL_RGB, 24, 1, &apply_packed_fmt}, + {PJMEDIA_FORMAT_ARGB, "ARGB", PJMEDIA_COLOR_MODEL_RGB, 32, 1, &apply_packed_fmt}, {PJMEDIA_FORMAT_RGBA, "RGBA", PJMEDIA_COLOR_MODEL_RGB, 32, 1, &apply_packed_fmt}, {PJMEDIA_FORMAT_BGRA, "BGRA", PJMEDIA_COLOR_MODEL_RGB, 32, 1, &apply_packed_fmt}, {PJMEDIA_FORMAT_DIB , "DIB ", PJMEDIA_COLOR_MODEL_RGB, 24, 1, &apply_packed_fmt}, diff -ruN pjproject-2.10/pjmedia/src/pjmedia/mixer_port.c pjsip/pjmedia/src/pjmedia/mixer_port.c --- pjproject-2.10/pjmedia/src/pjmedia/mixer_port.c 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/mixer_port.c 2021-02-06 18:45:36.859549796 +0100 @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2010 AG Projects + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#include +#include +#include +#include +#include + + +#define SIGNATURE PJMEDIA_SIG_PORT_MIXER +#define MIN(a, b) ((a)>(b)?(b):(a)) + +struct mixer_port +{ + pjmedia_port base; + pjmedia_frame_type last_frame_type; + pj_size_t last_frame_size; + pj_timestamp last_frame_timestamp; + pj_int16_t* buffer; + pj_size_t buffer_size; +}; + +static pj_status_t mixer_get_frame(pjmedia_port *port, pjmedia_frame *frame); +static pj_status_t mixer_put_frame(pjmedia_port *port, pjmedia_frame *frame); +static pj_status_t mixer_on_destroy(pjmedia_port *port); + + +PJ_DEF(pj_status_t) pjmedia_mixer_port_create(pj_pool_t *pool, + unsigned sampling_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + pjmedia_port **p_port) +{ + struct mixer_port *port; + const pj_str_t name = pj_str("mixer-port"); + + PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL); + + port = PJ_POOL_ZALLOC_T(pool, struct mixer_port); + PJ_ASSERT_RETURN(port != NULL, PJ_ENOMEM); + + pjmedia_port_info_init(&port->base.info, &name, SIGNATURE, sampling_rate, + channel_count, bits_per_sample, samples_per_frame); + + port->base.get_frame = &mixer_get_frame; + port->base.put_frame = &mixer_put_frame; + port->base.on_destroy = &mixer_on_destroy; + port->last_frame_type = PJMEDIA_FRAME_TYPE_NONE; + port->last_frame_size = 0; + port->last_frame_timestamp.u64 = 0; + port->buffer = (pj_int16_t*) pj_pool_calloc(pool, samples_per_frame, sizeof(pj_int16_t)); + port->buffer_size = sizeof(pj_int16_t) * samples_per_frame; + + *p_port = &port->base; + + return PJ_SUCCESS; +} + + + +/* + * Put frame to file. + */ +static pj_status_t mixer_put_frame(pjmedia_port *this_port, pjmedia_frame *frame) +{ + struct mixer_port* port = (struct mixer_port*) this_port; + + if (!frame->size || frame->type != PJMEDIA_FRAME_TYPE_AUDIO) { + port->last_frame_type = PJMEDIA_FRAME_TYPE_NONE; + port->last_frame_size = 0; + port->last_frame_timestamp.u64 = 0; + return PJ_SUCCESS; + } + + PJ_ASSERT_RETURN(frame->size <= port->buffer_size, PJ_EINVAL); + + port->last_frame_type = frame->type; + pj_get_timestamp(&port->last_frame_timestamp); + port->last_frame_size = MIN(port->buffer_size, frame->size); + memcpy(port->buffer, frame->buf, port->last_frame_size); + + return PJ_SUCCESS; +} + + +/* + * Get frame from file. + */ +static pj_status_t mixer_get_frame(pjmedia_port *this_port, pjmedia_frame *frame) +{ + struct mixer_port* port = (struct mixer_port*) this_port; + pj_timestamp now; + pj_uint32_t frame_age; + + pj_get_timestamp(&now); + frame_age = pj_elapsed_usec(&port->last_frame_timestamp, &now); + + if (port->last_frame_timestamp.u64 != 0 && frame_age <= 100000) { + frame->type = port->last_frame_type; + frame->size = port->last_frame_size; + frame->timestamp.u64 = 0; + if (port->last_frame_size > 0) { + memcpy(frame->buf, port->buffer, port->last_frame_size); + } + } else { + frame->type = PJMEDIA_FRAME_TYPE_NONE; + frame->size = 0; + frame->timestamp.u64 = 0; + } + + return PJ_SUCCESS; +} + + +/* + * Destroy port. + */ +static pj_status_t mixer_on_destroy(pjmedia_port *this_port) +{ + return PJ_SUCCESS; +} + diff -ruN pjproject-2.10/pjmedia/src/pjmedia/rtcp.c pjsip/pjmedia/src/pjmedia/rtcp.c --- pjproject-2.10/pjmedia/src/pjmedia/rtcp.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/rtcp.c 2021-02-06 20:30:29.245771690 +0100 @@ -1003,6 +1003,33 @@ sess->stat.rx.update_cnt++; } +PJ_DEF(pj_status_t) pjmedia_rtcp_build_rtcp_pli(pjmedia_rtcp_session *session, + void *buf, + pj_size_t *length) +{ + pjmedia_rtcp_common *hdr; + pj_uint8_t *p; + pj_size_t len = 12; /* pjmedia_rtcp_common + media SSRC (uint32_t) */ + + PJ_ASSERT_RETURN(session && buf && length, PJ_EINVAL); + + /* Verify buffer length */ + if (len > *length) + return PJ_ETOOSMALL; + + /* Build RTCP PLI */ + hdr = (pjmedia_rtcp_common*)buf; + pj_memcpy(hdr, &session->rtcp_sr_pkt.common, sizeof(*hdr)); + hdr->pt = RTCP_PSFB; + hdr->count = 1; /* FMT: 1 == Picture Loss Indication (PLI) */ + hdr->length = pj_htons((pj_uint16_t)(len/4 - 1)); + + p = (pj_uint8_t*)hdr + sizeof(*hdr); + pj_memset(p, 0, (pj_uint8_t*)hdr + len - p); + *length = len; + return PJ_SUCCESS; +} + PJ_DEF(pj_status_t) pjmedia_rtcp_build_rtcp_sdes( pjmedia_rtcp_session *session, diff -ruN pjproject-2.10/pjmedia/src/pjmedia/sound_port.c pjsip/pjmedia/src/pjmedia/sound_port.c --- pjproject-2.10/pjmedia/src/pjmedia/sound_port.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/sound_port.c 2021-02-06 21:42:52.101118944 +0100 @@ -98,7 +98,7 @@ if (snd_port->ec_state) { if (snd_port->ec_suspended) { snd_port->ec_suspended = PJ_FALSE; - //pjmedia_echo_state_reset(snd_port->ec_state); + pjmedia_echo_reset(snd_port->ec_state); PJ_LOG(4,(THIS_FILE, "EC activated")); } snd_port->ec_suspend_count = 0; @@ -312,14 +312,9 @@ snd_port->aud_param.ec_tail_ms)); } - status = pjmedia_snd_port_set_ec(snd_port, pool, - snd_port->aud_param.ec_tail_ms, - snd_port->prm_ec_options); - if (status != PJ_SUCCESS) { - pjmedia_aud_stream_destroy(snd_port->aud_stream); - snd_port->aud_stream = NULL; - return status; - } + pjmedia_snd_port_set_ec(snd_port, pool, + snd_port->aud_param.ec_tail_ms, + snd_port->prm_ec_options); } /* Start sound stream. */ @@ -550,6 +545,17 @@ } +/* Reset EC state */ +PJ_DEF(pj_status_t) pjmedia_snd_port_reset_ec_state( pjmedia_snd_port *snd_port ) +{ + PJ_ASSERT_RETURN(snd_port, PJ_EINVAL); + if (snd_port->ec_state) { + pjmedia_echo_reset(snd_port->ec_state); + PJ_LOG(4,(THIS_FILE, "EC reset")); + } + return PJ_SUCCESS; +} + /* * Change EC settings. */ diff -ruN pjproject-2.10/pjmedia/src/pjmedia/transport_ice.c pjsip/pjmedia/src/pjmedia/transport_ice.c --- pjproject-2.10/pjmedia/src/pjmedia/transport_ice.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/transport_ice.c 2021-02-06 21:40:50.021656143 +0100 @@ -163,6 +163,10 @@ pj_ice_strans_op op, pj_status_t status); +static void ice_on_ice_state(pj_ice_strans *ice_st, + pj_ice_strans_state prev, + pj_ice_strans_state curr); + /* * Clean up ICE resources. */ @@ -280,6 +284,7 @@ /* Configure ICE callbacks */ pj_bzero(&ice_st_cb, sizeof(ice_st_cb)); ice_st_cb.on_ice_complete = &ice_on_ice_complete; + ice_st_cb.on_ice_state = &ice_on_ice_state; ice_st_cb.on_rx_data = &ice_on_rx_data; /* Configure RTP socket buffer settings, if not set */ @@ -314,6 +319,17 @@ return PJ_SUCCESS; } +/* + * Get the ICE stream transport associated with this media transport. + */ +PJ_DEF(pj_ice_strans*) pjmedia_ice_get_strans(pjmedia_transport *tp) +{ + struct transport_ice *tp_ice; + + tp_ice = (struct transport_ice*) tp; + return tp_ice->ice_st; +} + PJ_DEF(pj_grp_lock_t *) pjmedia_ice_get_grp_lock(pjmedia_transport *tp) { PJ_ASSERT_RETURN(tp, NULL); @@ -403,6 +419,10 @@ "Stopping ICE, reason=%s", reason)); } + /* Notify application about ICE stop */ + if (tp_ice->cb.on_ice_stop) + (*tp_ice->cb.on_ice_stop)(&tp_ice->base, (char *)reason, err); + if (tp_ice->ice_st) { pj_ice_strans_stop_ice(tp_ice->ice_st); } @@ -1745,17 +1765,17 @@ if (status != PJ_SUCCESS) return status; - pj_sockaddr_cp(&info->sock_info.rtp_addr_name, &cand.addr); + pj_sockaddr_cp(&info->sock_info.rtp_addr_name, &cand.base_addr); /* Get RTCP default address */ if (tp_ice->use_rtcp_mux) { - pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, &cand.addr); + pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, &cand.base_addr); } else if (tp_ice->comp_cnt > 1) { status = pj_ice_strans_get_def_cand(tp_ice->ice_st, 2, &cand); if (status != PJ_SUCCESS) return status; - pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, &cand.addr); + pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, &cand.base_addr); } /* Set remote address originating RTP & RTCP if this transport has @@ -2115,6 +2135,19 @@ } } +static void ice_on_ice_state(pj_ice_strans *ice_st, + pj_ice_strans_state prev, + pj_ice_strans_state curr) +{ + struct transport_ice *tp_ice; + + tp_ice = (struct transport_ice*) pj_ice_strans_get_user_data(ice_st); + + /* Notify application */ + if (tp_ice->cb.on_ice_state) + (*tp_ice->cb.on_ice_state)(&tp_ice->base, prev, curr); +} + /* Simulate lost */ static pj_status_t transport_simulate_lost(pjmedia_transport *tp, diff -ruN pjproject-2.10/pjmedia/src/pjmedia/transport_zrtp.c pjsip/pjmedia/src/pjmedia/transport_zrtp.c --- pjproject-2.10/pjmedia/src/pjmedia/transport_zrtp.c 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/transport_zrtp.c 2021-02-06 16:42:04.322625052 +0100 @@ -0,0 +1,1261 @@ +/* $Id$ */ +/* + * Copyright (C) 2010 Werner Dittmann + * This is the pjmedia ZRTP transport module. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include + +#include "../../third_party/zsrtp/include/ZsrtpCWrapper.h" + +#define THIS_FILE "transport_zrtp.c" + +#define MAX_RTP_BUFFER_LEN PJMEDIA_MAX_MTU +#define MAX_RTCP_BUFFER_LEN PJMEDIA_MAX_MTU + + +/* Transport functions prototypes */ +static pj_status_t transport_get_info(pjmedia_transport *tp, + pjmedia_transport_info *info); +static pj_status_t transport_attach(pjmedia_transport *tp, + void *user_data, + const pj_sockaddr_t *rem_addr, + const pj_sockaddr_t *rem_rtcp, + unsigned addr_len, + void (*rtp_cb)(void*, + void*, + pj_ssize_t), + void (*rtcp_cb)(void*, + void*, + pj_ssize_t)); +static void transport_detach(pjmedia_transport *tp, + void *strm); +static pj_status_t transport_send_rtp(pjmedia_transport *tp, + const void *pkt, + pj_size_t size); +static pj_status_t transport_send_rtcp(pjmedia_transport *tp, + const void *pkt, + pj_size_t size); +static pj_status_t transport_send_rtcp2(pjmedia_transport *tp, + const pj_sockaddr_t *addr, + unsigned addr_len, + const void *pkt, + pj_size_t size); +static pj_status_t transport_media_create(pjmedia_transport *tp, + pj_pool_t *sdp_pool, + unsigned options, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index); +static pj_status_t transport_encode_sdp(pjmedia_transport *tp, + pj_pool_t *sdp_pool, + pjmedia_sdp_session *local_sdp, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index); +static pj_status_t transport_media_start(pjmedia_transport *tp, + pj_pool_t *pool, + const pjmedia_sdp_session *local_sdp, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index); +static pj_status_t transport_media_stop(pjmedia_transport *tp); +static pj_status_t transport_simulate_lost(pjmedia_transport *tp, + pjmedia_dir dir, + unsigned pct_lost); +static pj_status_t transport_destroy(pjmedia_transport *tp); + + +/* The transport operations */ +static struct pjmedia_transport_op tp_zrtp_op = +{ + &transport_get_info, + &transport_attach, + &transport_detach, + &transport_send_rtp, + &transport_send_rtcp, + &transport_send_rtcp2, + &transport_media_create, + &transport_encode_sdp, + &transport_media_start, + &transport_media_stop, + &transport_simulate_lost, + &transport_destroy +}; + +/* The transport zrtp instance */ +struct tp_zrtp +{ + pjmedia_transport base; + pj_pool_t *pool; + + /* Stream information. */ + void *stream_user_data; + void (*stream_rtp_cb)(void *user_data, + void *pkt, + pj_ssize_t); + void (*stream_rtcp_cb)(void *user_data, + void *pkt, + pj_ssize_t); + + /* Add your own member here.. */ + uint64_t protect; + uint64_t unprotect; + int32_t unprotect_err; + int32_t refcount; + pj_timer_heap_t* timer_heap; + pj_timer_entry timeoutEntry; + pj_mutex_t* zrtpMutex; + ZsrtpContext* srtpReceive; + ZsrtpContext* srtpSend; + ZsrtpContextCtrl* srtcpReceive; + ZsrtpContextCtrl* srtcpSend; + void* sendBuffer; + void* sendBufferCtrl; + pj_uint8_t* zrtpBuffer; +// pj_int32_t sendBufferLen; + pj_uint32_t peerSSRC; /* stored in host order */ + pj_uint32_t localSSRC; /* stored in host order */ + char* clientIdString; + pjmedia_transport *slave_tp; + pjmedia_zrtp_cb cb; + ZrtpContext* zrtpCtx; + pj_uint16_t zrtpSeq; + pj_bool_t enableZrtp; + pj_bool_t started; + pj_bool_t close_slave; + pj_bool_t mitmMode; + char cipher[128]; +}; + +/* Forward declaration of thethe ZRTP specific callback functions that this + adapter must implement */ +static int32_t zrtp_sendDataZRTP(ZrtpContext* ctx, const uint8_t* data, int32_t length) ; +static int32_t zrtp_activateTimer(ZrtpContext* ctx, int32_t time) ; +static int32_t zrtp_cancelTimer(ZrtpContext* ctx) ; +static void zrtp_sendInfo(ZrtpContext* ctx, int32_t severity, int32_t subCode) ; +static int32_t zrtp_srtpSecretsReady(ZrtpContext* ctx, C_SrtpSecret_t* secrets, int32_t part) ; +static void zrtp_srtpSecretsOff(ZrtpContext* ctx, int32_t part) ; +static void zrtp_srtpSecretsOn(ZrtpContext* ctx, char* c, char* s, int32_t verified) ; +static void zrtp_handleGoClear(ZrtpContext* ctx) ; +static void zrtp_zrtpNegotiationFailed(ZrtpContext* ctx, int32_t severity, int32_t subCode) ; +static void zrtp_zrtpNotSuppOther(ZrtpContext* ctx) ; +static void zrtp_synchEnter(ZrtpContext* ctx) ; +static void zrtp_synchLeave(ZrtpContext* ctx) ; +static void zrtp_zrtpAskEnrollment(ZrtpContext* ctx, int32_t info) ; +static void zrtp_zrtpInformEnrollment(ZrtpContext* ctx, int32_t info) ; +static void zrtp_signSAS(ZrtpContext* ctx, uint8_t* sasHash) ; +static int32_t zrtp_checkSASSignature(ZrtpContext* ctx, uint8_t* sasHash) ; + +/* The callback function structure for ZRTP */ +static zrtp_Callbacks c_callbacks = +{ + &zrtp_sendDataZRTP, + &zrtp_activateTimer, + &zrtp_cancelTimer, + &zrtp_sendInfo, + &zrtp_srtpSecretsReady, + &zrtp_srtpSecretsOff, + &zrtp_srtpSecretsOn, + &zrtp_handleGoClear, + &zrtp_zrtpNegotiationFailed, + &zrtp_zrtpNotSuppOther, + &zrtp_synchEnter, + &zrtp_synchLeave, + &zrtp_zrtpAskEnrollment, + &zrtp_zrtpInformEnrollment, + &zrtp_signSAS, + &zrtp_checkSASSignature +}; + +static void timer_callback(pj_timer_heap_t *ht, pj_timer_entry *e); + +static char clientId[] = "SIP SIMPLE Client SDK"; + +/* + * Create the ZRTP transport. + */ +PJ_DEF(pj_status_t) pjmedia_transport_zrtp_create(pjmedia_endpt *endpt, + pj_timer_heap_t *timer_heap, + pjmedia_transport *tp, + pjmedia_transport **p_tp, + pj_bool_t close_slave) +{ + pj_pool_t *pool; + struct tp_zrtp *zrtp; + + PJ_ASSERT_RETURN(endpt && tp && p_tp, PJ_EINVAL); + + /* Create the pool and initialize the adapter structure */ + pool = pjmedia_endpt_create_pool(endpt, "zrtp%p", 5*1024, 512); + zrtp = PJ_POOL_ZALLOC_T(pool, struct tp_zrtp); + zrtp->pool = pool; + + /* Initialize base pjmedia_transport */ + pj_memcpy(zrtp->base.name, pool->obj_name, PJ_MAX_OBJ_NAME); + zrtp->base.type = tp->type; + zrtp->base.op = &tp_zrtp_op; + + /* Set the timer heap to be used for timers */ + zrtp->timer_heap = timer_heap; + + /* Create the empty wrapper */ + zrtp->zrtpCtx = zrtp_CreateWrapper(); + + /* Initialize standard values */ + zrtp->clientIdString = clientId; /* Set standard name */ + zrtp->zrtpSeq = 1; /* TODO: randomize */ + pj_mutex_create_simple(zrtp->pool, "zrtp", &zrtp->zrtpMutex); + zrtp->zrtpBuffer = pj_pool_zalloc(pool, MAX_ZRTP_SIZE); + zrtp->sendBuffer = pj_pool_zalloc(pool, MAX_RTP_BUFFER_LEN); + zrtp->sendBufferCtrl = pj_pool_zalloc(pool, MAX_RTCP_BUFFER_LEN); + + zrtp->slave_tp = tp; + zrtp->close_slave = close_slave; + zrtp->mitmMode = PJ_FALSE; + + /* Done */ + zrtp->refcount++; + *p_tp = &zrtp->base; + return PJ_SUCCESS; +} + +PJ_DECL(pj_status_t) pjmedia_transport_zrtp_initialize(pjmedia_transport *tp, + const char *zidFilename, + pj_bool_t autoEnable, + pjmedia_zrtp_cb *cb) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + PJ_ASSERT_RETURN(tp, PJ_EINVAL); + + zrtp_initializeZrtpEngine(zrtp->zrtpCtx, &c_callbacks, zrtp->clientIdString, + zidFilename, zrtp, zrtp->mitmMode); + zrtp->enableZrtp = autoEnable; + if (cb) + pj_memcpy(&zrtp->cb, cb, sizeof(pjmedia_zrtp_cb)); + return PJ_SUCCESS; +} + +static void timer_callback(pj_timer_heap_t *ht, pj_timer_entry *e) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)e->user_data; + + zrtp_processTimeout(zrtp->zrtpCtx); + PJ_UNUSED_ARG(ht); +} + +/* + * Here start with callback functions that support the ZRTP core + */ +static int32_t zrtp_sendDataZRTP(ZrtpContext* ctx, const uint8_t* data, int32_t length) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + pj_uint16_t totalLen = length + 12; /* Fixed number of bytes of ZRTP header */ + pj_uint32_t crc; + pj_uint8_t* buffer = zrtp->zrtpBuffer; + pj_uint16_t* pus; + pj_uint32_t* pui; + + if ((totalLen) > MAX_ZRTP_SIZE) + return 0; + + /* Get some handy pointers */ + pus = (pj_uint16_t*)buffer; + pui = (pj_uint32_t*)buffer; + + /* set up fixed ZRTP header */ + *buffer = 0x10; /* invalid RTP version - refer to ZRTP spec chap 5 */ + *(buffer + 1) = 0; + pus[1] = pj_htons(zrtp->zrtpSeq++); + pui[1] = pj_htonl(ZRTP_MAGIC); + pui[2] = pj_htonl(zrtp->localSSRC); /* stored in host order */ + + /* Copy ZRTP message data behind the header data */ + pj_memcpy(buffer+12, data, length); + + /* Setup and compute ZRTP CRC */ + crc = zrtp_GenerateCksum(buffer, totalLen-CRC_SIZE); + + /* convert and store CRC in ZRTP packet.*/ + crc = zrtp_EndCksum(crc); + *(uint32_t*)(buffer+totalLen-CRC_SIZE) = pj_htonl(crc); + + /* Send the ZRTP packet using the slave transport */ + return (pjmedia_transport_send_rtp(zrtp->slave_tp, buffer, totalLen) == PJ_SUCCESS) ? 1 : 0; +} + +static int32_t zrtp_activateTimer(ZrtpContext* ctx, int32_t time) +{ + pj_time_val timeout; + pj_status_t status; + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + timeout.sec = time / 1000; + timeout.msec = time % 1000; + + pj_timer_entry_init(&zrtp->timeoutEntry, 0, zrtp, &timer_callback); + status = pj_timer_heap_schedule(zrtp->timer_heap, &zrtp->timeoutEntry, &timeout); + if (status == PJ_SUCCESS) + return 1; + else + return 0; +} + +static int32_t zrtp_cancelTimer(ZrtpContext* ctx) +{ + pj_status_t status; + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + status = pj_timer_heap_cancel(zrtp->timer_heap, &zrtp->timeoutEntry); + if (status == PJ_SUCCESS) + return 1; + else + return 0; +} + +static void zrtp_sendInfo(ZrtpContext* ctx, int32_t severity, int32_t subCode) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + if (zrtp->cb.show_message) + zrtp->cb.show_message(&zrtp->base, severity, subCode); + +} + +static int32_t zrtp_srtpSecretsReady(ZrtpContext* ctx, C_SrtpSecret_t* secrets, int32_t part) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + ZsrtpContext* recvCrypto; + ZsrtpContext* senderCrypto; + ZsrtpContextCtrl* recvCryptoCtrl; + ZsrtpContextCtrl* senderCryptoCtrl; + int cipher; + int authn; + int authKeyLen; + // int srtcpAuthTagLen; + + if (secrets->authAlgorithm == zrtp_Sha1) { + authn = SrtpAuthenticationSha1Hmac; + authKeyLen = 20; + // srtcpAuthTagLen = 80; // Always 80 bit for SRTCP / SHA1 + } + + if (secrets->authAlgorithm == zrtp_Skein) { + authn = SrtpAuthenticationSkeinHmac; + authKeyLen = 32; + // srtcpAuthTagLen = 64; // Always 64 bit for SRTCP / Skein + } + + if (secrets->symEncAlgorithm == zrtp_Aes) + cipher = SrtpEncryptionAESCM; + + if (secrets->symEncAlgorithm == zrtp_TwoFish) + cipher = SrtpEncryptionTWOCM; + + if (part == ForSender) { + // To encrypt packets: intiator uses initiator keys, + // responder uses responder keys + // Create a "half baked" crypto context first and store it. This is + // the main crypto context for the sending part of the connection. + if (secrets->role == Initiator) { + senderCrypto = zsrtp_CreateWrapper(zrtp->localSSRC, + 0, + 0L, // keyderivation << 48, + cipher, // encryption algo + authn, // authtentication algo + (unsigned char*)secrets->keyInitiator, // Master Key + secrets->initKeyLen / 8, // Master Key length + (unsigned char*)secrets->saltInitiator, // Master Salt + secrets->initSaltLen / 8, // Master Salt length + secrets->initKeyLen / 8, // encryption keyl + authKeyLen, // authentication key len + secrets->initSaltLen / 8, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag lenA + + senderCryptoCtrl = zsrtp_CreateWrapperCtrl(zrtp->localSSRC, + cipher, // encryption algo + authn, // authtication algo + (unsigned char*)secrets->keyInitiator, // Master Key + secrets->initKeyLen / 8, // Master Key length + (unsigned char*)secrets->saltInitiator, // Master Salt + secrets->initSaltLen / 8, // Master Salt length + secrets->initKeyLen / 8, // encryption keyl + authKeyLen, // authentication key len + secrets->initSaltLen / 8, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag len + // srtcpAuthTagLen / 8); // authentication tag len + } + else { + senderCrypto = zsrtp_CreateWrapper(zrtp->localSSRC, + 0, + 0L, // keyderivation << 48, + cipher, // encryption algo + authn, // authtentication algo + (unsigned char*)secrets->keyResponder, // Master Key + secrets->respKeyLen / 8, // Master Key length + (unsigned char*)secrets->saltResponder, // Master Salt + secrets->respSaltLen / 8, // Master Salt length + secrets->respKeyLen / 8, // encryption keyl + authKeyLen, // authentication key len + secrets->respSaltLen / 8, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag len + + senderCryptoCtrl = zsrtp_CreateWrapperCtrl(zrtp->localSSRC, + cipher, // encryption algo + authn, // authtication algo + (unsigned char*)secrets->keyResponder, // Master Key + secrets->respKeyLen / 8, // Master Key length + (unsigned char*)secrets->saltResponder, // Master Salt + secrets->respSaltLen / 8, // Master Salt length + secrets->respKeyLen / 8, // encryption keyl + authKeyLen, // authentication key len + secrets->respSaltLen / 8, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag len + // srtcpAuthTagLen / 8); // authentication tag len + } + if (senderCrypto == NULL) { + return 0; + } + // Create a SRTP crypto context for real SSRC sender stream. + // Note: key derivation can be done at this time only if the + // key derivation rate is 0 (disabled). For ZRTP this is the + // case: the key derivation is defined as 2^48 + // which is effectively 0. + zsrtp_deriveSrtpKeys(senderCrypto, 0L); + zrtp->srtpSend = senderCrypto; + + zsrtp_deriveSrtpKeysCtrl(senderCryptoCtrl); + zrtp->srtcpSend = senderCryptoCtrl; + } + if (part == ForReceiver) { + // To decrypt packets: intiator uses responder keys, + // responder initiator keys + // See comment above. + if (secrets->role == Initiator) { + recvCrypto = zsrtp_CreateWrapper(zrtp->peerSSRC, + 0, + 0L, // keyderivation << 48, + cipher, // encryption algo + authn, // authtentication algo + (unsigned char*)secrets->keyResponder, // Master Key + secrets->respKeyLen / 8, // Master Key length + (unsigned char*)secrets->saltResponder, // Master Salt + secrets->respSaltLen / 8, // Master Salt length + secrets->respKeyLen / 8, // encryption keyl + authKeyLen, // authentication key len + secrets->respSaltLen / 8, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag len + + recvCryptoCtrl = zsrtp_CreateWrapperCtrl(zrtp->peerSSRC, + cipher, // encryption algo + authn, // authtication algo + (unsigned char*)secrets->keyResponder, // Master Key + secrets->respKeyLen / 8, // Master Key length + (unsigned char*)secrets->saltResponder, // Master Salt + secrets->respSaltLen / 8, // Master Salt length + secrets->respKeyLen / 8, // encryption keyl + authKeyLen, // authentication key len + secrets->respSaltLen / 8, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag len + // srtcpAuthTagLen / 8); // authentication tag len + } + else { + recvCrypto = zsrtp_CreateWrapper(zrtp->peerSSRC, + 0, + 0L, // keyderivation << 48, + cipher, // encryption algo + authn, // authtentication algo + (unsigned char*)secrets->keyInitiator, // Master Key + secrets->initKeyLen / 8, // Master Key length + (unsigned char*)secrets->saltInitiator, // Master Salt + secrets->initSaltLen / 8, // Master Salt length + secrets->initKeyLen / 8, // encryption keyl + authKeyLen, // authentication key len + secrets->initSaltLen / 8, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag len + + recvCryptoCtrl = zsrtp_CreateWrapperCtrl(zrtp->peerSSRC, + cipher, // encryption algo + authn, // authtication algo + (unsigned char*)secrets->keyInitiator, // Master Key + secrets->initKeyLen / 8, // Master Key length + (unsigned char*)secrets->saltInitiator, // Master Salt + secrets->initSaltLen / 8, // Master Salt length + secrets->initKeyLen / 8, // encryption keyl + authKeyLen, // authentication key len + secrets->initSaltLen / 8, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag len + // srtcpAuthTagLen / 8); // authentication tag len + } + if (recvCrypto == NULL) { + return 0; + } + // Create a SRTP crypto context for real SSRC input stream. + // If the sender didn't provide a SSRC just insert the template + // into the queue. After we received the first packet the real + // crypto context will be created. + // + // Note: key derivation can be done at this time only if the + // key derivation rate is 0 (disabled). For ZRTP this is the + // case: the key derivation is defined as 2^48 + // which is effectively 0. + zsrtp_deriveSrtpKeys(recvCrypto, 0L); + zrtp->srtpReceive = recvCrypto; + + zsrtp_deriveSrtpKeysCtrl(recvCryptoCtrl); + zrtp->srtcpReceive = recvCryptoCtrl; + } + return 1; +} + +static void zrtp_srtpSecretsOff(ZrtpContext* ctx, int32_t part) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + if (part == ForSender) + { + zsrtp_DestroyWrapper(zrtp->srtpSend); + zsrtp_DestroyWrapperCtrl(zrtp->srtcpSend); + zrtp->srtpSend = NULL; + zrtp->srtcpSend = NULL; + } + if (part == ForReceiver) + { + zsrtp_DestroyWrapper(zrtp->srtpReceive); + zsrtp_DestroyWrapperCtrl(zrtp->srtcpReceive); + zrtp->srtpReceive = NULL; + zrtp->srtcpReceive = NULL; + } + + if (zrtp->cb.secure_off) + zrtp->cb.secure_off(&zrtp->base); +} + +static void zrtp_srtpSecretsOn(ZrtpContext* ctx, char* c, char* s, int32_t verified) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + int len; + + len = strlen(c); + if (len > sizeof(zrtp->cipher) - 1) + len = sizeof(zrtp->cipher) - 1; + memcpy(zrtp->cipher, c, len); + zrtp->cipher[len] = '\0'; + + if (zrtp->cb.secure_on) + zrtp->cb.secure_on(&zrtp->base, c); + + if (s && strlen(s) > 0 && zrtp->cb.show_sas) + zrtp->cb.show_sas(&zrtp->base, s, verified); +} + +static void zrtp_handleGoClear(ZrtpContext* ctx) +{ + /* TODO: implement */ +} + +static void zrtp_zrtpNegotiationFailed(ZrtpContext* ctx, int32_t severity, int32_t subCode) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + if (zrtp->cb.negotiation_failed) + zrtp->cb.negotiation_failed(&zrtp->base, severity, subCode); +} + +static void zrtp_zrtpNotSuppOther(ZrtpContext* ctx) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + if (zrtp->cb.not_supported_by_other) + zrtp->cb.not_supported_by_other(&zrtp->base); +} + +static void zrtp_synchEnter(ZrtpContext* ctx) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + pj_mutex_lock(zrtp->zrtpMutex); +} + +static void zrtp_synchLeave(ZrtpContext* ctx) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + pj_mutex_unlock(zrtp->zrtpMutex); +} + +static void zrtp_zrtpAskEnrollment(ZrtpContext* ctx, int32_t info) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + if (zrtp->cb.ask_enrollment) + zrtp->cb.ask_enrollment(&zrtp->base, info); +} + +static void zrtp_zrtpInformEnrollment(ZrtpContext* ctx, int32_t info) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + if (zrtp->cb.inform_enrollment) + zrtp->cb.inform_enrollment(&zrtp->base, info); +} + +static void zrtp_signSAS(ZrtpContext* ctx, uint8_t* sasHash) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + if (zrtp->cb.sign_sas) + zrtp->cb.sign_sas(&zrtp->base, sasHash); +} + +static int32_t zrtp_checkSASSignature(ZrtpContext* ctx, uint8_t* sasHash) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + if (zrtp->cb.check_sas_signature) + return zrtp->cb.check_sas_signature(&zrtp->base, sasHash); + return 0; +} + +/* + * Implement the specific ZRTP transport functions + */ +PJ_DEF(void) pjmedia_transport_zrtp_setEnableZrtp(pjmedia_transport *tp, pj_bool_t onOff) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp); + + zrtp->enableZrtp = onOff; +} + +PJ_DEF(pj_bool_t) pjmedia_transport_zrtp_isEnableZrtp(pjmedia_transport *tp) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + PJ_ASSERT_RETURN(tp, PJ_FALSE); + + return zrtp->enableZrtp; + +} + +PJ_DEF(void) pjmedia_transport_zrtp_startZrtp(pjmedia_transport *tp) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp && zrtp->zrtpCtx); + + zrtp_startZrtpEngine(zrtp->zrtpCtx); + zrtp->started = 1; +} + +PJ_DEF(void) pjmedia_transport_zrtp_stopZrtp(pjmedia_transport *tp) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp && zrtp->zrtpCtx); + + zrtp_stopZrtpEngine(zrtp->zrtpCtx); + zrtp->started = 0; +} + +PJ_DEF(void) pjmedia_transport_zrtp_setLocalSSRC(pjmedia_transport *tp, uint32_t ssrc) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp); + + zrtp->localSSRC = ssrc; +} + +PJ_DEF(pj_bool_t) pjmedia_transport_zrtp_isMitmMode(pjmedia_transport *tp) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp); + + return zrtp->mitmMode; +} + +PJ_DEF(void) pjmedia_transport_zrtp_setMitmMode(pjmedia_transport *tp, pj_bool_t mitmMode) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp); + + zrtp->mitmMode = mitmMode; +} + +PJ_DEF(ZrtpContext*) pjmedia_transport_zrtp_getZrtpContext(pjmedia_transport *tp) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + PJ_ASSERT_RETURN(tp, NULL); + + return zrtp->zrtpCtx; +} + +PJ_DEF(void) pjmedia_transport_zrtp_setSASVerified(pjmedia_transport *tp, pj_bool_t verified) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp); + + if (verified) + zrtp_SASVerified(zrtp->zrtpCtx); + else + zrtp_resetSASVerified(zrtp->zrtpCtx); +} + +PJ_DEF(int) pjmedia_transport_zrtp_getPeerZid(pjmedia_transport *tp, unsigned char* data) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp); + + return zrtp_getPeerZid(zrtp->zrtpCtx, data); +} + +PJ_DEF(char*) pjmedia_transport_zrtp_getPeerName(pjmedia_transport *tp) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp); + + return zrtp_getPeerName(zrtp->zrtpCtx); +} + +PJ_DEF(void) pjmedia_transport_zrtp_putPeerName(pjmedia_transport *tp, const char *name) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp); + + zrtp_putPeerName(zrtp->zrtpCtx, name); +} + +PJ_DEF(char*) pjmedia_transport_zrtp_getMultiStreamParameters(pjmedia_transport *tp, pj_int32_t *length) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp); + + return zrtp_getMultiStrParams(zrtp->zrtpCtx, length); +} + +PJ_DEF(void) pjmedia_transport_zrtp_setMultiStreamParameters(pjmedia_transport *tp, const char *parameters, pj_int32_t length, pjmedia_transport *master_tp) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + struct tp_zrtp *master_zrtp = (struct tp_zrtp*)master_tp; + pj_assert(tp); + pj_assert(master_tp); + + zrtp_setMultiStrParams(zrtp->zrtpCtx, (char*) parameters, length, master_zrtp->zrtpCtx); +} + +/* + * get_info() is called to get the transport addresses to be put + * in SDP c= line and a=rtcp line. + */ +static pj_status_t transport_get_info(pjmedia_transport *tp, + pjmedia_transport_info *info) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pjmedia_zrtp_info zrtp_info; + int spc_info_idx; + + PJ_ASSERT_RETURN(tp && info, PJ_EINVAL); + PJ_ASSERT_RETURN(info->specific_info_cnt < + PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXCNT, PJ_ETOOMANY); + + zrtp_info.active = zrtp_inState(zrtp->zrtpCtx, SecureState) ? PJ_TRUE : PJ_FALSE; + if (zrtp_info.active) + memcpy(zrtp_info.cipher, zrtp->cipher, sizeof(zrtp->cipher)); + else + zrtp_info.cipher[0] = '\0'; + + spc_info_idx = info->specific_info_cnt++; + info->spc_info[spc_info_idx].type = PJMEDIA_TRANSPORT_TYPE_ZRTP; + + pj_memcpy(&info->spc_info[spc_info_idx].buffer, &zrtp_info, + sizeof(zrtp_info)); + + return pjmedia_transport_get_info(zrtp->slave_tp, info); +} + +/* This is our RTP callback, that is called by the slave transport when it + * receives RTP packet. + */ +static void transport_rtp_cb(void *user_data, void *pkt, pj_ssize_t size) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)user_data; + + pj_uint8_t* buffer = (pj_uint8_t*)pkt; + int32_t newLen = 0; + pj_status_t rc = PJ_SUCCESS; + + pj_assert(zrtp && zrtp->stream_rtcp_cb && pkt); + + // check if this could be a real RTP/SRTP packet. + if ((*buffer & 0xf0) != 0x10) + { + // Could be real RTP, check if we are in secure mode + if (zrtp->srtpReceive == NULL || size < 0) + { + zrtp->stream_rtp_cb(zrtp->stream_user_data, pkt, size); + } + else + { + rc = zsrtp_unprotect(zrtp->srtpReceive, pkt, size, &newLen); + if (rc == 1) + { + zrtp->unprotect++; + zrtp->stream_rtp_cb(zrtp->stream_user_data, pkt, + newLen); + zrtp->unprotect_err = 0; + } + else + { + if (zrtp->cb.show_message) + { + if (rc == -1) + zrtp->cb.show_message(&zrtp->base, zrtp_Warning, zrtp_WarningSRTPauthError); + else + zrtp->cb.show_message(&zrtp->base, zrtp_Warning, zrtp_WarningSRTPreplayError); + } + zrtp->unprotect_err = rc; + /* We failed to decrypt the packet, but forward it regardless to the slave + * transport, it might not have been encrypted after all */ + zrtp->stream_rtp_cb(zrtp->stream_user_data, pkt, size); + } + } + if (!zrtp->started && zrtp->enableZrtp) + pjmedia_transport_zrtp_startZrtp((pjmedia_transport *)zrtp); + + return; + } + + // We assume all other packets are ZRTP packets here. Process + // if ZRTP processing is enabled. Because valid RTP packets are + // already handled we delete any packets here after processing. + if (zrtp->enableZrtp && zrtp->zrtpCtx != NULL) + { + // Get CRC value into crc (see above how to compute the offset) + pj_uint16_t temp = size - CRC_SIZE; + pj_uint32_t crc = *(uint32_t*)(buffer + temp); + crc = pj_ntohl(crc); + + if (!zrtp_CheckCksum(buffer, temp, crc)) + { + if (zrtp->cb.show_message) + zrtp->cb.show_message(&zrtp->base, zrtp_Warning, zrtp_WarningCRCmismatch); + return; + } + + pj_uint32_t magic = *(pj_uint32_t*)(buffer + 4); + magic = pj_ntohl(magic); + + // Check if it is really a ZRTP packet, return, no further processing + if (magic != ZRTP_MAGIC) + return; + + // cover the case if the other party sends _only_ ZRTP packets at the + // beginning of a session. Start ZRTP in this case as well. + if (!zrtp->started) + { + pjmedia_transport_zrtp_startZrtp((pjmedia_transport *)zrtp); + } + // this now points beyond the undefined and length field. + // We need them, thus adjust + unsigned char* zrtpMsg = (buffer + 12); + + // store peer's SSRC in host order, used when creating the CryptoContext + zrtp->peerSSRC = *(pj_uint32_t*)(buffer + 8); + zrtp->peerSSRC = pj_ntohl(zrtp->peerSSRC); + zrtp_processZrtpMessage(zrtp->zrtpCtx, zrtpMsg, zrtp->peerSSRC, size); + } +} + + +/* This is our RTCP callback, that is called by the slave transport when it + * receives RTCP packet. + */ +static void transport_rtcp_cb(void *user_data, void *pkt, pj_ssize_t size) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)user_data; + int32_t newLen = 0; + pj_status_t rc = PJ_SUCCESS; + + pj_assert(zrtp && zrtp->stream_rtcp_cb); + + if (zrtp->srtcpReceive == NULL || size < 0) + { + zrtp->stream_rtcp_cb(zrtp->stream_user_data, pkt, size); + } + else + { + rc = zsrtp_unprotectCtrl(zrtp->srtcpReceive, pkt, size, &newLen); + + if (rc == 1) + { + /* Call stream's callback */ + zrtp->stream_rtcp_cb(zrtp->stream_user_data, pkt, newLen); + } + else + { + // Testing: print some error output + } + } +} + + +/* + * attach() is called by stream to register callbacks that we should + * call on receipt of RTP and RTCP packets. + */ +static pj_status_t transport_attach(pjmedia_transport *tp, + void *user_data, + const pj_sockaddr_t *rem_addr, + const pj_sockaddr_t *rem_rtcp, + unsigned addr_len, + void (*rtp_cb)(void*, + void*, + pj_ssize_t), + void (*rtcp_cb)(void*, + void*, + pj_ssize_t)) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_status_t status; + + PJ_ASSERT_RETURN(tp && rem_addr && addr_len, PJ_EINVAL); + + /* In this example, we will save the stream information and callbacks + * to our structure, and we will register different RTP/RTCP callbacks + * instead. + */ + pj_assert(zrtp->stream_user_data == NULL); + zrtp->stream_user_data = user_data; + zrtp->stream_rtp_cb = rtp_cb; + zrtp->stream_rtcp_cb = rtcp_cb; + + status = pjmedia_transport_attach(zrtp->slave_tp, zrtp, rem_addr, + rem_rtcp, addr_len, &transport_rtp_cb, + &transport_rtcp_cb); + if (status != PJ_SUCCESS) + { + zrtp->stream_user_data = NULL; + zrtp->stream_rtp_cb = NULL; + zrtp->stream_rtcp_cb = NULL; + return status; + } + + return PJ_SUCCESS; +} + +/* + * detach() is called when the media is terminated, and the stream is + * to be disconnected from us. + */ +static void transport_detach(pjmedia_transport *tp, void *strm) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + + PJ_UNUSED_ARG(strm); + PJ_ASSERT_ON_FAIL(tp, return); + + if (zrtp->stream_user_data != NULL) + { + pjmedia_transport_detach(zrtp->slave_tp, zrtp); + zrtp->stream_user_data = NULL; + zrtp->stream_rtp_cb = NULL; + zrtp->stream_rtcp_cb = NULL; + } +} + + +/* + * send_rtp() is called to send RTP packet. The "pkt" and "size" argument + * contain both the RTP header and the payload. + */ +static pj_status_t transport_send_rtp(pjmedia_transport *tp, + const void *pkt, + pj_size_t size) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_uint32_t* pui = (pj_uint32_t*)pkt; + int32_t newLen = 0; + pj_status_t rc = PJ_SUCCESS; + + PJ_ASSERT_RETURN(tp && pkt, PJ_EINVAL); + + + if (!zrtp->started && zrtp->enableZrtp) + { + if (zrtp->localSSRC == 0) + zrtp->localSSRC = pj_ntohl(pui[2]); /* Learn own SSRC before starting ZRTP */ + + pjmedia_transport_zrtp_startZrtp((pjmedia_transport *)zrtp); + } + + if (zrtp->srtpSend == NULL) + { + return pjmedia_transport_send_rtp(zrtp->slave_tp, pkt, size); + } + else + { + if (size+80 > MAX_RTP_BUFFER_LEN) + return PJ_ETOOBIG; + + pj_memcpy(zrtp->sendBuffer, pkt, size); + rc = zsrtp_protect(zrtp->srtpSend, zrtp->sendBuffer, size, &newLen); + zrtp->protect++; + + if (rc == 1) + return pjmedia_transport_send_rtp(zrtp->slave_tp, zrtp->sendBuffer, newLen); + else + return PJ_EIGNORED; + } +} + + +/* + * send_rtcp() is called to send RTCP packet. The "pkt" and "size" argument + * contain the RTCP packet. + */ +static pj_status_t transport_send_rtcp(pjmedia_transport *tp, + const void *pkt, + pj_size_t size) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_status_t rc = PJ_SUCCESS; + int32_t newLen = 0; + PJ_ASSERT_RETURN(tp, PJ_EINVAL); + + /* You may do some processing to the RTCP packet here if you want. */ + if (zrtp->srtcpSend == NULL) + { + return pjmedia_transport_send_rtcp(zrtp->slave_tp, pkt, size); + } + else + { + if (size+80 > MAX_RTCP_BUFFER_LEN) + return PJ_ETOOBIG; + + pj_memcpy(zrtp->sendBufferCtrl, pkt, size); + rc = zsrtp_protectCtrl(zrtp->srtcpSend, zrtp->sendBufferCtrl, size, &newLen); + + if (rc == 1) + return pjmedia_transport_send_rtcp(zrtp->slave_tp, zrtp->sendBufferCtrl, newLen); + else + return PJ_EIGNORED; + } + + /* Send the packet using the slave transport */ +// return pjmedia_transport_send_rtcp(zrtp->slave_tp, pkt, size); +} + + +/* + * This is another variant of send_rtcp(), with the alternate destination + * address in the argument. + */ +static pj_status_t transport_send_rtcp2(pjmedia_transport *tp, + const pj_sockaddr_t *addr, + unsigned addr_len, + const void *pkt, + pj_size_t size) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + PJ_ASSERT_RETURN(tp, PJ_EINVAL); + + return pjmedia_transport_send_rtcp2(zrtp->slave_tp, addr, addr_len, + pkt, size); +} + +/* + * The media_create() is called when the transport is about to be used for + * a new call. + */ +static pj_status_t transport_media_create(pjmedia_transport *tp, + pj_pool_t *sdp_pool, + unsigned options, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + PJ_ASSERT_RETURN(tp, PJ_EINVAL); + + /* if "rem_sdp" is not NULL, it means we are UAS. You may do some + * inspections on the incoming SDP to verify that the SDP is acceptable + * for us. If the SDP is not acceptable, we can reject the SDP by + * returning non-PJ_SUCCESS. + */ + if (rem_sdp) + { + /* Do your stuff.. */ + } + + /* Once we're done with our initialization, pass the call to the + * slave transports to let it do it's own initialization too. + */ + return pjmedia_transport_media_create(zrtp->slave_tp, sdp_pool, options, + rem_sdp, media_index); +} + +/* + * The encode_sdp() is called when we're about to send SDP to remote party, + * either as SDP offer or as SDP answer. + */ +static pj_status_t transport_encode_sdp(pjmedia_transport *tp, + pj_pool_t *sdp_pool, + pjmedia_sdp_session *local_sdp, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + int32_t numVersions, i; + + PJ_ASSERT_RETURN(tp, PJ_EINVAL); + + /* If "rem_sdp" is not NULL, it means we're encoding SDP answer. You may + * do some more checking on the SDP's once again to make sure that + * everything is okay before we send SDP. + */ + if (rem_sdp) + { + /* Do checking stuffs here.. */ + } + + /* Add zrtp-hash attributes to both INVITE and 200 OK. */ + numVersions = zrtp_getNumberSupportedVersions(zrtp->zrtpCtx); + for (i = 0; i < numVersions; i++) { + char *zrtp_hello_hash = zrtp_getHelloHash(zrtp->zrtpCtx, i); + if (zrtp_hello_hash && *zrtp_hello_hash) { + int zrtp_hello_hash_len = strlen(zrtp_hello_hash); + pj_str_t *zrtp_hash_str = PJ_POOL_ALLOC_T(sdp_pool, pj_str_t); + pjmedia_sdp_attr *zrtp_hash = NULL; + + zrtp_hash_str->ptr = zrtp_hello_hash; + zrtp_hash_str->slen = zrtp_hello_hash_len; + + zrtp_hash = pjmedia_sdp_attr_create(sdp_pool, "zrtp-hash", zrtp_hash_str); + if (zrtp_hash && + pjmedia_sdp_attr_add(&local_sdp->media[media_index]->attr_count, local_sdp->media[media_index]->attr, zrtp_hash) == PJ_SUCCESS) { + PJ_LOG(4, (THIS_FILE, "attribute added: a=zrtp-hash:%s", zrtp_hello_hash)); + } + else { + PJ_LOG(4, (THIS_FILE, "error adding attribute: a=zrtp-hash:%s", zrtp_hello_hash)); + } + } + } + + /* You may do anything to the local_sdp, e.g. adding new attributes, or + * even modifying the SDP if you want. + */ + if (0) + { + /* Say we add a proprietary attribute here.. */ + pjmedia_sdp_attr *my_attr; + + my_attr = PJ_POOL_ALLOC_T(sdp_pool, pjmedia_sdp_attr); + pj_strdup2(sdp_pool, &my_attr->name, "X-zrtp"); + pj_strdup2(sdp_pool, &my_attr->value, "some value"); + + pjmedia_sdp_attr_add(&local_sdp->media[media_index]->attr_count, + local_sdp->media[media_index]->attr, + my_attr); + } + + /* And then pass the call to slave transport to let it encode its + * information in the SDP. You may choose to call encode_sdp() to slave + * first before adding your custom attributes if you want. + */ + return pjmedia_transport_encode_sdp(zrtp->slave_tp, sdp_pool, local_sdp, rem_sdp, media_index); +} + +/* + * The media_start() is called once both local and remote SDP have been + * negotiated successfully, and the media is ready to start. Here we can start + * committing our processing. + */ +static pj_status_t transport_media_start(pjmedia_transport *tp, + pj_pool_t *pool, + const pjmedia_sdp_session *local_sdp, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + PJ_ASSERT_RETURN(tp, PJ_EINVAL); + + /* Do something.. */ + + /* And pass the call to the slave transport */ + return pjmedia_transport_media_start(zrtp->slave_tp, pool, local_sdp, + rem_sdp, media_index); +} + +/* + * The media_stop() is called when media has been stopped. + */ +static pj_status_t transport_media_stop(pjmedia_transport *tp) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + PJ_ASSERT_RETURN(tp, PJ_EINVAL); + + /* Do something.. */ + PJ_LOG(4, (THIS_FILE, "Media stop - encrypted packets: %ld, decrypted packets: %ld", + zrtp->protect, zrtp->unprotect)); + + /* And pass the call to the slave transport */ + return pjmedia_transport_media_stop(zrtp->slave_tp); +} + +/* + * simulate_lost() is called to simulate packet lost + */ +static pj_status_t transport_simulate_lost(pjmedia_transport *tp, + pjmedia_dir dir, + unsigned pct_lost) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + + PJ_ASSERT_RETURN(tp, PJ_EINVAL); + + return pjmedia_transport_simulate_lost(zrtp->slave_tp, dir, pct_lost); +} + +/* + * destroy() is called when the transport is no longer needed. + */ +static pj_status_t transport_destroy(pjmedia_transport *tp) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + + PJ_ASSERT_RETURN(tp, PJ_EINVAL); + + PJ_LOG(4, (THIS_FILE, "Destroy - encrypted packets: %ld, decrypted packets: %ld", + zrtp->protect, zrtp->unprotect)); + + /* close the slave transport in case */ + if (zrtp->close_slave && zrtp->slave_tp) + pjmedia_transport_close(zrtp->slave_tp); + + /* Self destruct.. */ + zrtp_stopZrtpEngine(zrtp->zrtpCtx); + zrtp_DestroyWrapper(zrtp->zrtpCtx); + zrtp->zrtpCtx = NULL; + + /* In case mutex is being acquired by other thread */ + pj_mutex_lock(zrtp->zrtpMutex); + pj_mutex_unlock(zrtp->zrtpMutex); + pj_mutex_destroy(zrtp->zrtpMutex); + + pj_pool_release(zrtp->pool); + + return PJ_SUCCESS; +} + + + + -diff -ruN pjproject-2.10/pjmedia/src/pjmedia/vid_stream.c pjsip/pjmedia/src/pjmedia/vid_stream.c ---- pjproject-2.10/pjmedia/src/pjmedia/vid_stream.c 2020-02-14 10:48:27.000000000 +0100 -+++ pjsip/pjmedia/src/pjmedia/vid_stream.c 2021-02-06 21:17:10.577365678 +0100 -@@ -139,9 +139,15 @@ - unsigned dec_delay_cnt; /**< Decoding delay (in frames).*/ - pjmedia_event fmt_event; /**< Buffered fmt_changed event - to avoid deadlock */ -+ pjmedia_event found_keyframe_event; -+ /**< Buffered found keyframe -+ event for delayed republish*/ - pjmedia_event miss_keyframe_event; - /**< Buffered missing keyframe - event for delayed republish*/ -+ pjmedia_event keyframe_req_event; -+ /**< Buffered keyframe request -+ event for delayed republish*/ - - unsigned frame_size; /**< Size of encoded base frame.*/ - unsigned frame_ts_len; /**< Frame length in timestamp. */ -@@ -410,6 +416,11 @@ - pj_memcpy(&stream->fmt_event, event, sizeof(*event)); - return PJ_SUCCESS; - -+ case PJMEDIA_EVENT_KEYFRAME_FOUND: -+ /* Republish this event later from get_frame(). */ -+ pj_memcpy(&stream->found_keyframe_event, event, sizeof(*event)); -+ return PJ_SUCCESS; -+ - case PJMEDIA_EVENT_KEYFRAME_MISSING: - /* Republish this event later from get_frame(). */ - pj_memcpy(&stream->miss_keyframe_event, event, sizeof(*event)); -@@ -523,7 +534,7 @@ - pkt_len); - - /* Send RTCP */ -- send_rtcp(stream, PJ_TRUE, PJ_FALSE); -+ send_rtcp(stream, PJ_TRUE, PJ_FALSE, PJ_FALSE, PJ_FALSE); - - /* Update stats in case the stream is paused */ - stream->rtcp.stat.rtp_tx_last_seq = pj_ntohs(stream->enc->rtp.out_hdr.seq); -@@ -1028,6 +1039,14 @@ - } - - pjmedia_rtcp_rx_rtcp(&stream->rtcp, pkt, bytes_read); -+ -+ /* XXX: posting some event from the RTCP session might be a better option */ -+ if (stream->rtcp.keyframe_requested) { -+ pjmedia_event event; -+ pjmedia_event_init(&event, PJMEDIA_EVENT_KEYFRAME_REQUESTED, NULL, stream); -+ pj_memcpy(&stream->keyframe_req_event, &event, sizeof(event)); -+ } -+ - } - - static pj_status_t put_frame(pjmedia_port *port, -@@ -1419,8 +1438,8 @@ - new_fps.denum = ts_diff; - } - -- /* Only apply the new FPS when it is >0, <=100, and increasing */ -- if (new_fps.num/new_fps.denum <= 100 && -+ /* Only apply the new FPS when it is >0, <=60, and increasing */ -+ if (new_fps.num/new_fps.denum <= 60 && - new_fps.num/new_fps.denum > 0 && - new_fps.num*1.0/new_fps.denum > - stream->dec_max_fps.num*1.0/stream->dec_max_fps.denum) -@@ -1516,8 +1535,10 @@ - /* Override the framerate to be 1.5x higher in the event - * for the renderer. - */ -+#if 0 - fmt_chg_data->new_fmt.det.vid.fps.num *= 3; - fmt_chg_data->new_fmt.det.vid.fps.num /= 2; -+#endif - } else { - pjmedia_format_copy(&stream->info.codec_param->enc_fmt, - &fmt_chg_data->new_fmt); -@@ -1534,12 +1555,25 @@ - stream->fmt_event.type = PJMEDIA_EVENT_NONE; - } - -+ if (stream->found_keyframe_event.type != PJMEDIA_EVENT_NONE) { -+ pjmedia_event_publish(NULL, port, &stream->found_keyframe_event, -+ PJMEDIA_EVENT_PUBLISH_POST_EVENT); -+ stream->found_keyframe_event.type = PJMEDIA_EVENT_NONE; -+ } -+ - if (stream->miss_keyframe_event.type != PJMEDIA_EVENT_NONE) { - pjmedia_event_publish(NULL, port, &stream->miss_keyframe_event, - PJMEDIA_EVENT_PUBLISH_POST_EVENT); - stream->miss_keyframe_event.type = PJMEDIA_EVENT_NONE; - } - -+ if (stream->keyframe_req_event.type != PJMEDIA_EVENT_NONE) { -+ pjmedia_event_publish(NULL, port, &stream->keyframe_req_event, -+ PJMEDIA_EVENT_PUBLISH_POST_EVENT); -+ stream->keyframe_req_event.type = PJMEDIA_EVENT_NONE; -+ } -+ -+ - pj_mutex_lock( stream->jb_mutex ); - - if (stream->dec_frame.size == 0) { -@@ -1846,7 +1880,9 @@ - * local renderer clock) or video setup lag. Note that the actual framerate - * will be continuously calculated based on the incoming RTP timestamps. - */ -+#if 0 - vfd_dec->fps.num = vfd_dec->fps.num * 3 / 2; -+#endif - stream->dec_max_fps = vfd_dec->fps; - - /* Create decoder channel */ -@@ -2362,6 +2398,46 @@ - - return PJ_SUCCESS; - } -+ -+/* -+ * Send RTCP PLI. -+ */ -+PJ_DEF(pj_status_t) pjmedia_vid_stream_send_rtcp_pli( -+ pjmedia_vid_stream *stream) -+{ -+ PJ_ASSERT_RETURN(stream, PJ_EINVAL); -+ -+ if (stream->enc && stream->transport) { -+ void *sr_rr_pkt; -+ pj_uint8_t *pkt; -+ int len, max_len; -+ pj_status_t status; -+ pj_size_t pli_len; -+ -+ /* Build RTCP RR/SR packet */ -+ pjmedia_rtcp_build_rtcp(&stream->rtcp, &sr_rr_pkt, &len); -+ -+ pkt = (pj_uint8_t*) stream->out_rtcp_pkt; -+ pj_memcpy(pkt, sr_rr_pkt, len); -+ max_len = stream->out_rtcp_pkt_size; -+ -+ /* Build RTCP PLI packet */ -+ pli_len = max_len - len; -+ status = pjmedia_rtcp_build_rtcp_pli(&stream->rtcp, pkt+len, &pli_len); -+ if (status != PJ_SUCCESS) { -+ PJ_PERROR(4,(stream->name.ptr, status, "Error generating RTCP PLI")); -+ } else { -+ len += (int)pli_len; -+ } -+ -+ /* Send! */ -+ status = pjmedia_transport_send_rtcp(stream->transport, pkt, len); -+ -+ return status; -+ } -+ -+ return PJ_SUCCESS; -+} - - - /* diff -ruN pjproject-2.10/pjmedia/src/pjmedia/vid_tee.c pjsip/pjmedia/src/pjmedia/vid_tee.c --- pjproject-2.10/pjmedia/src/pjmedia/vid_tee.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/vid_tee.c 2021-02-06 21:28:30.516675163 +0100 @@ -52,6 +52,7 @@ unsigned dst_port_cnt; vid_tee_dst_port *dst_ports; pj_uint8_t *put_frm_flag; + pj_mutex_t *lock; struct vid_tee_conv_t { pjmedia_converter *conv; @@ -86,6 +87,11 @@ tee->pf = pool->factory; tee->pool = pj_pool_create(tee->pf, "video tee", 500, 500, NULL); + /* Create lock */ + status = pj_mutex_create_simple(pool, "vid-tee-mutex", &tee->lock); + if (status != PJ_SUCCESS) + return status; + /* Initialize video tee structure */ tee->dst_port_maxcnt = max_dst_cnt; tee->dst_ports = (vid_tee_dst_port*) @@ -100,14 +106,16 @@ /* Initialize video tee buffer, its size is one frame */ vfi = pjmedia_get_video_format_info(NULL, fmt->id); - if (vfi == NULL) - return PJMEDIA_EBADFMT; + if (vfi == NULL) { + status = PJMEDIA_EBADFMT; + goto on_error; + } pj_bzero(&vafp, sizeof(vafp)); vafp.size = fmt->det.vid.size; status = vfi->apply_fmt(vfi, &vafp); if (status != PJ_SUCCESS) - return status; + goto on_error; tee->buf_size = vafp.framebytes; @@ -118,7 +126,7 @@ PJMEDIA_DIR_ENCODING, fmt); if (status != PJ_SUCCESS) - return status; + goto on_error;; tee->base.get_frame = &tee_get_frame; tee->base.put_frame = &tee_put_frame; @@ -128,6 +136,12 @@ *p_vid_tee = &tee->base; return PJ_SUCCESS; + +on_error: + pj_mutex_destroy(tee->lock); + tee->lock = NULL; + return status; + } static void realloc_buf(vid_tee_port *vid_tee, @@ -169,21 +183,29 @@ { vid_tee_port *tee = (vid_tee_port*)vid_tee; pjmedia_video_format_detail *vfd; + pj_status_t status; PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN, PJ_EINVAL); - if (tee->dst_port_cnt >= tee->dst_port_maxcnt) - return PJ_ETOOMANY; - - if (vid_tee->info.fmt.id != port->info.fmt.id) - return PJMEDIA_EBADFMT; + pj_mutex_lock(tee->lock); + + if (tee->dst_port_cnt >= tee->dst_port_maxcnt) { + status = PJ_ETOOMANY; + goto end; + } + + if (vid_tee->info.fmt.id != port->info.fmt.id) { + status = PJMEDIA_EBADFMT; + goto end; + } vfd = pjmedia_format_get_video_format_detail(&port->info.fmt, PJ_TRUE); if (vfd->size.w != vid_tee->info.fmt.det.vid.size.w || vfd->size.h != vid_tee->info.fmt.det.vid.size.h) { - return PJMEDIA_EBADFMT; + status = PJMEDIA_EBADFMT; + goto end; } realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)? @@ -194,7 +216,12 @@ tee->dst_ports[tee->dst_port_cnt].option = option; ++tee->dst_port_cnt; - return PJ_SUCCESS; + status = PJ_SUCCESS; + +end: + pj_mutex_unlock(tee->lock); + return status; + } @@ -208,12 +235,17 @@ { vid_tee_port *tee = (vid_tee_port*)vid_tee; pjmedia_video_format_detail *vfd; + pj_status_t status; PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN, PJ_EINVAL); - - if (tee->dst_port_cnt >= tee->dst_port_maxcnt) - return PJ_ETOOMANY; + + pj_mutex_lock(tee->lock); + + if (tee->dst_port_cnt >= tee->dst_port_maxcnt) { + status = PJ_ETOOMANY; + goto end; + } pj_bzero(&tee->tee_conv[tee->dst_port_cnt], sizeof(tee->tee_conv[0])); @@ -226,17 +258,18 @@ const pjmedia_video_format_info *vfi; pjmedia_video_apply_fmt_param vafp; pjmedia_conversion_param conv_param; - pj_status_t status; vfi = pjmedia_get_video_format_info(NULL, port->info.fmt.id); - if (vfi == NULL) + if (vfi == NULL) { - return PJMEDIA_EBADFMT; + status = PJMEDIA_EBADFMT; + goto end; + } pj_bzero(&vafp, sizeof(vafp)); vafp.size = port->info.fmt.det.vid.size; status = vfi->apply_fmt(vfi, &vafp); if (status != PJ_SUCCESS) - return status; + goto end; realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)? 2: 1, vafp.framebytes); @@ -248,7 +280,7 @@ NULL, tee->pool, &conv_param, &tee->tee_conv[tee->dst_port_cnt].conv); if (status != PJ_SUCCESS) - return status; + goto end; tee->tee_conv[tee->dst_port_cnt].conv_buf_size = vafp.framebytes; } else { @@ -259,8 +291,12 @@ tee->dst_ports[tee->dst_port_cnt].dst = port; tee->dst_ports[tee->dst_port_cnt].option = option; ++tee->dst_port_cnt; - - return PJ_SUCCESS; + + status = PJ_SUCCESS; + +end: + pj_mutex_unlock(tee->lock); + return status; } @@ -276,6 +312,8 @@ PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN, PJ_EINVAL); + pj_mutex_lock(tee->lock); + for (i = 0; i < tee->dst_port_cnt; ++i) { if (tee->dst_ports[i].dst == port) { if (tee->tee_conv[i].conv) @@ -286,10 +324,13 @@ pj_array_erase(tee->tee_conv, sizeof(tee->tee_conv[0]), tee->dst_port_cnt, i); --tee->dst_port_cnt; + + pj_mutex_unlock(tee->lock); return PJ_SUCCESS; } } + pj_mutex_unlock(tee->lock); return PJ_ENOTFOUND; } @@ -300,6 +341,12 @@ unsigned i, j; const pj_uint8_t PUT_FRM_DONE = 1; + if (pj_mutex_trylock(tee->lock) != PJ_SUCCESS) { + /* we are busy adding / removing consumers */ + return PJ_SUCCESS; + } + + pj_bzero(tee->put_frm_flag, tee->dst_port_cnt * sizeof(tee->put_frm_flag[0])); @@ -364,6 +411,7 @@ } } + pj_mutex_unlock(tee->lock); return PJ_SUCCESS; } @@ -383,6 +431,11 @@ PJ_ASSERT_RETURN(port && port->info.signature==TEE_PORT_SIGN, PJ_EINVAL); + if (tee->lock) { + pj_mutex_destroy(tee->lock); + tee->lock = NULL; + } + pj_pool_release(tee->pool); if (tee->buf_pool) pj_pool_release(tee->buf_pool); diff -ruN pjproject-2.10/pjmedia/src/pjmedia-audiodev/alsa_dev.c pjsip/pjmedia/src/pjmedia-audiodev/alsa_dev.c --- pjproject-2.10/pjmedia/src/pjmedia-audiodev/alsa_dev.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia-audiodev/alsa_dev.c 2021-02-06 23:08:42.203153000 +0100 @@ -43,7 +43,7 @@ #define ALSASOUND_CAPTURE 2 #define MAX_SOUND_CARDS 5 #define MAX_SOUND_DEVICES_PER_CARD 5 -#define MAX_DEVICES 32 +#define MAX_DEVICES 128 #define MAX_MIX_NAME_LEN 64 /* Set to 1 to enable tracing */ @@ -74,6 +74,10 @@ pjmedia_aud_play_cb play_cb, void *user_data, pjmedia_aud_stream **p_strm); +static void alsa_factory_set_observer(pjmedia_aud_dev_factory *f, + pjmedia_aud_dev_change_callback cb); +static int alsa_factory_get_default_rec_dev(pjmedia_aud_dev_factory *f); +static int alsa_factory_get_default_play_dev(pjmedia_aud_dev_factory *f); /* * Stream prototypes @@ -90,6 +94,15 @@ static pj_status_t alsa_stream_stop(pjmedia_aud_stream *strm); static pj_status_t alsa_stream_destroy(pjmedia_aud_stream *strm); +/* alsa device info */ +struct alsa_dev_info +{ + pjmedia_aud_dev_info info; + char alsa_name[64]; + int input_count; + int output_count; +}; + struct alsa_factory { @@ -99,7 +112,7 @@ pj_pool_t *base_pool; unsigned dev_cnt; - pjmedia_aud_dev_info devs[MAX_DEVICES]; + struct alsa_dev_info devs[MAX_DEVICES]; char pb_mixer_name[MAX_MIX_NAME_LEN]; }; @@ -140,7 +153,10 @@ &alsa_factory_get_dev_info, &alsa_factory_default_param, &alsa_factory_create_stream, - &alsa_factory_refresh + &alsa_factory_refresh, + &alsa_factory_set_observer, + &alsa_factory_get_default_rec_dev, + &alsa_factory_get_default_play_dev }; static pjmedia_aud_stream_op alsa_stream_op = @@ -214,9 +230,9 @@ } -static pj_status_t add_dev (struct alsa_factory *af, const char *dev_name) +static pj_status_t add_dev (struct alsa_factory *af, const char *dev_name, const char *dev_desc) { - pjmedia_aud_dev_info *adi; + struct alsa_dev_info *adi; snd_pcm_t* pcm; int pb_result, ca_result; @@ -258,23 +274,63 @@ pj_bzero(adi, sizeof(*adi)); /* Set device name */ - strncpy(adi->name, dev_name, sizeof(adi->name)); + strncpy(adi->alsa_name, dev_name, sizeof(adi->alsa_name)); + + /* Set comprehensive device name */ + int name_size = sizeof(adi->info.name); + if (dev_desc) { + pj_bool_t name_set = PJ_FALSE; + if (strncmp("sysdefault", dev_name, 10) == 0) { + /* Only use first line for default device*/ + char *ptr = strstr(dev_desc, "\n"); + if (ptr) { + int len = ptr - dev_desc; + strncpy(adi->info.name, dev_desc, (len >= name_size-1)?name_size:len); + name_set = PJ_TRUE; + } + } else if (strncmp("iec958", dev_name, 6) == 0) { + /* Mangle name for SPDIF devices*/ + char *ptr = strstr(dev_desc, ","); + if (ptr) { + int len = ptr - dev_desc; + if (len + 18 < name_size) { + strncpy(adi->info.name, dev_desc, len); + strncpy(adi->info.name+len, ", Digital (S/PDIF)", 18); + name_set = PJ_TRUE; + } + } + } + + if (!name_set) { + /* Use the entire description for other device names */ + int i = 0; + while (i < name_size-1 && dev_desc[i] != '\0') { + if (dev_desc[i] == '\n' || dev_desc[i] == '\r') + adi->info.name[i] = ' '; + else + adi->info.name[i] = dev_desc[i]; + i++; + } + } + } else { + strncpy(adi->info.name, dev_name, name_size); + } /* Check the number of playback channels */ - adi->output_count = (pb_result>=0) ? 1 : 0; + adi->info.output_count = (pb_result>=0) ? 1 : 0; /* Check the number of capture channels */ - adi->input_count = (ca_result>=0) ? 1 : 0; + adi->info.input_count = (ca_result>=0) ? 1 : 0; /* Set the default sample rate */ - adi->default_samples_per_sec = 8000; + adi->info.default_samples_per_sec = 8000; /* Driver name */ - strcpy(adi->driver, "ALSA"); + strcpy(adi->info.driver, "ALSA"); ++af->dev_cnt; - PJ_LOG (5,(THIS_FILE, "Added sound device %s", adi->name)); + PJ_LOG (5,(THIS_FILE, "Added sound device %s", adi->alsa_name)); return PJ_SUCCESS; } @@ -399,10 +455,26 @@ n = hints; while (*n != NULL) { char *name = snd_device_name_get_hint(*n, "NAME"); - if (name != NULL) { - if (0 != strcmp("null", name)) - add_dev(af, name); + char *desc = snd_device_name_get_hint(*n, "DESC"); + if (name != NULL) { + if (strncmp("null", name, 4) == 0 || + strncmp("front", name, 5) == 0 || + strncmp("rear", name, 4) == 0 || + strncmp("side", name, 4) == 0 || + strncmp("dmix", name, 4) == 0 || + strncmp("dsnoop", name, 6) == 0 || + strncmp("hw", name, 2) == 0 || + strncmp("plughw", name, 6) == 0 || + strncmp("center_lfe", name, 10) == 0 || + strncmp("surround", name, 8) == 0 || + (strncmp("default", name, 7) == 0 && strstr(name, ":CARD=") != NULL)) { + /* skip these devices, 'sysdefault' always contains the relevant information */ + ; + } else { + add_dev(af, name, desc); + } free(name); + free(desc); } n++; } @@ -440,7 +512,7 @@ PJ_ASSERT_RETURN(index>=0 && indexdev_cnt, PJ_EINVAL); - pj_memcpy(info, &af->devs[index], sizeof(*info)); + pj_memcpy(info, &af->devs[index].info, sizeof(*info)); info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; return PJ_SUCCESS; @@ -452,22 +524,22 @@ pjmedia_aud_param *param) { struct alsa_factory *af = (struct alsa_factory*)f; - pjmedia_aud_dev_info *adi; + struct alsa_dev_info *adi; PJ_ASSERT_RETURN(index>=0 && indexdev_cnt, PJ_EINVAL); adi = &af->devs[index]; pj_bzero(param, sizeof(*param)); - if (adi->input_count && adi->output_count) { + if (adi->info.input_count && adi->info.output_count) { param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; param->rec_id = index; param->play_id = index; - } else if (adi->input_count) { + } else if (adi->info.input_count) { param->dir = PJMEDIA_DIR_CAPTURE; param->rec_id = index; param->play_id = PJMEDIA_AUD_INVALID_DEV; - } else if (adi->output_count) { + } else if (adi->info.output_count) { param->dir = PJMEDIA_DIR_PLAYBACK; param->play_id = index; param->rec_id = PJMEDIA_AUD_INVALID_DEV; @@ -475,11 +547,11 @@ return PJMEDIA_EAUD_INVDEV; } - param->clock_rate = adi->default_samples_per_sec; + param->clock_rate = adi->info.default_samples_per_sec; param->channel_count = 1; - param->samples_per_frame = adi->default_samples_per_sec * 20 / 1000; + param->samples_per_frame = adi->info.default_samples_per_sec * 20 / 1000; param->bits_per_sample = 16; - param->flags = adi->caps; + param->flags = adi->info.caps; param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY; param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY; @@ -626,9 +698,9 @@ /* Open PCM for playback */ PJ_LOG (5,(THIS_FILE, "open_playback: Open playback device '%s'", - stream->af->devs[param->play_id].name)); + stream->af->devs[param->play_id].alsa_name)); result = snd_pcm_open (&stream->pb_pcm, - stream->af->devs[param->play_id].name, + stream->af->devs[param->play_id].alsa_name, SND_PCM_STREAM_PLAYBACK, 0); if (result < 0) @@ -722,7 +794,7 @@ PJ_LOG (5,(THIS_FILE, "Opened device alsa(%s) for playing, sample rate=%d" ", ch=%d, bits=%d, period size=%d frames, latency=%d ms", - stream->af->devs[param->play_id].name, + stream->af->devs[param->play_id].alsa_name, rate, param->channel_count, param->bits_per_sample, stream->pb_frames, (int)stream->param.output_latency_ms)); @@ -746,9 +818,9 @@ /* Open PCM for capture */ PJ_LOG (5,(THIS_FILE, "open_capture: Open capture device '%s'", - stream->af->devs[param->rec_id].name)); + stream->af->devs[param->rec_id].alsa_name)); result = snd_pcm_open (&stream->ca_pcm, - stream->af->devs[param->rec_id].name, + stream->af->devs[param->rec_id].alsa_name, SND_PCM_STREAM_CAPTURE, 0); if (result < 0) @@ -842,7 +914,7 @@ PJ_LOG (5,(THIS_FILE, "Opened device alsa(%s) for capture, sample rate=%d" ", ch=%d, bits=%d, period size=%d frames, latency=%d ms", - stream->af->devs[param->rec_id].name, + stream->af->devs[param->rec_id].alsa_name, rate, param->channel_count, param->bits_per_sample, stream->ca_frames, (int)stream->param.input_latency_ms)); @@ -903,6 +975,27 @@ return PJ_SUCCESS; } +/* API: set audio device change observer */ +static void alsa_factory_set_observer(pjmedia_aud_dev_factory *f, + pjmedia_aud_dev_change_callback cb) +{ + PJ_UNUSED_ARG(f); + PJ_UNUSED_ARG(cb); +} + +/* API: get default recording device */ +static int alsa_factory_get_default_rec_dev(pjmedia_aud_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return -1; +} + +/* API: get default playback device */ +static int alsa_factory_get_default_play_dev(pjmedia_aud_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return -1; +} /* API: get running parameter */ static pj_status_t alsa_stream_get_param(pjmedia_aud_stream *s, diff -ruN pjproject-2.10/pjmedia/src/pjmedia-audiodev/coreaudio_dev.m pjsip/pjmedia/src/pjmedia-audiodev/coreaudio_dev.m --- pjproject-2.10/pjmedia/src/pjmedia-audiodev/coreaudio_dev.m 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia-audiodev/coreaudio_dev.m 2021-02-06 22:51:16.641714862 +0100 @@ -173,6 +173,11 @@ void *user_data, pjmedia_aud_stream **p_aud_strm); +static void ca_factory_set_observer(pjmedia_aud_dev_factory *f, + pjmedia_aud_dev_change_callback cb); +static int ca_factory_get_default_rec_dev(pjmedia_aud_dev_factory *f); +static int ca_factory_get_default_play_dev(pjmedia_aud_dev_factory *f); + static pj_status_t ca_stream_get_param(pjmedia_aud_stream *strm, pjmedia_aud_param *param); static pj_status_t ca_stream_get_cap(pjmedia_aud_stream *strm, @@ -206,7 +211,10 @@ &ca_factory_get_dev_info, &ca_factory_default_param, &ca_factory_create_stream, - &ca_factory_refresh + &ca_factory_refresh, + &ca_factory_set_observer, + &ca_factory_get_default_rec_dev, + &ca_factory_get_default_play_dev }; static pjmedia_aud_stream_op stream_op = @@ -717,6 +725,169 @@ return PJ_SUCCESS; } +static OSStatus property_listener_proc(AudioObjectID objectID, + UInt32 numberAddresses, + const AudioObjectPropertyAddress inAddresses[], + void *clientData) +{ + pjmedia_aud_dev_change_callback cb = (pjmedia_aud_dev_change_callback)clientData; + pjmedia_aud_dev_change_event event; + UInt32 i; + + for(i = 0; i < numberAddresses; i++) { + event = 0; + switch (inAddresses[i].mSelector) { + case kAudioHardwarePropertyDefaultInputDevice: + event = DEFAULT_INPUT_CHANGED; + break; + case kAudioHardwarePropertyDefaultOutputDevice: + event = DEFAULT_OUTPUT_CHANGED; + break; + case kAudioHardwarePropertyDevices: + event = DEVICE_LIST_CHANGED; + break; + default: + break; + } + if (event > 0) { + (cb)(event); + } + } + + return noErr; +} + +/* API: set audio device change observer */ +static void ca_factory_set_observer(pjmedia_aud_dev_factory *f, + pjmedia_aud_dev_change_callback cb) +{ + AudioObjectPropertyAddress addr; + OSStatus ostatus; + + /* observer for devices list */ + addr.mSelector = kAudioHardwarePropertyDevices; + addr.mScope = kAudioObjectPropertyScopeGlobal; + addr.mElement = kAudioObjectPropertyElementMaster; + + if (cb) { + ostatus = AudioObjectAddPropertyListener(kAudioObjectSystemObject, + &addr, + property_listener_proc, + cb); + } else { + ostatus = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, + &addr, + property_listener_proc, + cb); + } + if (ostatus != noErr) { + PJ_LOG(5,(THIS_FILE, "Error %sregistering devices list observer", cb==NULL ? "un-" : "")); + } + + /* observer for default input device */ + addr.mSelector = kAudioHardwarePropertyDefaultInputDevice; + + if (cb) { + ostatus = AudioObjectAddPropertyListener(kAudioObjectSystemObject, + &addr, + property_listener_proc, + cb); + } else { + ostatus = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, + &addr, + property_listener_proc, + cb); + } + if (ostatus != noErr) { + PJ_LOG(5,(THIS_FILE, "Error %sregistering default input device observer", cb==NULL ? "un-" : "")); + } + + /* observer for default output device */ + addr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + + if (cb) { + ostatus = AudioObjectAddPropertyListener(kAudioObjectSystemObject, + &addr, + property_listener_proc, + cb); + } else { + ostatus = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, + &addr, + property_listener_proc, + cb); + } + if (ostatus != noErr) { + PJ_LOG(5,(THIS_FILE, "Error %sregistering default output device observer", cb==NULL ? "un-" : "")); + } + +} + +/* API: get default recording device */ +static int ca_factory_get_default_rec_dev(pjmedia_aud_dev_factory *f) +{ + AudioDeviceID dev_id = kAudioObjectUnknown; + AudioObjectPropertyAddress addr; + UInt32 size; + OSStatus ostatus; + int i; + int idx = -1; + struct coreaudio_factory *cf = (struct coreaudio_factory*)f; + + /* Find default audio input device */ + addr.mSelector = kAudioHardwarePropertyDefaultInputDevice; + addr.mScope = kAudioObjectPropertyScopeGlobal; + addr.mElement = kAudioObjectPropertyElementMaster; + size = sizeof(dev_id); + + ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &addr, 0, NULL, + &size, (void *)&dev_id); + if (ostatus == noErr) { + for (i = 0; i < cf->dev_count; i++) { + struct coreaudio_dev_info *cdi; + cdi = &cf->dev_info[i]; + if (cdi->dev_id == dev_id) { + idx = i; + break; + } + } + } + return idx; +} + +/* API: get default playback device */ +static int ca_factory_get_default_play_dev(pjmedia_aud_dev_factory *f) +{ + AudioDeviceID dev_id = kAudioObjectUnknown; + AudioObjectPropertyAddress addr; + UInt32 size; + OSStatus ostatus; + int i; + int idx = -1; + struct coreaudio_factory *cf = (struct coreaudio_factory*)f; + + /* Find default audio output device */ + addr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + addr.mScope = kAudioObjectPropertyScopeGlobal; + addr.mElement = kAudioObjectPropertyElementMaster; + size = sizeof(dev_id); + + ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &addr, 0, NULL, + &size, (void *)&dev_id); + if (ostatus == noErr) { + for (i = 0; i < cf->dev_count; i++) { + struct coreaudio_dev_info *cdi; + cdi = &cf->dev_info[i]; + if (cdi->dev_id == dev_id) { + idx = i; + break; + } + } + } + return idx; +} + OSStatus resampleProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, @@ -1862,7 +2033,6 @@ { strm->param.input_latency_ms = (latency + latency2) * 1000 / strm->param.clock_rate; - strm->param.input_latency_ms++; } } #else @@ -1870,7 +2040,6 @@ strm->param.input_latency_ms = (unsigned)(([strm->sess inputLatency] + [strm->sess IOBufferDuration]) * 1000); - strm->param.input_latency_ms++; } else return PJMEDIA_EAUD_INVCAP; #endif @@ -1903,7 +2072,6 @@ { strm->param.output_latency_ms = (latency + latency2) * 1000 / strm->param.clock_rate; - strm->param.output_latency_ms++; } } #else @@ -1911,11 +2079,10 @@ strm->param.output_latency_ms = (unsigned)(([strm->sess outputLatency] + [strm->sess IOBufferDuration]) * 1000); - strm->param.output_latency_ms++; } else return PJMEDIA_EAUD_INVCAP; #endif - *(unsigned*)pval = (++strm->param.output_latency_ms * 2); + *(unsigned*)pval = strm->param.output_latency_ms; return PJ_SUCCESS; } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING && (strm->param.dir & PJMEDIA_DIR_PLAYBACK)) diff -ruN pjproject-2.10/pjmedia/src/pjmedia-audiodev/wmme_dev.c pjsip/pjmedia/src/pjmedia-audiodev/wmme_dev.c --- pjproject-2.10/pjmedia/src/pjmedia-audiodev/wmme_dev.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia-audiodev/wmme_dev.c 2021-02-06 22:51:16.641714862 +0100 @@ -32,6 +32,7 @@ #endif #include +#include #include #include @@ -69,6 +70,15 @@ #define THIS_FILE "wmme_dev.c" +/* WMME device change observer */ +struct wmme_dev_observer +{ + pj_thread_t *thread; + pj_pool_t *pool; + pjmedia_aud_dev_change_callback cb; + HWND hWnd; +}; + /* WMME device info */ struct wmme_dev_info { @@ -87,6 +97,8 @@ unsigned dev_count; struct wmme_dev_info *dev_info; + + struct wmme_dev_observer dev_observer; }; @@ -151,6 +163,11 @@ pjmedia_aud_play_cb play_cb, void *user_data, pjmedia_aud_stream **p_aud_strm); +static void factory_set_observer(pjmedia_aud_dev_factory *f, + pjmedia_aud_dev_change_callback cb); +static int factory_get_default_rec_dev(pjmedia_aud_dev_factory *f); +static int factory_get_default_play_dev(pjmedia_aud_dev_factory *f); + static pj_status_t stream_get_param(pjmedia_aud_stream *strm, pjmedia_aud_param *param); @@ -174,7 +191,10 @@ &factory_get_dev_info, &factory_default_param, &factory_create_stream, - &factory_refresh + &factory_refresh, + &factory_set_observer, + &factory_get_default_rec_dev, + &factory_get_default_play_dev }; static pjmedia_aud_stream_op stream_op = @@ -1336,6 +1356,201 @@ return PJ_SUCCESS; } +/* Processes OS messages arriving at the hWnd window */ +INT_PTR WINAPI ProcessOSMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + /* wf is used in order to query the number of audio devices currently handled */ + static struct wmme_factory *wf = NULL; + + switch( message ) + { + case WM_CREATE: + /* Initialize wf pointer on the first run */ + if (wf == NULL) + { + CREATESTRUCT *CrtStrPtr = (CREATESTRUCT *) lParam; + wf = (struct wmme_factory *)(CrtStrPtr->lpCreateParams); + } + break; + case WM_DEVICECHANGE: + /* Possible insertion or removal of device. There's some issues: + + - Some devices/drivers does not trigger arrival nor + removecomplete events, but only devnodes_changed events. + Therefore, we process all of those type of events. + + - Some hardware can send many devnodes_changed events at the + same time (up to ~15 of such events). These batches are + detected using temporal locality, using constMaxBatchPeriod_. + Once the device is detected, the rest of redundant events + are discarded. In order to know if there's a new device or not, + actual audio devices count is compared to stored audio devices + count (via wf->dev_count). + + - Hardware takes some time to settle and be recognized by + drivers. A small window of time is given in order to account + for this (constMaxSettleTime_); + + Settle time should be slightly lower than batch period. + */ + if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE || wParam == DBT_DEVNODES_CHANGED) { + const int constMaxBatchPeriod_ = 3; /* seconds */ + const int constMaxSettleTime_ = (constMaxBatchPeriod_ * 1000) - 500; /* milliseconds */ + + /* Loop that allows hardware to settle */ + int settleTimeLeft = constMaxSettleTime_; + while (settleTimeLeft > 0) { + /* Check if actual devices lists (I/O) sizes have actually + changed before notifying upper levels. Consider input + devices, output devices and a WAVE MAPPER device for each. + */ + if(waveInGetNumDevs() + waveOutGetNumDevs() + 2 != wf->dev_count) { + /* Hardware changed */ + if (wf->dev_observer.cb) { + wf->dev_observer.cb(DEVICE_LIST_CHANGED); + } + break; + } else { + /* Hardware is settling... */ + Sleep(250); + settleTimeLeft -= 250; + } + } + } + break; + case WM_CLOSE: + if (!DestroyWindow(hWnd)) { + PJ_LOG(4,(THIS_FILE, "Couldn't destroy message window")); + } + break; + case WM_DESTROY: + PostQuitMessage(0); + break; + default: + break; + } + + return 1; +} + +static pj_status_t create_os_messages_window(struct wmme_factory *wf) +{ + pj_status_t status = PJ_EBUG; + WNDCLASSEX wndClass; + HWND hWnd; + + /* Set up and register window class */ + ZeroMemory(&wndClass, sizeof(WNDCLASSEX)); + wndClass.cbSize = sizeof(WNDCLASSEX); + wndClass.style = CS_OWNDC; + wndClass.lpfnWndProc = (WNDPROC)(ProcessOSMessage); + wndClass.hInstance = (HINSTANCE)(GetModuleHandle(0)); + wndClass.lpszClassName = "DeviceChangeMessageWindow"; + + if (RegisterClassEx(&wndClass)) { + /* Create the window that will receive OS messages */ + hWnd = CreateWindowEx( 0, "DeviceChangeMessageWindow", NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, (LPVOID)(wf)); + if (hWnd != NULL) { + wf->dev_observer.hWnd = hWnd; + if (UpdateWindow(hWnd) != 0) { + status = PJ_SUCCESS; + } + } else { + PJ_LOG(4,(THIS_FILE, "Error creating window to receive device change events")); + } + } + + return status; + +} + +static pj_status_t dispatch_os_messages(void) +{ + pj_status_t status = PJ_SUCCESS; + MSG msg; + int ret; + + /* Process OS messages with low cpu-usage wait loop */ + while((ret = GetMessage(&msg, NULL, 0, 0)) != 0) { + if (ret == -1) { + PJ_LOG(4,(THIS_FILE, "Couldn't process OS message")); + status = PJ_EBUG; + break; + } else { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + return status; + +} + +/* WMME device observer thread thread. */ +static int PJ_THREAD_FUNC wmme_dev_observer_thread(void *arg) +{ + struct wmme_factory *wf = (struct wmme_factory*)arg; + pj_status_t status; + + status = create_os_messages_window(wf); + if (status == PJ_SUCCESS) { + status = dispatch_os_messages(); + if (status != PJ_SUCCESS) { + PJ_LOG(4,(THIS_FILE, "Error dispatching device detection window events")); + } + } else { + PJ_LOG(4,(THIS_FILE, "Failed to create window for receiving device detection events")); + } + + return status; +} + +/* API: set audio device change observer */ +static void factory_set_observer(pjmedia_aud_dev_factory *f, + pjmedia_aud_dev_change_callback cb) +{ + struct wmme_factory *wf = (struct wmme_factory*)f; + pj_pool_t *pool; + pj_status_t status; + + if (cb) { + pool = pj_pool_create(wf->pf, "wmme-dev-observer", 1000, 1000, NULL); + PJ_ASSERT_ON_FAIL(pool != NULL, {return;}); + status = pj_thread_create(pool, "wmme_observer", &wmme_dev_observer_thread, wf, 0, 0, &wf->dev_observer.thread); + if (status != PJ_SUCCESS) { + PJ_LOG(4,(THIS_FILE, "Failed to create WMME device detection thread")); + wf->dev_observer.thread = NULL; + return; + } + wf->dev_observer.cb = cb; + } else { + wf->dev_observer.cb = NULL; + if (wf->dev_observer.hWnd) { + CloseWindow(wf->dev_observer.hWnd); + wf->dev_observer.hWnd = NULL; + } + pj_thread_join(wf->dev_observer.thread); + pj_thread_destroy(wf->dev_observer.thread); + wf->dev_observer.thread = NULL; + } +} + +/* API: get default recording device */ +static int factory_get_default_rec_dev(pjmedia_aud_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + /* Let PJMEDIA pick the first one available */ + return -1; +} + +/* API: get default playback device */ +static int factory_get_default_play_dev(pjmedia_aud_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + /* Let PJMEDIA pick the first one available */ + return -1; +} + /* API: Get stream info. */ static pj_status_t stream_get_param(pjmedia_aud_stream *s, pjmedia_aud_param *pi) --- pjproject-2.10/pjmedia/src/pjmedia-codec/opus.c 2021-03-19 04:55:21.491267756 +0100 +++ pjsip/pjmedia/src/pjmedia-codec/opus.c 2021-03-19 05:48:19.457507442 +0100 @@ -542,7 +542,7 @@ static pj_status_t factory_enum_codecs( pjmedia_codec_factory *factory, */ codecs[0].encoding_name = pj_str("opus"); codecs[0].clock_rate = 48000; - codecs[0].channel_cnt = 2; + codecs[0].channel_cnt = 1; *count = 1; } diff -ruN pjproject-2.10/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c pjsip/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c --- pjproject-2.10/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c 2021-02-06 22:51:16.641714862 +0100 @@ -62,16 +62,7 @@ #endif #if LIBAVCODEC_VER_AT_LEAST(53,61) -# if LIBAVCODEC_VER_AT_LEAST(54,59) - /* Not sure when AVCodec::encode is obsoleted/removed. */ -# define AVCODEC_HAS_ENCODE(c) (c->encode2) -# else - /* Not sure when AVCodec::encode2 is introduced. It appears in - * libavcodec 53.61 where some codecs actually still use AVCodec::encode - * (e.g: H263, H264). - */ -# define AVCODEC_HAS_ENCODE(c) (c->encode || c->encode2) -# endif +# define AVCODEC_HAS_ENCODE(c) (c->encode2) # define AV_OPT_SET(obj,name,val,opt) (av_opt_set(obj,name,val,opt)==0) # define AV_OPT_SET_INT(obj,name,val) (av_opt_set_int(obj,name,val,0)==0) #else @@ -266,6 +257,7 @@ /* H264 constants */ #define PROFILE_H264_BASELINE 66 #define PROFILE_H264_MAIN 77 +#define PROFILE_H264_HIGH 100 /* Codec specific functions */ #if PJMEDIA_HAS_FFMPEG_CODEC_H264 @@ -404,6 +396,9 @@ case PROFILE_H264_MAIN: profile = "main"; break; + case PROFILE_H264_HIGH: + profile = "high"; + break; default: break; } @@ -440,11 +435,11 @@ /* Misc x264 settings (performance, quality, latency, etc). * Let's just use the x264 predefined preset & tune. */ - if (!AV_OPT_SET(ctx->priv_data, "preset", "veryfast", 0)) { + if (!AV_OPT_SET(ctx->priv_data, "preset", "ultrafast", 0)) { PJ_LOG(3, (THIS_FILE, "Failed to set x264 preset 'veryfast'")); } - if (!AV_OPT_SET(ctx->priv_data, "tune", "animation+zerolatency", 0)) { - PJ_LOG(3, (THIS_FILE, "Failed to set x264 tune 'zerolatency'")); + if (!AV_OPT_SET(ctx->priv_data, "tune", "fastdecode+zerolatency", 0)) { + PJ_LOG(3, (THIS_FILE, "Failed to set x264 tune 'fastdecode+zerolatency'")); } } @@ -1430,12 +1425,12 @@ /* Check if encoder has been opened */ PJ_ASSERT_RETURN(ff->enc_ctx, PJ_EINVALIDOP); -#ifdef PJMEDIA_USE_OLD_FFMPEG - avcodec_get_frame_defaults(&avframe); -#else +//#ifdef PJMEDIA_USE_OLD_FFMPEG +// avcodec_get_frame_defaults(&avframe); +//#else pj_bzero(&avframe, sizeof(avframe)); av_frame_unref(&avframe); -#endif +//#endif // Let ffmpeg manage the timestamps /* @@ -1692,12 +1687,12 @@ * whole decoding session, and seems to be freed when the codec context * closed). */ -#ifdef PJMEDIA_USE_OLD_FFMPEG - avcodec_get_frame_defaults(&avframe); -#else +//#ifdef PJMEDIA_USE_OLD_FFMPEG +// avcodec_get_frame_defaults(&avframe); +//#else pj_bzero(&avframe, sizeof(avframe)); av_frame_unref(&avframe); -#endif +//#endif /* Init packet, the container of the encoded data */ av_init_packet(&avpacket); @@ -1705,12 +1700,12 @@ avpacket.size = (int)input->size; /* ffmpeg warns: - * - input buffer padding, at least FF_INPUT_BUFFER_PADDING_SIZE + * - input buffer padding, at least AV_INPUT_BUFFER_PADDING_SIZE * - null terminated * Normally, encoded buffer is allocated more than needed, so lets just * bzero the input buffer end/pad, hope it will be just fine. */ - pj_bzero(avpacket.data+avpacket.size, FF_INPUT_BUFFER_PADDING_SIZE); + pj_bzero(avpacket.data+avpacket.size, AV_INPUT_BUFFER_PADDING_SIZE); output->bit_info = 0; output->timestamp = input->timestamp; diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/avf_dev.m pjsip/pjmedia/src/pjmedia-videodev/avf_dev.m --- pjproject-2.10/pjmedia/src/pjmedia-videodev/avf_dev.m 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia-videodev/avf_dev.m 2021-02-06 18:46:39.045322874 +0100 @@ -0,0 +1,682 @@ +/* + * Copyright (C) 2014-present AG Projects (http://ag-projects.com) + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +#if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \ + defined(PJMEDIA_VIDEO_DEV_HAS_AVF) && PJMEDIA_VIDEO_DEV_HAS_AVF != 0 + +#include +#include +#include +#include + +#define THIS_FILE "avf_dev.c" +#define DEFAULT_CLOCK_RATE 90000 +#define DEFAULT_WIDTH 640 +#define DEFAULT_HEIGHT 480 +#define DEFAULT_FPS 15 + + +typedef struct avf_fmt_info +{ + pjmedia_format_id pjmedia_format; + unsigned avf_format; +} avf_fmt_info; + +static avf_fmt_info avf_fmts[] = +{ + {PJMEDIA_FORMAT_BGRA, kCVPixelFormatType_32BGRA}, + {PJMEDIA_FORMAT_YUY2, kCVPixelFormatType_422YpCbCr8_yuvs}, + {PJMEDIA_FORMAT_UYVY, kCVPixelFormatType_422YpCbCr8}, +}; + +/* avf device info */ +struct avf_dev_info +{ + pjmedia_vid_dev_info info; + AVCaptureDevice *dev; +}; + +/* avf factory */ +struct avf_factory +{ + pjmedia_vid_dev_factory base; + pj_pool_t *pool; + pj_pool_t *dev_pool; + pj_pool_factory *pf; + + unsigned dev_count; + struct avf_dev_info *dev_info; +}; + +struct avf_stream; /* forward declaration */ +typedef void (*func_ptr)(struct avf_stream *strm); + +@interface AVFDelegate: NSObject +{ +@public + struct avf_stream *stream; +} +@end + + +/* Video stream. */ +struct avf_stream +{ + pjmedia_vid_dev_stream base; /**< Base stream */ + pjmedia_vid_dev_param param; /**< Settings */ + pj_pool_t *pool; /**< Memory pool. */ + + pj_timestamp cap_frame_ts; /**< Captured frame tstamp */ + unsigned cap_ts_inc; /**< Increment */ + + pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */ + void *user_data; /**< Application data. */ + + pjmedia_rect_size size; + + pj_bool_t cap_thread_initialized; + pj_thread_desc cap_thread_desc; + pj_thread_t *cap_thread; + pj_bool_t cap_exited; + + struct avf_factory *af; + pj_status_t status; + pj_bool_t is_running; + + dispatch_queue_t video_ops_queue; + + AVCaptureSession *cap_session; + AVCaptureDeviceInput *dev_input; + AVCaptureVideoDataOutput *video_output; + AVFDelegate *delegate; +}; + + +/* Prototypes */ +static pj_status_t avf_factory_init(pjmedia_vid_dev_factory *f); +static pj_status_t avf_factory_destroy(pjmedia_vid_dev_factory *f); +static pj_status_t avf_factory_refresh(pjmedia_vid_dev_factory *f); +static unsigned avf_factory_get_dev_count(pjmedia_vid_dev_factory *f); +static pj_status_t avf_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info); +static pj_status_t avf_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param); +static pj_status_t avf_factory_create_stream(pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm); + +static pj_status_t avf_stream_get_param(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_param *param); +static pj_status_t avf_stream_get_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + void *value); +static pj_status_t avf_stream_set_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + const void *value); +static pj_status_t avf_stream_start(pjmedia_vid_dev_stream *strm); +static pj_status_t avf_stream_stop(pjmedia_vid_dev_stream *strm); +static pj_status_t avf_stream_destroy(pjmedia_vid_dev_stream *strm); + +/* Operations */ +static pjmedia_vid_dev_factory_op factory_op = +{ + &avf_factory_init, + &avf_factory_destroy, + &avf_factory_get_dev_count, + &avf_factory_get_dev_info, + &avf_factory_default_param, + &avf_factory_create_stream, + &avf_factory_refresh +}; + +static pjmedia_vid_dev_stream_op stream_op = +{ + &avf_stream_get_param, + &avf_stream_get_cap, + &avf_stream_set_cap, + &avf_stream_start, + NULL, + NULL, + &avf_stream_stop, + &avf_stream_destroy +}; + + +/**************************************************************************** + * Factory operations + */ +/* + * Init avf video driver. + */ +pjmedia_vid_dev_factory* pjmedia_avf_factory(pj_pool_factory *pf) +{ + struct avf_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "avf video", 4000, 4000, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct avf_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + + +/* API: init factory */ +static pj_status_t avf_factory_init(pjmedia_vid_dev_factory *f) +{ + return avf_factory_refresh(f); +} + +/* API: destroy factory */ +static pj_status_t avf_factory_destroy(pjmedia_vid_dev_factory *f) +{ + struct avf_factory *af = (struct avf_factory*)f; + pj_pool_t *pool = af->pool; + + if (af->dev_pool) + pj_pool_release(af->dev_pool); + af->pool = NULL; + if (pool) + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +/* API: refresh the list of devices */ +static pj_status_t avf_factory_refresh(pjmedia_vid_dev_factory *f) +{ + struct avf_factory *af = (struct avf_factory*)f; + struct avf_dev_info *di; + unsigned dev_count = 0; + NSAutoreleasePool *apool = [[NSAutoreleasePool alloc]init]; + NSArray *dev_array; + + if (af->dev_pool) { + pj_pool_release(af->dev_pool); + af->dev_pool = NULL; + } + + dev_array = [AVCaptureDevice devices]; + for (AVCaptureDevice *device in dev_array) { + if ([device hasMediaType:AVMediaTypeVideo] && ![device isSuspended]) { + dev_count++; + } + } + + /* Initialize input and output devices here */ + af->dev_count = 0; + af->dev_pool = pj_pool_create(af->pf, "avf video", 500, 500, NULL); + + af->dev_info = (struct avf_dev_info*) pj_pool_calloc(af->dev_pool, dev_count, sizeof(struct avf_dev_info)); + for (AVCaptureDevice *device in dev_array) { + if (![device hasMediaType:AVMediaTypeVideo] || [device isSuspended]) { + continue; + } + + di = &af->dev_info[af->dev_count++]; + pj_bzero(di, sizeof(*di)); + di->dev = device; + pj_ansi_strncpy(di->info.name, [device.localizedName UTF8String], sizeof(di->info.name)); + pj_ansi_strncpy(di->info.driver, "AVF", sizeof(di->info.driver)); + di->info.dir = PJMEDIA_DIR_CAPTURE; + di->info.has_callback = PJ_TRUE; + di->info.fmt_cnt = 0; + di->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT; + + PJ_LOG(4, (THIS_FILE, " dev: %s", di->info.name)); + + for (AVCaptureDeviceFormat* f in [device formats]) { + unsigned i; + CMFormatDescriptionRef desc = [f formatDescription]; + for (i = 0; i < PJ_ARRAY_SIZE(avf_fmts); i++) { + if (CMFormatDescriptionGetMediaSubType(desc) == avf_fmts[i].avf_format) { + char fmt_name[5]; + CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(desc); + if (dim.width < 640) + continue; + pjmedia_fourcc_name(avf_fmts[i].pjmedia_format, fmt_name); + PJ_LOG(4, (THIS_FILE, " detected resolution %dx%d (%s)", dim.width, dim.height, fmt_name)); + pjmedia_format *fmt = &di->info.fmt[di->info.fmt_cnt++]; + pjmedia_format_init_video(fmt, + avf_fmts[i].pjmedia_format, + dim.width, + dim.height, + DEFAULT_FPS, 1); + } + } + } + + if (di->info.fmt_cnt == 0) { + PJ_LOG(4, (THIS_FILE, " there are no compatible formats, using default")); + pjmedia_format *fmt = &di->info.fmt[di->info.fmt_cnt++]; + pjmedia_format_init_video(fmt, + avf_fmts[0].pjmedia_format, + DEFAULT_WIDTH, + DEFAULT_HEIGHT, + DEFAULT_FPS, 1); + } + } + + [apool release]; + + PJ_LOG(4, (THIS_FILE, "avf video has %d devices", af->dev_count)); + + return PJ_SUCCESS; +} + +/* API: get number of devices */ +static unsigned avf_factory_get_dev_count(pjmedia_vid_dev_factory *f) +{ + struct avf_factory *af = (struct avf_factory*)f; + return af->dev_count; +} + +/* API: get device info */ +static pj_status_t avf_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info) +{ + struct avf_factory *af = (struct avf_factory*)f; + PJ_ASSERT_RETURN(index < af->dev_count, PJMEDIA_EVID_INVDEV); + + pj_memcpy(info, &af->dev_info[index].info, sizeof(*info)); + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t avf_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param) +{ + struct avf_factory *af = (struct avf_factory*)f; + struct avf_dev_info *di = &af->dev_info[index]; + + PJ_ASSERT_RETURN(index < af->dev_count, PJMEDIA_EVID_INVDEV); + PJ_UNUSED_ARG(pool); + + pj_bzero(param, sizeof(*param)); + param->dir = PJMEDIA_DIR_CAPTURE; + param->cap_id = index; + param->rend_id = PJMEDIA_VID_INVALID_DEV; + param->flags = PJMEDIA_VID_DEV_CAP_FORMAT; + param->clock_rate = DEFAULT_CLOCK_RATE; + pj_memcpy(¶m->fmt, &di->info.fmt[0], sizeof(param->fmt)); + + return PJ_SUCCESS; +} + +static avf_fmt_info* get_avf_format_info(pjmedia_format_id id) +{ + unsigned i; + + for (i = 0; i < PJ_ARRAY_SIZE(avf_fmts); i++) { + if (avf_fmts[i].pjmedia_format == id) + return &avf_fmts[i]; + } + + return NULL; +} + + +@implementation AVFDelegate +- (void)captureOutput:(AVCaptureOutput *)captureOutput + didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)connection +{ + pjmedia_frame frame = {0}; + CVImageBufferRef img; + CVReturn ret; + OSType type; + size_t width, height; + + /* Register thread if needed */ + if (stream->cap_thread_initialized == 0 || !pj_thread_is_registered()) { + pj_bzero(stream->cap_thread_desc, sizeof(pj_thread_desc)); + pj_thread_register("avf_cap", stream->cap_thread_desc, &stream->cap_thread); + stream->cap_thread_initialized = 1; + } + + if (!sampleBuffer) + return; + + /* Get a CMSampleBuffer's Core Video image buffer for the media data */ + img = CMSampleBufferGetImageBuffer(sampleBuffer); + if (!img) + return; + + /* Check for supported formats */ + type = CVPixelBufferGetPixelFormatType(img); + switch(type) { + case kCVPixelFormatType_32BGRA: + case kCVPixelFormatType_422YpCbCr8_yuvs: + case kCVPixelFormatType_422YpCbCr8: + break; + default: + PJ_LOG(4, (THIS_FILE, "Unsupported image format! %c%c%c%c", type>>24, type>>16, type>>8, type>>0)); + return; + } + + /* Lock the base address of the pixel buffer */ + ret = CVPixelBufferLockBaseAddress(img, kCVPixelBufferLock_ReadOnly); + if (ret != kCVReturnSuccess) + return; + + width = CVPixelBufferGetWidth(img); + height = CVPixelBufferGetHeight(img); + + /* Prepare frame */ + frame.type = PJMEDIA_FRAME_TYPE_VIDEO; + frame.timestamp.u64 = stream->cap_frame_ts.u64; + frame.buf = CVPixelBufferGetBaseAddress(img); + frame.size = CVPixelBufferGetBytesPerRow(img) * height; + + if (stream->size.w != width || stream->size.h != height) { + PJ_LOG(4, (THIS_FILE, "AVF image size changed, before: %dx%d, after: %dx%d", stream->size.w, stream->size.h, width, height)); + } + + if (stream->vid_cb.capture_cb) { + (*stream->vid_cb.capture_cb)(&stream->base, stream->user_data, &frame); + } + + stream->cap_frame_ts.u64 += stream->cap_ts_inc; + + /* Unlock the pixel buffer */ + CVPixelBufferUnlockBaseAddress(img, kCVPixelBufferLock_ReadOnly); +} +@end + + +static void init_avf_stream(struct avf_stream *strm) +{ + const pjmedia_video_format_info *vfi; + pjmedia_video_format_detail *vfd; + avf_fmt_info *fi = get_avf_format_info(strm->param.fmt.id); + NSError *error; + pj_status_t status; + + if (!fi) { + strm->status = PJMEDIA_EVID_BADFORMAT; + return; + } + + strm->cap_session = [[AVCaptureSession alloc] init]; + if (!strm->cap_session) { + strm->status = PJ_ENOMEM; + return; + } + + strm->cap_session.sessionPreset = AVCaptureSessionPresetHigh; + vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE); + pj_assert(vfd); + vfi = pjmedia_get_video_format_info(NULL, strm->param.fmt.id); + pj_assert(vfi); + vfd->size = strm->size; + + PJ_LOG(4, (THIS_FILE, "Opening video device at %dx%d resolution", vfd->size.w, vfd->size.h)); + + /* Add the video device to the session as a device input */ + AVCaptureDevice *videoDevice = strm->af->dev_info[strm->param.cap_id].dev; + strm->dev_input = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error: &error]; + if (!strm->dev_input) { + status = PJMEDIA_EVID_SYSERR; + return; + } + + [strm->cap_session addInput:strm->dev_input]; + + strm->video_output = [[AVCaptureVideoDataOutput alloc] init]; + if (!strm->video_output) { + status = PJMEDIA_EVID_SYSERR; + return; + } + [strm->cap_session addOutput:strm->video_output]; + + /* Configure the video output */ + strm->video_output.alwaysDiscardsLateVideoFrames = YES; + /* The Apple provided documentation says the only supported key is kCVPixelBufferPixelFormatTypeKey, + * but it turns out kCVPixelBufferWidthKey and kCVPixelBufferHeightKey are also required. Thanks + * Chromium, for figuring it out.*/ + strm->video_output.videoSettings = + [NSDictionary dictionaryWithObjectsAndKeys: @(fi->avf_format), + kCVPixelBufferPixelFormatTypeKey, + @(vfd->size.w), + kCVPixelBufferWidthKey, + @(vfd->size.h), + kCVPixelBufferHeightKey, + nil]; + strm->delegate = [[AVFDelegate alloc] init]; + strm->delegate->stream = strm; + dispatch_queue_t queue = dispatch_queue_create("AVFQueue", NULL); + [strm->video_output setSampleBufferDelegate:strm->delegate queue:queue]; + dispatch_release(queue); +} + +static void run_func_on_video_queue(struct avf_stream *strm, func_ptr func) +{ + dispatch_sync(strm->video_ops_queue, ^{ + (*func)(strm); + }); +} + +/* API: create stream */ +static pj_status_t avf_factory_create_stream(pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm) +{ + struct avf_factory *af = (struct avf_factory*)f; + pj_pool_t *pool; + struct avf_stream *strm; + const pjmedia_video_format_info *vfi; + pjmedia_video_format_detail *vfd; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL); + PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO && + param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO && + param->dir == PJMEDIA_DIR_CAPTURE, + PJ_EINVAL); + + vfi = pjmedia_get_video_format_info(NULL, param->fmt.id); + if (!vfi) + return PJMEDIA_EVID_BADFORMAT; + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(af->pf, "avf-dev", 4000, 4000, NULL); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct avf_stream); + pj_memcpy(&strm->param, param, sizeof(*param)); + strm->pool = pool; + pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); + strm->user_data = user_data; + strm->af = af; + + vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE); + pj_memcpy(&strm->size, &vfd->size, sizeof(vfd->size)); + pj_assert(vfd->fps.num); + strm->cap_ts_inc = PJMEDIA_SPF2(strm->param.clock_rate, &vfd->fps, 1); + + /* Create dispatch queue */ + strm->video_ops_queue = dispatch_queue_create("AVF Video Ops", DISPATCH_QUEUE_SERIAL); + + /* Create capture stream here */ + strm->status = PJ_SUCCESS; + run_func_on_video_queue(strm, init_avf_stream); + status = strm->status; + if (status != PJ_SUCCESS) { + dispatch_release(strm->video_ops_queue); + avf_stream_destroy((pjmedia_vid_dev_stream *)strm); + return status; + } + + /* Update param as output */ + param->fmt = strm->param.fmt; + + /* Done */ + strm->base.op = &stream_op; + *p_vid_strm = &strm->base; + + return PJ_SUCCESS; +} + +/* API: Get stream info. */ +static pj_status_t avf_stream_get_param(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_param *pi) +{ + struct avf_stream *strm = (struct avf_stream*)s; + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t avf_stream_get_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + void *pval) +{ + struct avf_stream *strm = (struct avf_stream*)s; + + PJ_UNUSED_ARG(strm); + PJ_UNUSED_ARG(cap); + PJ_UNUSED_ARG(pval); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + return PJMEDIA_EVID_INVCAP; +} + +/* API: set capability */ +static pj_status_t avf_stream_set_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + const void *pval) +{ + struct avf_stream *strm = (struct avf_stream*)s; + + PJ_UNUSED_ARG(strm); + PJ_UNUSED_ARG(cap); + PJ_UNUSED_ARG(pval); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + return PJMEDIA_EVID_INVCAP; +} + +static void start_avf(struct avf_stream *strm) +{ + [strm->cap_session startRunning]; +} + +static void stop_avf(struct avf_stream *strm) +{ + [strm->cap_session stopRunning]; +} + +/* API: Start stream. */ +static pj_status_t avf_stream_start(pjmedia_vid_dev_stream *strm) +{ + struct avf_stream *stream = (struct avf_stream*)strm; + + PJ_LOG(4, (THIS_FILE, "Starting avf video stream")); + + if (stream->cap_session) { + run_func_on_video_queue(stream, start_avf); + if (![stream->cap_session isRunning]) + return PJMEDIA_EVID_NOTREADY; + stream->is_running = PJ_TRUE; + } + + return PJ_SUCCESS; +} + +/* API: Stop stream. */ +static pj_status_t avf_stream_stop(pjmedia_vid_dev_stream *strm) +{ + struct avf_stream *stream = (struct avf_stream*)strm; + + PJ_LOG(4, (THIS_FILE, "Stopping avf video stream")); + + if (stream->cap_session && [stream->cap_session isRunning]) { + int i; + stream->cap_exited = PJ_FALSE; + run_func_on_video_queue(stream, stop_avf); + stream->is_running = PJ_FALSE; + for (i = 50; i >= 0 && !stream->cap_exited; i--) { + pj_thread_sleep(10); + } + } + + return PJ_SUCCESS; +} + +static void destroy_avf(struct avf_stream *strm) +{ + if (strm->cap_session) { + [strm->cap_session removeInput:strm->dev_input]; + [strm->cap_session removeOutput:strm->video_output]; + [strm->cap_session release]; + strm->cap_session = NULL; + } + + if (strm->delegate) { + [strm->delegate release]; + strm->delegate = NULL; + } + + if (strm->dev_input) { + strm->dev_input = NULL; + } + if (strm->video_output) { + strm->video_output = NULL; + } +} + +/* API: Destroy stream. */ +static pj_status_t avf_stream_destroy(pjmedia_vid_dev_stream *strm) +{ + struct avf_stream *stream = (struct avf_stream*)strm; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + avf_stream_stop(strm); + run_func_on_video_queue(stream, destroy_avf); + + dispatch_release(stream->video_ops_queue); + pj_pool_release(stream->pool); + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_VIDEO_DEV_HAS_AVF */ diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/dshow_dev.c pjsip/pjmedia/src/pjmedia-videodev/dshow_dev.c --- pjproject-2.10/pjmedia/src/pjmedia-videodev/dshow_dev.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia-videodev/dshow_dev.c 2021-02-06 22:51:16.641714862 +0100 @@ -20,30 +20,24 @@ #include #include #include -#include #if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \ defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0 - -#ifdef _MSC_VER -# pragma warning(push, 3) -#endif - #include #define COBJMACROS #include #include +#include -#ifdef _MSC_VER -# pragma warning(pop) +#ifndef DIBSIZE +# define WIDTHBYTES(BTIS) ((DWORD)(((BTIS)+31) & (~31)) / 8) +# define DIBWIDTHBYTES(BI) (DWORD)(BI).biBitCount) * (DWORD)WIDTHBYTES((DWORD)(BI).biWidth +# define _DIBSIZE(BI) (DIBWIDTHBYTES(BI) * (DWORD)(BI).biHeight) +# define DIBSIZE(BI) ((BI).biHeight < 0 ? (-1)*(_DIBSIZE(BI)) : _DIBSIZE(BI)) #endif -#pragma comment(lib, "Strmiids.lib") -#pragma comment(lib, "Rpcrt4.lib") -#pragma comment(lib, "Quartz.lib") - #define THIS_FILE "dshow_dev.c" #define DEFAULT_CLOCK_RATE 90000 #define DEFAULT_WIDTH 640 @@ -57,10 +51,6 @@ typedef struct NullRenderer NullRenderer; IBaseFilter* NullRenderer_Create(input_callback input_cb, void *user_data); -typedef struct SourceFilter SourceFilter; -IBaseFilter* SourceFilter_Create(SourceFilter **pSrc); -HRESULT SourceFilter_Deliver(SourceFilter *src, void *buf, long size); -void SourceFilter_SetMediaType(SourceFilter *src, AM_MEDIA_TYPE *pmt); typedef struct dshow_fmt_info { @@ -121,7 +111,6 @@ { IFilterGraph *filter_graph; IMediaFilter *media_filter; - SourceFilter *csource_filter; IBaseFilter *source_filter; IBaseFilter *rend_filter; AM_MEDIA_TYPE *mediatype; @@ -160,8 +149,6 @@ pjmedia_vid_dev_cap cap, const void *value); static pj_status_t dshow_stream_start(pjmedia_vid_dev_stream *strm); -static pj_status_t dshow_stream_put_frame(pjmedia_vid_dev_stream *strm, - const pjmedia_frame *frame); static pj_status_t dshow_stream_stop(pjmedia_vid_dev_stream *strm); static pj_status_t dshow_stream_destroy(pjmedia_vid_dev_stream *strm); @@ -184,7 +171,7 @@ &dshow_stream_set_cap, &dshow_stream_start, NULL, - &dshow_stream_put_frame, + NULL, &dshow_stream_stop, &dshow_stream_destroy }; @@ -213,19 +200,12 @@ /* API: init factory */ static pj_status_t dshow_factory_init(pjmedia_vid_dev_factory *f) { - HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); - if (hr == RPC_E_CHANGED_MODE) { - /* When using apartment mode, Dshow object would not be accessible from - * other thread. Take this into consideration when implementing native - * renderer using Dshow. - */ - hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); - if (FAILED(hr)) { - PJ_LOG(4,(THIS_FILE, "Failed initializing DShow: " - "COM library already initialized with " - "incompatible concurrency model")); - return PJMEDIA_EVID_INIT; - } + HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + if (hr == RPC_E_CHANGED_MODE) { + PJ_LOG(4,(THIS_FILE, "Failed initializing DShow: " + "COM library already initialized with " + "incompatible concurrency model")); + return PJMEDIA_EVID_INIT; } return dshow_factory_refresh(f); @@ -459,13 +439,21 @@ if (SUCCEEDED(hr) && var_name.bstrVal) { WCHAR *wszDisplayName = NULL; IBaseFilter *filter; + pj_ssize_t len; ddi = &df->dev_info[df->dev_count++]; pj_bzero(ddi, sizeof(*ddi)); - pj_unicode_to_ansi(var_name.bstrVal, - wcslen(var_name.bstrVal), - ddi->info.name, - sizeof(ddi->info.name)); + + len = wcslen(var_name.bstrVal), + len = WideCharToMultiByte(CP_ACP, + 0, + var_name.bstrVal, + (int)len, + ddi->info.name, + sizeof(ddi->info.name), + NULL, + NULL); + ddi->info.name[len] = '\0'; hr = IMoniker_GetDisplayName(moniker, NULL, NULL, &wszDisplayName); @@ -502,26 +490,6 @@ ICreateDevEnum_Release(dev_enum); } -#if HAS_VMR - ddi = &df->dev_info[df->dev_count++]; - pj_bzero(ddi, sizeof(*ddi)); - pj_ansi_strncpy(ddi->info.name, "Video Mixing Renderer", - sizeof(ddi->info.name)); - ddi->info.name[sizeof(ddi->info.name)-1] = '\0'; - pj_ansi_strncpy(ddi->info.driver, "dshow", sizeof(ddi->info.driver)); - ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0'; - ddi->info.dir = PJMEDIA_DIR_RENDER; - ddi->info.has_callback = PJ_FALSE; - ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT; -// TODO: -// ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW; - - ddi->info.fmt_cnt = 1; - pjmedia_format_init_video(&ddi->info.fmt[0], dshow_fmts[0].pjmedia_format, - DEFAULT_WIDTH, DEFAULT_HEIGHT, - DEFAULT_FPS, 1); -#endif - PJ_LOG(4, (THIS_FILE, "DShow has %d devices:", df->dev_count)); for (c = 0; c < df->dev_count; ++c) { @@ -574,10 +542,6 @@ param->dir = PJMEDIA_DIR_CAPTURE; param->cap_id = index; param->rend_id = PJMEDIA_VID_INVALID_DEV; - } else if (di->info.dir & PJMEDIA_DIR_RENDER) { - param->dir = PJMEDIA_DIR_RENDER; - param->rend_id = index; - param->cap_id = PJMEDIA_VID_INVALID_DEV; } else { return PJMEDIA_EVID_INVDEV; } @@ -645,26 +609,6 @@ (*strm->vid_cb.capture_cb)(&strm->base, strm->user_data, &frame); } -/* API: Put frame from stream */ -static pj_status_t dshow_stream_put_frame(pjmedia_vid_dev_stream *strm, - const pjmedia_frame *frame) -{ - struct dshow_stream *stream = (struct dshow_stream*)strm; - HRESULT hr; - - if (stream->quit_flag) { - stream->rend_thread_exited = PJ_TRUE; - return PJ_SUCCESS; - } - - hr = SourceFilter_Deliver(stream->dgraph.csource_filter, - frame->buf, (long)frame->size); - if (FAILED(hr)) - return hr; - - return PJ_SUCCESS; -} - static dshow_fmt_info* get_dshow_format_info(pjmedia_format_id id) { unsigned i; @@ -689,16 +633,24 @@ IEnumPins *pEnum; IPin *srcpin = NULL; IPin *sinkpin = NULL; - AM_MEDIA_TYPE *mediatype= NULL, mtype; + AM_MEDIA_TYPE *mediatype = NULL; VIDEOINFOHEADER *video_info, *vi = NULL; pjmedia_video_format_detail *vfd; const pjmedia_video_format_info *vfi; + PJ_ASSERT_RETURN(dir == PJMEDIA_DIR_CAPTURE, PJ_EINVAL); + vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(), strm->param.fmt.id); if (!vfi) return PJMEDIA_EVID_BADFORMAT; + hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + if (FAILED(hr)) { + PJ_LOG(4,(THIS_FILE, "Error: CoInitializeEx")); + goto on_error; + } + hr = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC, &IID_IFilterGraph, (LPVOID *)&graph->filter_graph); if (FAILED(hr)) { @@ -711,14 +663,10 @@ goto on_error; } - if (dir == PJMEDIA_DIR_CAPTURE) { hr = get_cap_device(df, id, &graph->source_filter); if (FAILED(hr)) { goto on_error; } - } else { - graph->source_filter = SourceFilter_Create(&graph->csource_filter); - } hr = IFilterGraph_AddFilter(graph->filter_graph, graph->source_filter, L"capture"); @@ -726,16 +674,7 @@ goto on_error; } - if (dir == PJMEDIA_DIR_CAPTURE) { - graph->rend_filter = NullRenderer_Create(input_cb, strm); - } else { - hr = CoCreateInstance(&CLSID_VideoMixingRenderer, NULL, - CLSCTX_INPROC, &IID_IBaseFilter, - (LPVOID *)&graph->rend_filter); - if (FAILED (hr)) { - goto on_error; - } - } + graph->rend_filter = NullRenderer_Create(input_cb, strm); IBaseFilter_EnumPins(graph->rend_filter, &pEnum); if (SUCCEEDED(hr)) { @@ -763,34 +702,6 @@ (use_def_size? 0: vfd->size.h), &srcpin, NULL); graph->mediatype = mediatype; - if (srcpin && dir == PJMEDIA_DIR_RENDER) { - mediatype = graph->mediatype = &mtype; - - memset (mediatype, 0, sizeof(AM_MEDIA_TYPE)); - mediatype->majortype = MEDIATYPE_Video; - mediatype->subtype = *(get_dshow_format_info(strm->param.fmt.id)-> - dshow_format); - mediatype->bFixedSizeSamples = TRUE; - mediatype->bTemporalCompression = FALSE; - - vi = (VIDEOINFOHEADER *) - CoTaskMemAlloc(sizeof(VIDEOINFOHEADER)); - memset (vi, 0, sizeof(VIDEOINFOHEADER)); - mediatype->formattype = FORMAT_VideoInfo; - mediatype->cbFormat = sizeof(VIDEOINFOHEADER); - mediatype->pbFormat = (BYTE *)vi; - - vi->rcSource.bottom = vfd->size.h; - vi->rcSource.right = vfd->size.w; - vi->rcTarget.bottom = vfd->size.h; - vi->rcTarget.right = vfd->size.w; - - vi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - vi->bmiHeader.biPlanes = 1; - vi->bmiHeader.biBitCount = vfi->bpp; - vi->bmiHeader.biCompression = strm->param.fmt.id; - } - if (!srcpin || !sinkpin || !mediatype) { hr = VFW_E_TYPE_NOT_ACCEPTED; goto on_error; @@ -809,9 +720,6 @@ } video_info->bmiHeader.biSizeImage = DIBSIZE(video_info->bmiHeader); mediatype->lSampleSize = DIBSIZE(video_info->bmiHeader); - if (graph->csource_filter) - SourceFilter_SetMediaType(graph->csource_filter, - mediatype); hr = IFilterGraph_AddFilter(graph->filter_graph, (IBaseFilter *)graph->rend_filter, @@ -893,9 +801,9 @@ pj_pool_t *pool; struct dshow_stream *strm; pj_status_t status; + const pjmedia_video_format_detail *vfd; - PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_CAPTURE || - param->dir == PJMEDIA_DIR_RENDER, PJ_EINVAL); + PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_CAPTURE, PJ_EINVAL); if (!get_dshow_format_info(param->fmt.id)) return PJMEDIA_EVID_BADFORMAT; @@ -910,9 +818,6 @@ pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); strm->user_data = user_data; - if (param->dir & PJMEDIA_DIR_CAPTURE) { - const pjmedia_video_format_detail *vfd; - /* Create capture stream here */ status = create_filter_graph(PJMEDIA_DIR_CAPTURE, param->cap_id, PJ_FALSE, PJ_FALSE, df, strm, @@ -944,21 +849,6 @@ vfd = pjmedia_format_get_video_format_detail(¶m->fmt, PJ_TRUE); strm->cap_ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1); - } else if (param->dir & PJMEDIA_DIR_RENDER) { - /* Create render stream here */ - status = create_filter_graph(PJMEDIA_DIR_RENDER, param->rend_id, - PJ_FALSE, PJ_FALSE, df, strm, - &strm->dgraph); - if (status != PJ_SUCCESS) - goto on_error; - } - - /* Apply the remaining settings */ - if (param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) { - dshow_stream_set_cap(&strm->base, - PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW, - ¶m->window); - } /* Done */ strm->base.op = &stream_op; diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/dshow_filter.cpp pjsip/pjmedia/src/pjmedia-videodev/dshow_filter.cpp --- pjproject-2.10/pjmedia/src/pjmedia-videodev/dshow_filter.cpp 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia-videodev/dshow_filter.cpp 2021-02-06 18:47:09.030177557 +0100 @@ -0,0 +1,83 @@ +/* $Id: dshowclasses.cpp 4062 2012-04-19 06:36:57Z ming $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + + +#if defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0 + +#include +#include +#include + +typedef void (*input_callback)(void *user_data, IMediaSample *pMediaSample); + +const GUID CLSID_NullRenderer = {0xF9168C5E, 0xCEB2, 0x4FAA, {0xB6, 0xBF, + 0x32, 0x9B, 0xF3, 0x9F, 0xA1, 0xE4}}; + +class NullRenderer: public CBaseRenderer +{ +public: + NullRenderer(HRESULT *pHr); + virtual ~NullRenderer(); + + virtual HRESULT CheckMediaType(const CMediaType *pmt); + virtual HRESULT DoRenderSample(IMediaSample *pMediaSample); + + input_callback input_cb; + void *user_data; +}; + +NullRenderer::NullRenderer(HRESULT *pHr): CBaseRenderer(CLSID_NullRenderer, + "NullRenderer", + NULL, pHr) +{ + input_cb = NULL; +} + +NullRenderer::~NullRenderer() +{ +} + +HRESULT NullRenderer::CheckMediaType(const CMediaType *pmt) +{ + return S_OK; +} + +HRESULT NullRenderer::DoRenderSample(IMediaSample *pMediaSample) +{ + if (input_cb) + input_cb(user_data, pMediaSample); + + return S_OK; +} + +extern "C" IBaseFilter* NullRenderer_Create(input_callback input_cb, + void *user_data) +{ + HRESULT hr; + NullRenderer *renderer = new NullRenderer(&hr); + renderer->AddRef(); + renderer->input_cb = input_cb; + renderer->user_data = user_data; + + return (CBaseFilter *)renderer; +} + +#endif /* PJMEDIA_VIDEO_DEV_HAS_DSHOW */ diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/fb_dev.c pjsip/pjmedia/src/pjmedia-videodev/fb_dev.c --- pjproject-2.10/pjmedia/src/pjmedia-videodev/fb_dev.c 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia-videodev/fb_dev.c 2021-02-06 18:47:25.010632996 +0100 @@ -0,0 +1,459 @@ +/* + * Copyright (C) 2014-present AG Projects + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +#if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \ + defined(PJMEDIA_VIDEO_DEV_HAS_FB) && PJMEDIA_VIDEO_DEV_HAS_FB != 0 + +#include + +#define THIS_FILE "fb_dev.c" +#define DEFAULT_CLOCK_RATE 90000 +#define DEFAULT_WIDTH 640 +#define DEFAULT_HEIGHT 480 +#define DEFAULT_FPS 25 + + +/* Supported formats */ +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 +static pjmedia_format_id fb_fmts[] = {PJMEDIA_FORMAT_ARGB}; +#else +static pjmedia_format_id fb_fmts[] = {PJMEDIA_FORMAT_BGRA}; +#endif + + +/* fb device info */ +struct fb_dev_info +{ + pjmedia_vid_dev_info info; +}; + + +/* factory */ +struct fb_factory +{ + pjmedia_vid_dev_factory base; + pj_pool_t *pool; + pj_pool_factory *pf; + + unsigned dev_count; + struct fb_dev_info *dev_info; +}; + + +/* Video stream. */ +struct fb_stream +{ + pjmedia_vid_dev_stream base; /**< Base stream */ + pjmedia_vid_dev_param param; /**< Settings */ + pj_pool_t *pool; /**< Memory pool. */ + + pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */ + void *user_data; /**< Application data. */ + + struct fb_factory *ff; + pj_bool_t is_running; + pjmedia_rect_size vid_size; + + struct { + pjmedia_vid_dev_fb_frame_cb cb; + void *user_data; + } frame_handler; +}; + + +/* Prototypes */ +static pj_status_t fb_factory_init(pjmedia_vid_dev_factory *f); +static pj_status_t fb_factory_destroy(pjmedia_vid_dev_factory *f); +static pj_status_t fb_factory_refresh(pjmedia_vid_dev_factory *f); +static unsigned fb_factory_get_dev_count(pjmedia_vid_dev_factory *f); +static pj_status_t fb_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info); +static pj_status_t fb_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param); +static pj_status_t fb_factory_create_stream(pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm); + +static pj_status_t fb_stream_get_param(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_param *param); +static pj_status_t fb_stream_get_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + void *value); +static pj_status_t fb_stream_set_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + const void *value); +static pj_status_t fb_stream_put_frame(pjmedia_vid_dev_stream *strm, + const pjmedia_frame *frame); +static pj_status_t fb_stream_start(pjmedia_vid_dev_stream *strm); +static pj_status_t fb_stream_stop(pjmedia_vid_dev_stream *strm); +static pj_status_t fb_stream_destroy(pjmedia_vid_dev_stream *strm); + + +/* Operations */ +static pjmedia_vid_dev_factory_op factory_op = +{ + &fb_factory_init, + &fb_factory_destroy, + &fb_factory_get_dev_count, + &fb_factory_get_dev_info, + &fb_factory_default_param, + &fb_factory_create_stream, + &fb_factory_refresh +}; + +static pjmedia_vid_dev_stream_op stream_op = +{ + &fb_stream_get_param, + &fb_stream_get_cap, + &fb_stream_set_cap, + &fb_stream_start, + NULL, + &fb_stream_put_frame, + &fb_stream_stop, + &fb_stream_destroy +}; + + +/**************************************************************************** + * Factory operations + */ +/* + * Init FB video driver. + */ +pjmedia_vid_dev_factory* pjmedia_fb_factory(pj_pool_factory *pf) +{ + struct fb_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "fb video", 1000, 1000, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct fb_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + + +/* API: init factory */ +static pj_status_t fb_factory_init(pjmedia_vid_dev_factory *f) +{ + struct fb_factory *ff = (struct fb_factory*)f; + struct fb_dev_info *di; + unsigned i, l; + + /* Initialize input and output devices here */ + ff->dev_info = (struct fb_dev_info*) + pj_pool_calloc(ff->pool, 1, sizeof(struct fb_dev_info)); + + ff->dev_count = 0; + di = &ff->dev_info[ff->dev_count++]; + pj_bzero(di, sizeof(*di)); + strcpy(di->info.name, "FrameBuffer renderer"); + strcpy(di->info.driver, "FrameBuffer"); + di->info.dir = PJMEDIA_DIR_RENDER; + di->info.has_callback = PJ_FALSE; + di->info.caps = 0; + + for (i = 0; i < ff->dev_count; i++) { + di = &ff->dev_info[i]; + di->info.fmt_cnt = PJ_ARRAY_SIZE(fb_fmts); + di->info.caps |= PJMEDIA_VID_DEV_CAP_FORMAT; + + for (l = 0; l < PJ_ARRAY_SIZE(fb_fmts); l++) { + pjmedia_format *fmt = &di->info.fmt[l]; + pjmedia_format_init_video(fmt, + fb_fmts[l], + DEFAULT_WIDTH, + DEFAULT_HEIGHT, + DEFAULT_FPS, 1); + } + } + + PJ_LOG(4, (THIS_FILE, "FrameBuffer initialized")); + + return PJ_SUCCESS; +} + + +/* API: destroy factory */ +static pj_status_t fb_factory_destroy(pjmedia_vid_dev_factory *f) +{ + struct fb_factory *ff = (struct fb_factory*)f; + pj_pool_t *pool = ff->pool; + + ff->pool = NULL; + pj_pool_release(pool); + + return PJ_SUCCESS; +} + + +/* API: refresh the list of devices */ +static pj_status_t fb_factory_refresh(pjmedia_vid_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return PJ_SUCCESS; +} + + +/* API: get number of devices */ +static unsigned fb_factory_get_dev_count(pjmedia_vid_dev_factory *f) +{ + struct fb_factory *ff = (struct fb_factory*)f; + return ff->dev_count; +} + + +/* API: get device info */ +static pj_status_t fb_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info) +{ + struct fb_factory *ff = (struct fb_factory*)f; + + PJ_ASSERT_RETURN(index < ff->dev_count, PJMEDIA_EVID_INVDEV); + pj_memcpy(info, &ff->dev_info[index].info, sizeof(*info)); + + return PJ_SUCCESS; +} + + +/* API: create default device parameter */ +static pj_status_t fb_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param) +{ + struct fb_factory *ff = (struct fb_factory*)f; + struct fb_dev_info *di = &ff->dev_info[index]; + + PJ_ASSERT_RETURN(index < ff->dev_count, PJMEDIA_EVID_INVDEV); + PJ_UNUSED_ARG(pool); + + pj_bzero(param, sizeof(*param)); + param->dir = PJMEDIA_DIR_RENDER; + param->rend_id = index; + param->cap_id = PJMEDIA_VID_INVALID_DEV; + + /* Set the device capabilities here */ + param->flags = PJMEDIA_VID_DEV_CAP_FORMAT; + param->clock_rate = DEFAULT_CLOCK_RATE; + pj_memcpy(¶m->fmt, &di->info.fmt[0], sizeof(param->fmt)); + + return PJ_SUCCESS; +} + + +/* API: Put frame from stream */ +static pj_status_t fb_stream_put_frame(pjmedia_vid_dev_stream *strm, + const pjmedia_frame *frame) +{ + struct fb_stream *stream = (struct fb_stream*)strm; + + if (!stream->is_running) + return PJ_EINVALIDOP; + + if (frame->size==0 || frame->buf==NULL) + return PJ_SUCCESS; + + if (stream->frame_handler.cb) + stream->frame_handler.cb(frame, stream->vid_size, stream->frame_handler.user_data); + + return PJ_SUCCESS; +} + +/* API: create stream */ +static pj_status_t fb_factory_create_stream(pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm) +{ + struct fb_factory *ff = (struct fb_factory*)f; + pj_pool_t *pool; + pj_status_t status; + struct fb_stream *strm; + const pjmedia_video_format_info *vfi; + + PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL); + PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_RENDER, PJ_EINVAL); + PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO && + param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO && + param->dir == PJMEDIA_DIR_RENDER, + PJ_EINVAL); + + vfi = pjmedia_get_video_format_info(NULL, param->fmt.id); + if (!vfi) + return PJMEDIA_EVID_BADFORMAT; + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(ff->pf, "fb-dev", 1000, 1000, NULL); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct fb_stream); + pj_memcpy(&strm->param, param, sizeof(*param)); + strm->pool = pool; + strm->ff = ff; + pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); + strm->user_data = user_data; + + status = fb_stream_set_cap(&strm->base, PJMEDIA_VID_DEV_CAP_FORMAT, ¶m->fmt); + if (status != PJ_SUCCESS) { + fb_stream_destroy((pjmedia_vid_dev_stream *)strm); + return status; + } + + /* Done */ + strm->base.op = &stream_op; + *p_vid_strm = &strm->base; + + return PJ_SUCCESS; +} + + +/* API: Get stream info. */ +static pj_status_t fb_stream_get_param(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_param *pi) +{ + struct fb_stream *strm = (struct fb_stream*)s; + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + + return PJ_SUCCESS; +} + + +/* API: get capability */ +static pj_status_t fb_stream_get_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + void *pval) +{ + struct fb_stream *strm = (struct fb_stream*)s; + + PJ_UNUSED_ARG(strm); + PJ_UNUSED_ARG(cap); + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + return PJMEDIA_EVID_INVCAP; +} + + +/* API: set capability */ +static pj_status_t fb_stream_set_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + const void *pval) +{ + struct fb_stream *strm = (struct fb_stream*)s; + + PJ_UNUSED_ARG(strm); + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap == PJMEDIA_VID_DEV_CAP_FORMAT) { + const pjmedia_video_format_info *vfi; + pjmedia_video_format_detail *vfd; + pjmedia_format *fmt = (pjmedia_format *)pval; + + vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(), fmt->id); + if (!vfi) + return PJMEDIA_EVID_BADFORMAT; + + pjmedia_format_copy(&strm->param.fmt, fmt); + + vfd = pjmedia_format_get_video_format_detail(fmt, PJ_TRUE); + pj_memcpy(&strm->vid_size, &vfd->size, sizeof(vfd->size)); + if (strm->param.disp_size.w == 0 || strm->param.disp_size.h == 0) + pj_memcpy(&strm->param.disp_size, &vfd->size, sizeof(vfd->size)); + + return PJ_SUCCESS; + } + + return PJMEDIA_EVID_INVCAP; +} + + +/* API: Start stream. */ +static pj_status_t fb_stream_start(pjmedia_vid_dev_stream *strm) +{ + struct fb_stream *stream = (struct fb_stream*)strm; + PJ_UNUSED_ARG(strm); + + PJ_LOG(4, (THIS_FILE, "Starting FB video stream")); + stream->is_running = PJ_TRUE; + + return PJ_SUCCESS; +} + + +/* API: Stop stream. */ +static pj_status_t fb_stream_stop(pjmedia_vid_dev_stream *strm) +{ + struct fb_stream *stream = (struct fb_stream*)strm; + PJ_UNUSED_ARG(strm); + + PJ_LOG(4, (THIS_FILE, "Stopping FB video stream")); + stream->is_running = PJ_FALSE; + + return PJ_SUCCESS; +} + + +/* API: Destroy stream. */ +static pj_status_t fb_stream_destroy(pjmedia_vid_dev_stream *strm) +{ + struct fb_stream *stream = (struct fb_stream*)strm; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + fb_stream_stop(strm); + pj_pool_release(stream->pool); + + return PJ_SUCCESS; +} + + +/* API: set callback for handling frames */ +pj_status_t +pjmedia_vid_dev_fb_set_callback(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_fb_frame_cb cb, + void *user_data) +{ + struct fb_stream *stream = (struct fb_stream*)strm; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + if (stream->is_running) + return PJ_EBUSY; + + stream->frame_handler.cb = cb; + stream->frame_handler.user_data = user_data; + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_VIDEO_DEV_HAS_FB */ diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/null_dev.c pjsip/pjmedia/src/pjmedia-videodev/null_dev.c --- pjproject-2.10/pjmedia/src/pjmedia-videodev/null_dev.c 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia-videodev/null_dev.c 2021-02-06 18:47:47.343269399 +0100 @@ -0,0 +1,440 @@ +/* $Id: colorbar_dev.c 4158 2012-06-06 09:56:14Z nanang $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + + +#if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \ + defined(PJMEDIA_VIDEO_DEV_HAS_NULL) && \ + PJMEDIA_VIDEO_DEV_HAS_NULL != 0 + + +#define THIS_FILE "null_dev.c" +#define DEFAULT_CLOCK_RATE 90000 +#define DEFAULT_WIDTH 640 +#define DEFAULT_HEIGHT 480 +#define DEFAULT_FPS 5 + +/* null_ device info */ +struct null_dev_info +{ + pjmedia_vid_dev_info info; +}; + +/* null_ factory */ +struct null_factory +{ + pjmedia_vid_dev_factory base; + pj_pool_t *pool; + pj_pool_factory *pf; + + unsigned dev_count; + struct null_dev_info *dev_info; +}; + +struct null_fmt_info { + pjmedia_format_id fmt_id; /* Format ID */ +}; + +/* Null video source supports */ +static struct null_fmt_info null_fmts[] = +{ + { PJMEDIA_FORMAT_BGRA }, +}; + +/* Video stream. */ +struct null_stream +{ + pjmedia_vid_dev_stream base; /**< Base stream */ + pjmedia_vid_dev_param param; /**< Settings */ + pj_pool_t *pool; /**< Memory pool. */ + + pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */ + void *user_data; /**< Application data. */ + + const struct null_fmt_info *cbfi; + const pjmedia_video_format_info *vfi; + pjmedia_video_apply_fmt_param vafp; + pj_uint8_t *first_line[PJMEDIA_MAX_VIDEO_PLANES]; + pj_timestamp ts; + unsigned ts_inc; +}; + + +/* Prototypes */ +static pj_status_t null_factory_init(pjmedia_vid_dev_factory *f); +static pj_status_t null_factory_destroy(pjmedia_vid_dev_factory *f); +static pj_status_t null_factory_refresh(pjmedia_vid_dev_factory *f); +static unsigned null_factory_get_dev_count(pjmedia_vid_dev_factory *f); +static pj_status_t null_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info); +static pj_status_t null_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param); +static pj_status_t null_factory_create_stream( + pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm); + +static pj_status_t null_stream_get_param(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_param *param); +static pj_status_t null_stream_get_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + void *value); +static pj_status_t null_stream_set_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + const void *value); +static pj_status_t null_stream_get_frame(pjmedia_vid_dev_stream *strm, + pjmedia_frame *frame); +static pj_status_t null_stream_start(pjmedia_vid_dev_stream *strm); +static pj_status_t null_stream_stop(pjmedia_vid_dev_stream *strm); +static pj_status_t null_stream_destroy(pjmedia_vid_dev_stream *strm); + +/* Operations */ +static pjmedia_vid_dev_factory_op factory_op = +{ + &null_factory_init, + &null_factory_destroy, + &null_factory_get_dev_count, + &null_factory_get_dev_info, + &null_factory_default_param, + &null_factory_create_stream, + &null_factory_refresh +}; + +static pjmedia_vid_dev_stream_op stream_op = +{ + &null_stream_get_param, + &null_stream_get_cap, + &null_stream_set_cap, + &null_stream_start, + &null_stream_get_frame, + NULL, + &null_stream_stop, + &null_stream_destroy +}; + + +/**************************************************************************** + * Factory operations + */ +/* + * Init null_ video driver. + */ +pjmedia_vid_dev_factory* pjmedia_null_factory(pj_pool_factory *pf) +{ + struct null_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "null video", 512, 512, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct null_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + + +/* API: init factory */ +static pj_status_t null_factory_init(pjmedia_vid_dev_factory *f) +{ + struct null_factory *cf = (struct null_factory*)f; + struct null_dev_info *ddi; + unsigned i; + + cf->dev_count = 1; + cf->dev_info = (struct null_dev_info*) + pj_pool_calloc(cf->pool, cf->dev_count, + sizeof(struct null_dev_info)); + + ddi = &cf->dev_info[0]; + pj_bzero(ddi, sizeof(*ddi)); + pj_ansi_strncpy(ddi->info.name, "Null video device", + sizeof(ddi->info.name)); + ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0'; + pj_ansi_strncpy(ddi->info.driver, "Null", sizeof(ddi->info.driver)); + ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0'; + ddi->info.dir = PJMEDIA_DIR_CAPTURE; + ddi->info.has_callback = PJ_FALSE; + + ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT; + ddi->info.fmt_cnt = sizeof(null_fmts)/sizeof(null_fmts[0]); + for (i = 0; i < ddi->info.fmt_cnt; i++) { + pjmedia_format *fmt = &ddi->info.fmt[i]; + pjmedia_format_init_video(fmt, null_fmts[i].fmt_id, + DEFAULT_WIDTH, DEFAULT_HEIGHT, + DEFAULT_FPS, 1); + } + + PJ_LOG(4, (THIS_FILE, "Null video src initialized with %d device(s):", cf->dev_count)); + for (i = 0; i < cf->dev_count; i++) { + PJ_LOG(4, (THIS_FILE, "%2d: %s", i, cf->dev_info[i].info.name)); + } + + return PJ_SUCCESS; +} + +/* API: destroy factory */ +static pj_status_t null_factory_destroy(pjmedia_vid_dev_factory *f) +{ + struct null_factory *cf = (struct null_factory*)f; + pj_pool_t *pool = cf->pool; + + cf->pool = NULL; + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +/* API: refresh the list of devices */ +static pj_status_t null_factory_refresh(pjmedia_vid_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return PJ_SUCCESS; +} + +/* API: get number of devices */ +static unsigned null_factory_get_dev_count(pjmedia_vid_dev_factory *f) +{ + struct null_factory *cf = (struct null_factory*)f; + return cf->dev_count; +} + +/* API: get device info */ +static pj_status_t null_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info) +{ + struct null_factory *cf = (struct null_factory*)f; + + PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV); + + pj_memcpy(info, &cf->dev_info[index].info, sizeof(*info)); + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t null_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param) +{ + struct null_factory *cf = (struct null_factory*)f; + struct null_dev_info *di = &cf->dev_info[index]; + + PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV); + + PJ_UNUSED_ARG(pool); + + pj_bzero(param, sizeof(*param)); + param->dir = PJMEDIA_DIR_CAPTURE; + param->cap_id = index; + param->rend_id = PJMEDIA_VID_INVALID_DEV; + param->flags = PJMEDIA_VID_DEV_CAP_FORMAT; + param->clock_rate = DEFAULT_CLOCK_RATE; + pj_memcpy(¶m->fmt, &di->info.fmt[0], sizeof(param->fmt)); + + return PJ_SUCCESS; +} + +static const struct null_fmt_info* get_null_fmt_info(pjmedia_format_id id) +{ + unsigned i; + + for (i = 0; i < sizeof(null_fmts)/sizeof(null_fmts[0]); i++) { + if (null_fmts[i].fmt_id == id) + return &null_fmts[i]; + } + + return NULL; +} + + +/* API: create stream */ +static pj_status_t null_factory_create_stream( + pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm) +{ + struct null_factory *cf = (struct null_factory*)f; + pj_pool_t *pool; + struct null_stream *strm; + const pjmedia_video_format_detail *vfd; + const pjmedia_video_format_info *vfi; + pjmedia_video_apply_fmt_param vafp; + const struct null_fmt_info *cbfi; + unsigned i; + + PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL); + PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO && + param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO && + param->dir == PJMEDIA_DIR_CAPTURE, + PJ_EINVAL); + + pj_bzero(&vafp, sizeof(vafp)); + + vfd = pjmedia_format_get_video_format_detail(¶m->fmt, PJ_TRUE); + vfi = pjmedia_get_video_format_info(NULL, param->fmt.id); + cbfi = get_null_fmt_info(param->fmt.id); + if (!vfi || !cbfi) + return PJMEDIA_EVID_BADFORMAT; + + vafp.size = param->fmt.det.vid.size; + if (vfi->apply_fmt(vfi, &vafp) != PJ_SUCCESS) + return PJMEDIA_EVID_BADFORMAT; + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(cf->pf, "null-dev", 512, 512, NULL); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct null_stream); + pj_memcpy(&strm->param, param, sizeof(*param)); + strm->pool = pool; + pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); + strm->user_data = user_data; + strm->vfi = vfi; + strm->cbfi = cbfi; + pj_memcpy(&strm->vafp, &vafp, sizeof(vafp)); + strm->ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1); + + for (i = 0; i < vfi->plane_cnt; ++i) { + strm->first_line[i] = pj_pool_alloc(pool, vafp.strides[i]); + pj_memset(strm->first_line[i], 0, vafp.strides[i]); + } + + /* Done */ + strm->base.op = &stream_op; + *p_vid_strm = &strm->base; + + return PJ_SUCCESS; +} + +/* API: Get stream info. */ +static pj_status_t null_stream_get_param(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_param *pi) +{ + struct null_stream *strm = (struct null_stream*)s; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t null_stream_get_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + void *pval) +{ + struct null_stream *strm = (struct null_stream*)s; + + PJ_UNUSED_ARG(strm); + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + return PJMEDIA_EVID_INVCAP; +} + +/* API: set capability */ +static pj_status_t null_stream_set_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + const void *pval) +{ + struct null_stream *strm = (struct null_stream*)s; + + PJ_UNUSED_ARG(strm); + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + return PJMEDIA_EVID_INVCAP; +} + + +/* API: Get frame from stream */ +static pj_status_t null_stream_get_frame(pjmedia_vid_dev_stream *strm, + pjmedia_frame *frame) +{ + struct null_stream *stream = (struct null_stream*)strm; + unsigned i; + pj_uint8_t *ptr = frame->buf; + + frame->type = PJMEDIA_FRAME_TYPE_VIDEO; + frame->bit_info = 0; + frame->timestamp = stream->ts; + stream->ts.u64 += stream->ts_inc; + + /* paint subsequent lines */ + for (i=0; ivfi->plane_cnt; ++i) { + pj_uint8_t *plane_end; + plane_end = ptr + stream->vafp.plane_bytes[i]; + while (ptr < plane_end) { + pj_memcpy(ptr, stream->first_line[i], stream->vafp.strides[i]); + ptr += stream->vafp.strides[i]; + } + } + + return PJ_SUCCESS; +} + +/* API: Start stream. */ +static pj_status_t null_stream_start(pjmedia_vid_dev_stream *strm) +{ + struct null_stream *stream = (struct null_stream*)strm; + + PJ_UNUSED_ARG(stream); + + PJ_LOG(4, (THIS_FILE, "Starting null video stream")); + + return PJ_SUCCESS; +} + +/* API: Stop stream. */ +static pj_status_t null_stream_stop(pjmedia_vid_dev_stream *strm) +{ + struct null_stream *stream = (struct null_stream*)strm; + + PJ_UNUSED_ARG(stream); + + PJ_LOG(4, (THIS_FILE, "Stopping null video stream")); + + return PJ_SUCCESS; +} + + +/* API: Destroy stream. */ +static pj_status_t null_stream_destroy(pjmedia_vid_dev_stream *strm) +{ + struct null_stream *stream = (struct null_stream*)strm; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + null_stream_stop(strm); + + pj_pool_release(stream->pool); + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_VIDEO_DEV_HAS_NULL */ diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/videodev.c pjsip/pjmedia/src/pjmedia-videodev/videodev.c --- pjproject-2.10/pjmedia/src/pjmedia-videodev/videodev.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia-videodev/videodev.c 2021-02-06 23:01:33.883264555 +0100 @@ -51,6 +51,10 @@ pjmedia_vid_dev_factory* pjmedia_qt_factory(pj_pool_factory *pf); #endif +#if PJMEDIA_VIDEO_DEV_HAS_AVF +pjmedia_vid_dev_factory* pjmedia_avf_factory(pj_pool_factory *pf); +#endif + #if PJMEDIA_VIDEO_DEV_HAS_DARWIN pjmedia_vid_dev_factory* pjmedia_darwin_factory(pj_pool_factory *pf); #endif @@ -59,6 +63,14 @@ pjmedia_vid_dev_factory* pjmedia_opengl_factory(pj_pool_factory *pf); #endif +#if PJMEDIA_VIDEO_DEV_HAS_FB +pjmedia_vid_dev_factory* pjmedia_fb_factory(pj_pool_factory *pf); +#endif + +#if PJMEDIA_VIDEO_DEV_HAS_NULL +pjmedia_vid_dev_factory* pjmedia_null_factory(pj_pool_factory *pf); +#endif + #if PJMEDIA_VIDEO_DEV_HAS_ANDROID pjmedia_vid_dev_factory* pjmedia_and_factory(pj_pool_factory *pf); #endif @@ -98,6 +110,9 @@ #if PJMEDIA_VIDEO_DEV_HAS_QT vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_qt_factory; #endif +#if PJMEDIA_VIDEO_DEV_HAS_AVF + vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_avf_factory; +#endif #if PJMEDIA_VIDEO_DEV_HAS_OPENGL vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_opengl_factory; #endif @@ -122,6 +137,12 @@ */ vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_cbar_factory; #endif +#if PJMEDIA_VIDEO_DEV_HAS_FB + vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_fb_factory; +#endif +#if PJMEDIA_VIDEO_DEV_HAS_NULL + vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_null_factory; +#endif /* Initialize each factory and build the device ID list */ for (i=0; idrv_cnt; ++i) { diff -ruN pjproject-2.10/pjnath/include/pjnath/ice_strans.h pjsip/pjnath/include/pjnath/ice_strans.h --- pjproject-2.10/pjnath/include/pjnath/ice_strans.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjnath/include/pjnath/ice_strans.h 2021-02-06 17:37:27.238934521 +0100 @@ -144,6 +144,52 @@ } pj_ice_strans_op; + +/** + * ICE stream transport's state. + */ +typedef enum pj_ice_strans_state +{ + /** + * ICE stream transport is not created. + */ + PJ_ICE_STRANS_STATE_NULL, + + /** + * ICE candidate gathering process is in progress. + */ + PJ_ICE_STRANS_STATE_INIT, + + /** + * ICE stream transport initialization/candidate gathering process is + * complete, ICE session may be created on this stream transport. + */ + PJ_ICE_STRANS_STATE_READY, + + /** + * New session has been created and the session is ready. + */ + PJ_ICE_STRANS_STATE_SESS_READY, + + /** + * ICE negotiation is in progress. + */ + PJ_ICE_STRANS_STATE_NEGO, + + /** + * ICE negotiation has completed successfully and media is ready + * to be used. + */ + PJ_ICE_STRANS_STATE_RUNNING, + + /** + * ICE negotiation has completed with failure. + */ + PJ_ICE_STRANS_STATE_FAILED + +} pj_ice_strans_state; + + /** * This structure contains callbacks that will be called by the * ICE stream transport. @@ -192,6 +238,18 @@ pj_ice_strans_op op, pj_status_t status); + /** + * Callback to report ICE state changes. + * + * @param ice_st The ICE stream transport. + * @param prev Previous state. + * @param curr Current state. + */ + void (*on_ice_state)(pj_ice_strans *ice_st, + pj_ice_strans_state prev, + pj_ice_strans_state curr); + + } pj_ice_strans_cb; @@ -522,51 +580,6 @@ } pj_ice_strans_cfg; -/** - * ICE stream transport's state. - */ -typedef enum pj_ice_strans_state -{ - /** - * ICE stream transport is not created. - */ - PJ_ICE_STRANS_STATE_NULL, - - /** - * ICE candidate gathering process is in progress. - */ - PJ_ICE_STRANS_STATE_INIT, - - /** - * ICE stream transport initialization/candidate gathering process is - * complete, ICE session may be created on this stream transport. - */ - PJ_ICE_STRANS_STATE_READY, - - /** - * New session has been created and the session is ready. - */ - PJ_ICE_STRANS_STATE_SESS_READY, - - /** - * ICE negotiation is in progress. - */ - PJ_ICE_STRANS_STATE_NEGO, - - /** - * ICE negotiation has completed successfully and media is ready - * to be used. - */ - PJ_ICE_STRANS_STATE_RUNNING, - - /** - * ICE negotiation has completed with failure. - */ - PJ_ICE_STRANS_STATE_FAILED - -} pj_ice_strans_state; - - /** * Initialize ICE transport configuration with default values. * @@ -921,6 +934,27 @@ unsigned comp_id); /** + * Retrieve the ICE session associated with this transport + * + * @param ice_st The ICE stream transport. + * + * @return The ICE session associated with this transport + */ +PJ_DECL(pj_ice_sess*) +pj_ice_strans_get_session(const pj_ice_strans *ice_st); + +/** + * Retrieve the ICE start time + * + * @param ice_st The ICE stream transport. + * + * @return The ICE start time + */ +PJ_DECL(pj_time_val) +pj_ice_strans_get_start_time(const pj_ice_strans *ice_st); + + +/** * Stop and destroy the ICE session inside this media transport. Application * needs to call this function once the media session is over (the call has * been disconnected). diff -ruN pjproject-2.10/pjnath/src/pjnath/ice_strans.c pjsip/pjnath/src/pjnath/ice_strans.c --- pjproject-2.10/pjnath/src/pjnath/ice_strans.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjnath/src/pjnath/ice_strans.c 2021-02-06 17:27:03.341194358 +0100 @@ -240,6 +240,19 @@ } sock_user_data; +/* Set ICE state*/ +static void set_ice_state(pj_ice_strans *ice_st, pj_ice_strans_state state) +{ + pj_ice_strans_state prev = ice_st->state; + + if (prev != state) { + ice_st->state = state; + if (ice_st->cb.on_ice_state) + (*ice_st->cb.on_ice_state)(ice_st, prev, state); + } +} + + /* Validate configuration */ static pj_status_t pj_ice_strans_cfg_check_valid(const pj_ice_strans_cfg *cfg) { @@ -928,7 +941,7 @@ pj_pool_calloc(pool, comp_cnt, sizeof(pj_ice_strans_comp*)); /* Move state to candidate gathering */ - ice_st->state = PJ_ICE_STRANS_STATE_INIT; + set_ice_state(ice_st, PJ_ICE_STRANS_STATE_INIT); /* Acquire initialization mutex to prevent callback to be * called before we finish initialization. @@ -1123,7 +1136,7 @@ * candidate for a component. */ ice_st->cb_called = PJ_TRUE; - ice_st->state = PJ_ICE_STRANS_STATE_READY; + set_ice_state(ice_st, PJ_ICE_STRANS_STATE_READY); if (ice_st->cb.on_ice_complete) (*ice_st->cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_INIT, status); @@ -1289,7 +1302,7 @@ } /* ICE session is ready for negotiation */ - ice_st->state = PJ_ICE_STRANS_STATE_SESS_READY; + set_ice_state(ice_st, PJ_ICE_STRANS_STATE_SESS_READY); return PJ_SUCCESS; @@ -1518,7 +1531,7 @@ return status; } - ice_st->state = PJ_ICE_STRANS_STATE_NEGO; + set_ice_state(ice_st, PJ_ICE_STRANS_STATE_NEGO); return status; } @@ -1539,6 +1552,25 @@ } /* + * Get ICE session. + */ +PJ_DEF(pj_ice_sess*) +pj_ice_strans_get_session(const pj_ice_strans *ice_st) +{ + return ice_st->ice; +} + +/* + * Get ICE start time. + */ +PJ_DEF(pj_time_val) +pj_ice_strans_get_start_time(const pj_ice_strans *ice_st) +{ + + return ice_st->start_time; +} + +/* * Stop ICE! */ PJ_DEF(pj_status_t) pj_ice_strans_stop_ice(pj_ice_strans *ice_st) @@ -1556,7 +1588,7 @@ ice_st->ice = NULL; } - ice_st->state = PJ_ICE_STRANS_STATE_INIT; + set_ice_state(ice_st, PJ_ICE_STRANS_STATE_INIT); pj_grp_lock_release(ice_st->grp_lock); @@ -1877,8 +1909,8 @@ } } - ice_st->state = (status==PJ_SUCCESS) ? PJ_ICE_STRANS_STATE_RUNNING : - PJ_ICE_STRANS_STATE_FAILED; + set_ice_state(ice_st, (status==PJ_SUCCESS) ? PJ_ICE_STRANS_STATE_RUNNING : + PJ_ICE_STRANS_STATE_FAILED); pj_log_push_indent(); (*cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_NEGOTIATION, status); diff -ruN pjproject-2.10/pjsip/include/pjsip/sip_msg.h pjsip/pjsip/include/pjsip/sip_msg.h --- pjproject-2.10/pjsip/include/pjsip/sip_msg.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjsip/include/pjsip/sip_msg.h 2021-02-06 19:48:20.570302003 +0100 @@ -675,6 +675,23 @@ }; /** + * General purpose function to print a SIP message body. + * Uses the appropriate internal functions to print the string representation + * of a SIP message body. It sets the output buffer to a statically allocated + * buffer, so the caller is responsible to copy it. + * + * @param msg_body The message body. + * @param buf Pointer to get the result buffer (statically allocated). + * @param size The size of the buffer. + * + * @return The length copied to the buffer, or -1. + */ +PJ_DECL(int) pjsip_print_body( pjsip_msg_body *msg_body, + char **buf, int *len); + + + +/** * General purpose function to textual data in a SIP body. Attach this function * in a SIP message body only if the data in pjsip_msg_body is a textual * message ready to be embedded in a SIP message. If the data in the message @@ -894,6 +911,20 @@ PJ_DECL(void*) pjsip_msg_find_remove_hdr( pjsip_msg *msg, pjsip_hdr_e hdr, void *start); +/** + * Find and remove a header in the message. + * + * @param msg The message. + * @param name The header name to find. + * @param start The first header field where the search should begin, + * or NULL to search from the first header in the message. + * + * @return The header field, or NULL if not found. + */ +PJ_DECL(void*) pjsip_msg_find_remove_hdr_by_name( pjsip_msg *msg, + pj_str_t *name, + void *start); + /** * Add a header to the message, putting it last in the header list. * diff -ruN pjproject-2.10/pjsip/include/pjsip-simple/evsub.h pjsip/pjsip/include/pjsip-simple/evsub.h --- pjproject-2.10/pjsip/include/pjsip-simple/evsub.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjsip/include/pjsip-simple/evsub.h 2021-02-06 19:49:09.127691820 +0100 @@ -524,6 +524,18 @@ pj_uint32_t seconds); +/* Update evbsub internal refresh_time with the given interval */ +PJ_DECL(void) pjsip_evsub_update_expires( pjsip_evsub *sub, + pj_uint32_t interval ); + + +/* Set the specified timer (UAC or UAS) to the specified time */ +PJ_DECL(void) pjsip_evsub_set_timer( pjsip_evsub *sub, + int timer_id, + pj_int32_t seconds ); + + + PJ_END_DECL /** diff -ruN pjproject-2.10/pjsip/src/pjsip/sip_msg.c pjsip/pjsip/src/pjsip/sip_msg.c --- pjproject-2.10/pjsip/src/pjsip/sip_msg.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjsip/src/pjsip/sip_msg.c 2021-02-06 19:39:21.510782607 +0100 @@ -394,6 +394,18 @@ return hdr; } +PJ_DEF(void*) pjsip_msg_find_remove_hdr_by_name( pjsip_msg *msg, + pj_str_t *name, + void *start) +{ + pjsip_hdr *hdr = (pjsip_hdr*) pjsip_msg_find_hdr_by_name(msg, name, start); + if (hdr) { + pj_list_erase(hdr); + } + return hdr; +} + + PJ_DEF(pj_ssize_t) pjsip_msg_print( const pjsip_msg *msg, char *buf, pj_size_t size) { @@ -2146,6 +2158,21 @@ /* * Message body manipulations. */ + +PJ_DEF(int) pjsip_print_body(pjsip_msg_body *msg_body, char **buf, int *len) +{ + static char s_buf[PJSIP_MAX_PKT_LEN]; + int res; + + res = (*msg_body->print_body)(msg_body, s_buf, PJSIP_MAX_PKT_LEN); + if (res < 0) { + return -1; + } + *buf = s_buf; + *len = res; + return 0; +} + PJ_DEF(int) pjsip_print_text_body(pjsip_msg_body *msg_body, char *buf, pj_size_t size) { if (size < msg_body->len) diff -ruN pjproject-2.10/pjsip/src/pjsip-simple/evsub.c pjsip/pjsip/src/pjsip-simple/evsub.c --- pjproject-2.10/pjsip/src/pjsip-simple/evsub.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjsip/src/pjsip-simple/evsub.c 2021-02-06 19:40:40.321065814 +0100 @@ -495,6 +495,12 @@ } +PJ_DEF(void) pjsip_evsub_update_expires( pjsip_evsub *sub, pj_uint32_t interval ) +{ + update_expires(sub, interval); +} + + /* * Schedule timer. */ @@ -538,6 +544,13 @@ } +PJ_DEF(void) pjsip_evsub_set_timer( pjsip_evsub *sub, int timer_id, + pj_int32_t seconds) +{ + set_timer(sub, timer_id, seconds); +} + + /* * Destructor. */ diff -ruN pjproject-2.10/pjsip/src/pjsip-simple/evsub_msg.c pjsip/pjsip/src/pjsip-simple/evsub_msg.c --- pjproject-2.10/pjsip/src/pjsip-simple/evsub_msg.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjsip/src/pjsip-simple/evsub_msg.c 2021-02-06 19:41:44.190911650 +0100 @@ -293,6 +293,45 @@ } /* + * Parse Allow-Events header. + */ +static pjsip_hdr* parse_hdr_allow_events(pjsip_parse_ctx *ctx) +{ + pjsip_allow_events_hdr *allow_events = + pjsip_allow_events_hdr_create(ctx->pool); + const pjsip_parser_const_t *pc = pjsip_parser_const(); + pj_scanner *scanner = ctx->scanner; + + /* Some header fields allow empty elements in the value: + * Accept, Allow, Supported + */ + if (pj_scan_is_eof(scanner) || + *scanner->curptr == '\r' || *scanner->curptr == '\n') + { + goto end; + } + + pj_scan_get( scanner, &pc->pjsip_NOT_COMMA_OR_NEWLINE, + &allow_events->values[0]); + allow_events->count++; + + while (*scanner->curptr == ',') { + pj_scan_get_char(scanner); + pj_scan_get( scanner, &pc->pjsip_NOT_COMMA_OR_NEWLINE, + &allow_events->values[allow_events->count]); + allow_events->count++; + + if (allow_events->count >= PJSIP_MAX_ALLOW_EVENTS) + break; + } + +end: + pjsip_parse_end_hdr_imp(scanner); + return (pjsip_hdr*)allow_events; +} + + +/* * Register header parsers. */ PJ_DEF(void) pjsip_evsub_init_parser(void) diff -ruN pjproject-2.10/pjsip/src/pjsip-ua/sip_inv.c pjsip/pjsip/src/pjsip-ua/sip_inv.c --- pjproject-2.10/pjsip/src/pjsip-ua/sip_inv.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjsip/src/pjsip-ua/sip_inv.c 2021-02-06 19:46:19.602835344 +0100 @@ -2006,6 +2006,20 @@ return PJMEDIA_SDP_EINSDP; } + /* Only accept SDP in INVITE, UPDATE and ACK requests, 18x (reliable) and 183 provisional responses + * and 200 final response. + */ + if (!(msg->type == PJSIP_REQUEST_MSG && msg->line.req.method.id == PJSIP_INVITE_METHOD) && + !(msg->type == PJSIP_REQUEST_MSG && msg->line.req.method.id == PJSIP_ACK_METHOD) && + !(msg->type == PJSIP_REQUEST_MSG && pjsip_method_cmp(&msg->line.req.method, &pjsip_update_method)==0) && + !(msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code/10==18 && pjsip_100rel_is_reliable(rdata)) && + !(msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code == 183) && + !(msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code == 200)) { + PJ_LOG(4,(inv->obj_name, "ignored SDP body")); + return PJ_SUCCESS; + } + + /* Get/attach invite session's transaction data */ tsx_inv_data = (struct tsx_inv_data*) tsx->mod_data[mod_inv.mod.id]; if (tsx_inv_data == NULL) { @@ -2233,6 +2247,11 @@ { status = pjmedia_sdp_neg_set_local_answer(inv->pool_prov, inv->neg, local_sdp); + } else if (pjmedia_sdp_neg_get_state(inv->neg)== + PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) + { + /* Go forward with our local offer */ + status = PJ_SUCCESS; } else { /* Can not specify local SDP at this state. */ @@ -3976,8 +3995,9 @@ if (inv->state != PJSIP_INV_STATE_DISCONNECTED && ((tsx->status_code == PJSIP_SC_CALL_TSX_DOES_NOT_EXIST && tsx->method.id != PJSIP_CANCEL_METHOD) || - tsx->status_code == PJSIP_SC_REQUEST_TIMEOUT || - tsx->status_code == PJSIP_SC_TSX_TIMEOUT)) + (inv->state != PJSIP_INV_STATE_CONFIRMED && + (tsx->status_code == PJSIP_SC_TSX_TIMEOUT || + tsx->status_code == PJSIP_SC_TSX_TRANSPORT_ERROR)))) { pjsip_tx_data *bye; pj_status_t status; diff -ruN pjproject-2.10/third_party/build/os-auto.mak.in pjsip/third_party/build/os-auto.mak.in --- pjproject-2.10/third_party/build/os-auto.mak.in 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/third_party/build/os-auto.mak.in 2021-02-06 16:09:49.029693848 +0100 @@ -73,6 +73,8 @@ endif endif +DIRS += zsrtp + ifneq (@ac_no_webrtc@,1) ifeq (@ac_external_webrtc@,1) # External webrtc diff -ruN pjproject-2.10/build.mak.in pjsip/build.mak.in --- pjproject-2.10/build.mak.in 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/build.mak.in 2021-02-06 16:09:49.029693848 +0100 @@ -148,6 +148,19 @@ endif endif +#ifneq (@ac_no_zsrtp@,1) +ifeq (@ac_external_zsrtp@,1) +APP_THIRD_PARTY_EXT += -lzsrtp +else +APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libzsrtp-$(LIB_SUFFIX) +ifeq ($(PJ_SHARED_LIBRARIES),) +APP_THIRD_PARTY_LIBS += -lzsrtp-$(TARGET_NAME) -lsqlite3 -lstdc++ +else +APP_THIRD_PARTY_LIBS += -lzsrtp +APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libzsrtp.$(SHLIB_SUFFIX).$(PJ_VERSION_MAJOR) $(PJ_DIR)/third_party/lib/libzsrtp.$(SHLIB_SUFFIX) +endif +endif +#endif # Additional flags @ac_build_mak_vars@ diff -ruN pjproject-2.10/third_party/build/os-darwinos.mak pjsip/third_party/build/os-darwinos.mak --- pjproject-2.10/third_party/build/os-darwinos.mak 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/third_party/build/os-darwinos.mak 2021-02-06 16:00:34.131596843 +0100 @@ -5,5 +5,6 @@ DIRS += g7221 DIRS += srtp DIRS += resample +DIRS += zsrtp diff -ruN pjproject-2.10/third_party/build/os-linux.mak pjsip/third_party/build/os-linux.mak --- pjproject-2.10/third_party/build/os-linux.mak 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/third_party/build/os-linux.mak 2021-02-06 15:59:58.326708529 +0100 @@ -5,5 +5,6 @@ DIRS += g7221 DIRS += srtp DIRS += resample +DIRS += zsrtp diff -ruN pjproject-2.10/third_party/build/os-win32.mak pjsip/third_party/build/os-win32.mak --- pjproject-2.10/third_party/build/os-win32.mak 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/third_party/build/os-win32.mak 2021-02-06 16:00:20.023246385 +0100 @@ -5,5 +5,6 @@ DIRS += g7221 DIRS += srtp DIRS += resample +DIRS += zsrtp --- pjproject-2.10/pjmedia/src/pjmedia-audiodev/audiodev.c 2021-02-04 11:19:25.357096871 +0100 +++ pjsip/pjmedia/src/pjmedia-audiodev/audiodev.c 2021-02-04 11:09:10.120174258 +0100 @@ -143,6 +143,18 @@ aud_subsys->drv[aud_subsys->drv_cnt++].create = &pjmedia_null_audio_factory; #endif + /* Initialize audio device observer objects */ + pj_status_t st; + aud_subsys->dev_observer.pool = pj_pool_create(pf, "aud_dev_observer_pool", 512, 512, NULL); + if (!aud_subsys->dev_observer.pool) { + return PJ_ENOMEM; + } + st = pj_mutex_create_simple(aud_subsys->dev_observer.pool, "aud_dev_observer_lock", &aud_subsys->dev_observer.lock); + if (st != PJ_SUCCESS) { + return st; + } + aud_subsys->dev_observer.cb = NULL; + /* Initialize each factory and build the device ID list */ for (i=0; idrv_cnt; ++i) { status = pjmedia_aud_driver_init(i, PJ_FALSE); @@ -229,6 +241,9 @@ pjmedia_aud_driver_deinit(i); } + pj_mutex_destroy(aud_subsys->dev_observer.lock); + pj_pool_release(aud_subsys->dev_observer.pool); + aud_subsys->pf = NULL; } return PJ_SUCCESS; --- pjproject-2.10/pjmedia/src/pjmedia/audiodev.c 2021-02-04 11:19:25.357096871 +0100 +++ pjsip/pjmedia/src/pjmedia/audiodev.c 2021-02-04 11:09:10.120174258 +0100 @@ -74,6 +74,60 @@ return &aud_subsys; } +/* callback for device change operations */ +static void process_aud_dev_change_event(pjmedia_aud_dev_change_event event) +{ + pj_status_t status; + + if (!pj_thread_is_registered()) { + status = pj_thread_register("aud_dev_observer", aud_subsys.dev_observer.thread_desc, &aud_subsys.dev_observer.thread); + if (status != PJ_SUCCESS) { + return; + } + PJ_LOG(5, (THIS_FILE, "Audio device change thread registered")); + } + + status = pj_mutex_lock(aud_subsys.dev_observer.lock); + if (status != PJ_SUCCESS) { + PJ_LOG(5, (THIS_FILE, "Could not acquire audio device change lock")); + return; + } + + if (!aud_subsys.dev_observer.cb) { + /* there is no registered callback to call */ + goto end; + } + + switch(event) { + case DEFAULT_INPUT_CHANGED: + PJ_LOG(5, (THIS_FILE, "Default input device changed")); + pjmedia_aud_dev_refresh(); + (*aud_subsys.dev_observer.cb)(PJMEDIA_AUD_DEV_DEFAULT_INPUT_CHANGED); + break; + case DEFAULT_OUTPUT_CHANGED: + PJ_LOG(5, (THIS_FILE, "Default output device changed")); + pjmedia_aud_dev_refresh(); + (*aud_subsys.dev_observer.cb)(PJMEDIA_AUD_DEV_DEFAULT_OUTPUT_CHANGED); + break; + case DEVICE_LIST_CHANGED: + PJ_LOG(5, (THIS_FILE, "Device list changed")); + (*aud_subsys.dev_observer.cb)(PJMEDIA_AUD_DEV_LIST_WILL_REFRESH); + pjmedia_aud_dev_refresh(); + (*aud_subsys.dev_observer.cb)(PJMEDIA_AUD_DEV_LIST_DID_REFRESH); + break; + default: + PJ_LOG(5, (THIS_FILE, "Unknown event: %d", event)); + break; + } + +end: + status = pj_mutex_unlock(aud_subsys.dev_observer.lock); + if (status != PJ_SUCCESS) { + PJ_LOG(5, (THIS_FILE, "Could not release audio device change lock")); + } + +} + /* API: init driver */ PJ_DEF(pj_status_t) pjmedia_aud_driver_init(unsigned drv_idx, pj_bool_t refresh) @@ -99,6 +152,11 @@ f = drv->f; } + /* TODO Does device change observer still need to be registered? */ + //if (!refresh) { + // f->op->set_dev_change_cb(f, &process_aud_dev_change_event); + //} + if (!f) return PJ_EUNKNOWN; @@ -123,8 +181,8 @@ */ /* Fill in default devices */ - drv->play_dev_idx = drv->rec_dev_idx = - drv->dev_idx = PJMEDIA_AUD_INVALID_DEV; + drv->rec_dev_idx = f->op->get_default_rec_dev(f); + drv->play_dev_idx = f->op->get_default_play_dev(f); for (i=0; irec_dev_idx = i; } - if (drv->dev_idx < 0 && info.input_count && - info.output_count) - { - /* Set default capture and playback device */ - drv->dev_idx = i; - } - if (drv->play_dev_idx >= 0 && drv->rec_dev_idx >= 0 && - drv->dev_idx >= 0) + if (drv->play_dev_idx >= 0 && drv->rec_dev_idx >= 0) { /* Done. */ break; @@ -183,13 +248,13 @@ pjmedia_aud_driver *drv = &aud_subsys.drv[drv_idx]; if (drv->f) { + drv->f->op->set_dev_change_cb(drv->f, NULL); drv->f->op->destroy(drv->f); drv->f = NULL; } pj_bzero(drv, sizeof(*drv)); - drv->play_dev_idx = drv->rec_dev_idx = - drv->dev_idx = PJMEDIA_AUD_INVALID_DEV; + drv->play_dev_idx = drv->rec_dev_idx = PJMEDIA_AUD_INVALID_DEV; } /* API: get capability name/info */ @@ -374,11 +426,7 @@ for (i=0; idev_idx >= 0) { - id = drv->dev_idx; - make_global_index(i, &id); - break; - } else if (id==PJMEDIA_AUD_DEFAULT_CAPTURE_DEV && + if (id==PJMEDIA_AUD_DEFAULT_CAPTURE_DEV && drv->rec_dev_idx >= 0) { id = drv->rec_dev_idx; @@ -390,7 +455,7 @@ id = drv->play_dev_idx; make_global_index(i, &id); break; - } + } } if (id < 0) { @@ -625,4 +590,24 @@ return strm->op->destroy(strm); } +/* API: Register device change observer. */ +PJ_DEF(pj_status_t) pjmedia_aud_dev_set_observer_cb(pjmedia_aud_dev_observer_callback cb) +{ + pj_status_t status; + + status = pj_mutex_lock(aud_subsys.dev_observer.lock); + if (status != PJ_SUCCESS) { + PJ_LOG(5, (THIS_FILE, "Could not acquire audio device change lock")); + return status; + } + + aud_subsys.dev_observer.cb = cb; + + status = pj_mutex_unlock(aud_subsys.dev_observer.lock); + if (status != PJ_SUCCESS) { + PJ_LOG(5, (THIS_FILE, "Could not release audio device change lock")); + } + + return status; +} --- pjsip/pjmedia/src/pjmedia/transport_zrtp.c 2021-02-05 12:38:15.913793992 +0100 +++ pjsip/pjmedia/src/pjmedia/transport_zrtp.c 2021-03-11 09:20:35.134754145 +0100 @@ -78,6 +78,8 @@ pjmedia_dir dir, unsigned pct_lost); static pj_status_t transport_destroy(pjmedia_transport *tp); +static pj_status_t transport_attach2(pjmedia_transport *tp, + pjmedia_transport_attach_param *att_param); /* The transport operations */ @@ -94,7 +96,8 @@ &transport_media_start, &transport_media_stop, &transport_simulate_lost, - &transport_destroy + &transport_destroy, + &transport_attach2 }; /* The transport zrtp instance */ @@ -108,6 +111,7 @@ void (*stream_rtp_cb)(void *user_data, void *pkt, pj_ssize_t); + void (*stream_rtp_cb2)(pjmedia_tp_cb_param *param); void (*stream_rtcp_cb)(void *user_data, void *pkt, pj_ssize_t); @@ -794,7 +798,7 @@ int32_t newLen = 0; pj_status_t rc = PJ_SUCCESS; - pj_assert(zrtp && zrtp->stream_rtcp_cb && pkt); + pj_assert(zrtp && zrtp->stream_rtp_cb && pkt); // check if this could be a real RTP/SRTP packet. if ((*buffer & 0xf0) != 0x10) @@ -876,6 +880,106 @@ } } +/* This is our RTP callback, that is called by the slave transport when it + * receives RTP packet. + */ +static void transport_rtp_cb2(pjmedia_tp_cb_param *param) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)param->user_data; + pj_uint8_t* buffer = (pj_uint8_t*)param->pkt; + int32_t newLen = 0; + pj_status_t rc = PJ_SUCCESS; + pjmedia_tp_cb_param cbparam; + + pj_assert(zrtp && zrtp->stream_rtp_cb2 && param->pkt); + + // check if this could be a real RTP/SRTP packet. + if ((*buffer & 0xf0) != 0x10) + { + // Could be real RTP, check if we are in secure mode + if (zrtp->srtpReceive == NULL || param->size < 0) + { + pj_memcpy(&cbparam, param, sizeof(cbparam)); + cbparam.user_data = zrtp->stream_user_data; + zrtp->stream_rtp_cb2(&cbparam); + } + else + { + rc = zsrtp_unprotect(zrtp->srtpReceive, param->pkt, param->size, &newLen); + if (rc == 1) + { + zrtp->unprotect++; + param->size = newLen; + pj_memcpy(&cbparam, param, sizeof(cbparam)); + cbparam.user_data = zrtp->stream_user_data; + zrtp->stream_rtp_cb2(&cbparam); + //zrtp->stream_rtp_cb(zrtp->stream_user_data, pkt, + // newLen); + zrtp->unprotect_err = 0; + } + else + { + if (zrtp->cb.show_message) + { + if (rc == -1) + zrtp->cb.show_message(&zrtp->base, zrtp_Warning, zrtp_WarningSRTPauthError); + else + zrtp->cb.show_message(&zrtp->base, zrtp_Warning, zrtp_WarningSRTPreplayError); + } + zrtp->unprotect_err = rc; + /* We failed to decrypt the packet, but forward it regardless to the slave + * transport, it might not have been encrypted after all */ + pj_memcpy(&cbparam, param, sizeof(cbparam)); + cbparam.user_data = zrtp->stream_user_data; + zrtp->stream_rtp_cb2(&cbparam); + } + } + if (!zrtp->started && zrtp->enableZrtp) + pjmedia_transport_zrtp_startZrtp((pjmedia_transport *)zrtp); + + return; + } + + // We assume all other packets are ZRTP packets here. Process + // if ZRTP processing is enabled. Because valid RTP packets are + // already handled we delete any packets here after processing. + if (zrtp->enableZrtp && zrtp->zrtpCtx != NULL) + { + // Get CRC value into crc (see above how to compute the offset) + pj_uint16_t temp = param->size - CRC_SIZE; + pj_uint32_t crc = *(uint32_t*)(buffer + temp); + crc = pj_ntohl(crc); + + if (!zrtp_CheckCksum(buffer, temp, crc)) + { + if (zrtp->cb.show_message) + zrtp->cb.show_message(&zrtp->base, zrtp_Warning, zrtp_WarningCRCmismatch); + return; + } + + pj_uint32_t magic = *(pj_uint32_t*)(buffer + 4); + magic = pj_ntohl(magic); + + // Check if it is really a ZRTP packet, return, no further processing + if (magic != ZRTP_MAGIC) + return; + + // cover the case if the other party sends _only_ ZRTP packets at the + // beginning of a session. Start ZRTP in this case as well. + if (!zrtp->started) + { + pjmedia_transport_zrtp_startZrtp((pjmedia_transport *)zrtp); + } + // this now points beyond the undefined and length field. + // We need them, thus adjust + unsigned char* zrtpMsg = (buffer + 12); + + // store peer's SSRC in host order, used when creating the CryptoContext + zrtp->peerSSRC = *(pj_uint32_t*)(buffer + 8); + zrtp->peerSSRC = pj_ntohl(zrtp->peerSSRC); + zrtp_processZrtpMessage(zrtp->zrtpCtx, zrtpMsg, zrtp->peerSSRC, param->size); + } +} /* This is our RTCP callback, that is called by the slave transport when it * receives RTCP packet. @@ -1256,6 +1359,45 @@ return PJ_SUCCESS; } +/* + * attach2() is called by stream to register callbacks that we should + * call on receipt of RTP and RTCP packets. + */ +static pj_status_t transport_attach2(pjmedia_transport *tp, + pjmedia_transport_attach_param *att_param) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_status_t status; + + /* In this example, we will save the stream information and callbacks + * to our structure, and we will register different RTP/RTCP callbacks + * instead. + */ + pj_assert(zrtp->stream_user_data == NULL); + zrtp->stream_user_data = att_param->user_data; + if (att_param->rtp_cb2) { + zrtp->stream_rtp_cb2 = att_param->rtp_cb2; + } else { + zrtp->stream_rtp_cb = att_param->rtp_cb; + } + zrtp->stream_rtcp_cb = att_param->rtcp_cb; + + att_param->rtp_cb2 = &transport_rtp_cb2; + att_param->rtp_cb = NULL; + att_param->rtcp_cb = &transport_rtcp_cb; + att_param->user_data = zrtp; + + status = pjmedia_transport_attach2(zrtp->slave_tp, att_param); + if (status != PJ_SUCCESS) { + zrtp->stream_user_data = NULL; + zrtp->stream_rtp_cb = NULL; + zrtp->stream_rtp_cb2 = NULL; + zrtp->stream_rtcp_cb = NULL; + return status; + } + + return PJ_SUCCESS; +} --- pjsip/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c 2021-03-19 17:02:04.749861806 +0100 +++ pjsip/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c 2021-03-19 17:03:03.618376942 +0100 @@ -58,9 +58,9 @@ #define MAX_DEV_CNT 8 -#ifndef PJMEDIA_USE_OLD_FFMPEG +//#ifndef PJMEDIA_USE_OLD_FFMPEG # define av_close_input_stream(ctx) avformat_close_input(&ctx) -#endif +//#endif typedef struct ffmpeg_dev_info --- pjsip/pjmedia/build/os-darwinos.mak 2021-03-22 07:33:02.058097009 +0100 +++ pjsip/pjmedia/build/os-darwinos.mak 2021-03-22 07:48:59.147298176 +0100 @@ -139,4 +139,5 @@ export CFLAGS += -DPJMEDIA_SOUND_IMPLEMENTATION=PJMEDIA_SOUND_NULL_SOUND endif +export PJMEDIA_VIDEODEV_OBJS += avf_dev.o --- pjsip/pjmedia/build/os-auto.mak.in 2021-03-26 22:08:01.739146366 +0100 +++ pjsip/pjmedia/build/os-auto.mak.in 2021-03-26 22:07:16.701862482 +0100 @@ -302,7 +302,8 @@ # ifneq (,$(filter $(AC_PJMEDIA_VIDEO),darwin_os)) # Mac and iPhone OS specific, use obj-c -export PJMEDIA_VIDEODEV_OBJS += sdl_dev_m.o +export PJMEDIA_VIDEODEV_OBJS += sdl_dev_m.o avf_dev.o +export CFLAGS += -DPJMEDIA_VIDEO_DEV_HAS_AVF=1 -DPJMEDIA_VIDEO_HAS_VTOOLBOX=1 -DPJMEDIA_HAS_VID_TOOLBOX_CODEC=1 -DPJMEDIA_USE_OLD_FFMPEG=0 else # Other platforms, compile .c export PJMEDIA_VIDEODEV_OBJS += sdl_dev.o --- pjsip/pjsip/src/pjsip/sip_transport_tls.c +++ pjsip/pjsip/src/pjsip/sip_transport_tls.c @@ -88,6 +88,7 @@ struct tls_transport pjsip_transport base; pj_bool_t is_server; pj_str_t remote_name; + pj_str_t tls_name; pj_bool_t is_registered; pj_bool_t is_closing; @@ -162,6 +163,7 @@ static pj_status_t tls_create(struct tls_listener *listener, const pj_sockaddr *local, const pj_sockaddr *remote, const pj_str_t *remote_name, + const pj_str_t *tls_name, pj_grp_lock_t *glock, struct tls_transport **p_tls); @@ -796,6 +798,7 @@ static pj_status_t tls_create( struct tls_listener *listener, const pj_sockaddr *local, const pj_sockaddr *remote, const pj_str_t *remote_name, + const pj_str_t *tls_name, pj_grp_lock_t *glock, struct tls_transport **p_tls) { @@ -839,6 +842,9 @@ static pj_status_t tls_create( struct tls_listener *listener, if (remote_name) pj_strdup(pool, &tls->remote_name, remote_name); + if (tls_name) + pj_strdup(pool, &tls->tls_name, tls_name); + tls->base.key.type = listener->factory.type; pj_sockaddr_cp(&tls->base.key.rem_addr, remote); tls->base.type_name = (char*)pjsip_transport_get_type_name( @@ -1142,7 +1148,11 @@ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, pj_ssl_sock_param ssock_param; pj_sockaddr local_addr; pj_str_t remote_name; + pj_str_t tls_name; pj_status_t status; + pjsip_param *p = NULL; + pjsip_sip_uri *sip_uri = NULL; + pjsip_route_hdr *route_hdr = NULL; /* Sanity checks */ PJ_ASSERT_RETURN(factory && mgr && endpt && rem_addr && @@ -1162,8 +1172,27 @@ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); /* Get remote host name from tdata */ - if (tdata) - remote_name = tdata->dest_info.name; + if (tdata) { + remote_name = tdata->dest_info.name; + /* Get tls_name from sip route if defined */ + route_hdr = (pjsip_route_hdr*)pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL); + if (route_hdr != NULL) { + if (PJSIP_URI_SCHEME_IS_SIPS(route_hdr->name_addr.uri) || + PJSIP_URI_SCHEME_IS_SIP(route_hdr->name_addr.uri)) { + sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(route_hdr->name_addr.uri); + p = sip_uri->other_param.next; + while (p != &sip_uri->other_param) { + const pj_str_t st_name = {"tls_name", 8}; + if (pj_stricmp(&p->name, &st_name) == 0) { + pj_strdup(pool, &tls_name, &p->value); + //tls_name.ptr[tls_name.slen] = '\x00'; + break; + } + p = p->next; + } + } + } + } else pj_bzero(&remote_name, sizeof(remote_name)); @@ -1237,7 +1266,7 @@ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, /* Create the transport descriptor */ status = tls_create(listener, pool, ssock, PJ_FALSE, &local_addr, - rem_addr, &remote_name, glock, &tls); + rem_addr, &remote_name, &tls_name, glock, &tls); if (status != PJ_SUCCESS) return status; @@ -1398,7 +1427,7 @@ static pj_bool_t on_accept_complete2(pj_ssl_sock_t *ssock, */ status = tls_create( listener, NULL, new_ssock, PJ_TRUE, &ssl_info.local_addr, &tmp_src_addr, NULL, - ssl_info.grp_lock, &tls); + NULL, ssl_info.grp_lock, &tls); if (status != PJ_SUCCESS) { if (listener->tls_setting.on_accept_fail_cb) { @@ -1821,8 +1850,11 @@ static pj_bool_t on_connect_complete(pj_ssl_sock_t *ssock, pj_bool_t matched = PJ_FALSE; unsigned i; - /* Remote name may be hostname or IP address */ - if (tls->remote_name.slen) + /* Remote name may be hostname or IP address + * but might as well be defined in tls_name */ + if (tls->tls_name.slen) + remote_name = &tls->tls_name; + else if (tls->remote_name.slen) remote_name = &tls->remote_name; else remote_name = &tls->base.remote_name.host; --- pjsip/aconfigure 2021-04-17 03:33:50.992738038 +0200 +++ pjsip/aconfigure 2021-04-17 03:35:46.528046741 +0200 @@ -8072,7 +8072,7 @@ if test "x$ac_ssl_backend" = "xdarwin"; then $as_echo "#define PJ_HAS_SSL_SOCK 1" >>confdefs.h - $as_echo "#define PJ_SSL_SOCK_IMP PJ_SSL_SOCK_IMP_DARWIN" >>confdefs.h + $as_echo "#define PJ_SSL_SOCK_IMP PJ_SSL_SOCK_IMP_OPENSSL" >>confdefs.h LIBS="$LIBS -framework Security" { $as_echo "$as_me:${as_lineno-$LINENO}: result: Checking if Darwin SSL is available... yes" >&5 --- pjsip/pjsip/src/pjsip/sip_transport_tls.c +++ pjsip/pjsip/src/pjsip/sip_transport_tls.c @@ -1206,7 +1206,10 @@ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, ssock_param.async_cnt = 1; ssock_param.ioqueue = pjsip_endpt_get_ioqueue(listener->endpt); ssock_param.timer_heap = pjsip_endpt_get_timer_heap(listener->endpt); - ssock_param.server_name = remote_name; + if (tls_name.slen) + ssock_param.server_name = tls_name; + else + ssock_param.server_name = remote_name; ssock_param.timeout = listener->tls_setting.timeout; ssock_param.user_data = NULL; /* pending, must be set later */ ssock_param.verify_peer = PJ_FALSE; /* avoid SSL socket closing the socket --- pjsip/pjlib/src/pj/ssl_sock_ossl.c +++ pjsip/pjlib/src/pj/ssl_sock_ossl.c @@ -1702,6 +1702,9 @@ ossl_sock_t *ossock = (ossl_sock_t *)ssock; X509 *x; STACK_OF(X509) *chain; + pj_parsed_time v; + pj_time_val end; + pj_bool_t gmt; pj_assert(ssock->ssl_state == SSL_STATE_ESTABLISHED); @@ -1709,6 +1712,12 @@ x = SSL_get_certificate(ossock->ossl_ssl); if (x) { get_cert_info(ssock->pool, &ssock->local_cert_info, x, PJ_FALSE); + parse_ossl_asn1_time(&end, &gmt, X509_get_notAfter(x)); + + pj_time_decode(&end, &v); + + PJ_LOG(1, (THIS_FILE, "[local TLS certificate] subject:%s | issuer:%s | valid until:%d-%d-%d", ssock->local_cert_info.subject.info.ptr, ssock->local_cert_info.issuer.info.ptr, v.year, v.mon, v.day)); + /* Don't free local's X509! */ } else { pj_bzero(&ssock->local_cert_info, sizeof(pj_ssl_cert_info)); @@ -1718,6 +1727,12 @@ x = SSL_get_peer_certificate(ossock->ossl_ssl); if (x) { get_cert_info(ssock->pool, &ssock->remote_cert_info, x, PJ_TRUE); + parse_ossl_asn1_time(&end, &gmt, X509_get_notAfter(x)); + + pj_time_decode(&end, &v); + + PJ_LOG(1, (THIS_FILE, "[remote TLS certificate] subject:%s | issuer:%s | valid until:%d-%d-%d | host:%s:%d", ssock->remote_cert_info.subject.info.ptr, ssock->remote_cert_info.issuer.info.ptr, v.year, v.mon, v.day, pj_inet_ntoa(ssock->rem_addr.ipv4.sin_addr), ssock->rem_addr.ipv4.sin_port)); + /* Free peer's X509 */ X509_free(x); } else { @@ -1766,6 +1770,7 @@ static void ssl_set_peer_name(pj_ssl_sock_t *ssock) /* Set server name to connect */ if (ssock->param.server_name.slen) { /* Server name is null terminated already */ + PJ_LOG(1,(ssock->pool->obj_name, "[SSL_set_tlsext_host_name] server_name:%s", ssock->param.server_name.ptr)); if (!SSL_set_tlsext_host_name(ossock->ossl_ssl, ssock->param.server_name.ptr)) { diff --git a/deps/patches/002_zsrtp.patch b/deps/patches/2.11/002_zsrtp.patch similarity index 100% rename from deps/patches/002_zsrtp.patch rename to deps/patches/2.11/002_zsrtp.patch diff --git a/deps/patches/003_pjsip_tls_log_fix.patch b/deps/patches/2.11/003_pjsip_tls_log_fix.patch similarity index 100% rename from deps/patches/003_pjsip_tls_log_fix.patch rename to deps/patches/2.11/003_pjsip_tls_log_fix.patch diff --git a/deps/patches/003_vpx.patch b/deps/patches/2.11/003_vpx.patch similarity index 100% rename from deps/patches/003_vpx.patch rename to deps/patches/2.11/003_vpx.patch diff --git a/deps/patches/004_mac_audio_device_fix.patch b/deps/patches/2.11/004_mac_audio_device_fix.patch similarity index 100% rename from deps/patches/004_mac_audio_device_fix.patch rename to deps/patches/2.11/004_mac_audio_device_fix.patch diff --git a/deps/patches/006_fix_uint_zrtp.patch b/deps/patches/2.11/006_fix_uint_zrtp.patch similarity index 100% rename from deps/patches/006_fix_uint_zrtp.patch rename to deps/patches/2.11/006_fix_uint_zrtp.patch diff --git a/deps/patches/m1/001_pjsip_210.patch b/deps/patches/m1/001_pjsip_210.patch index 4e2c69ab..4791e56b 100644 --- a/deps/patches/m1/001_pjsip_210.patch +++ b/deps/patches/m1/001_pjsip_210.patch @@ -1,8103 +1,8078 @@ diff -ruN pjproject-2.10/base_rev pjsip/base_rev --- pjproject-2.10/base_rev 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/base_rev 2021-02-07 00:21:58.163902742 +0100 @@ -0,0 +1 @@ -+210 ++211 diff -ruN pjproject-2.10/pjlib/src/pj/os_core_unix.c pjsip/pjlib/src/pj/os_core_unix.c --- pjproject-2.10/pjlib/src/pj/os_core_unix.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjlib/src/pj/os_core_unix.c 2021-02-06 23:59:38.145916811 +0100 @@ -37,6 +37,11 @@ #if defined(PJ_HAS_SEMAPHORE_H) && PJ_HAS_SEMAPHORE_H != 0 # include +# if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 +# include +# include +# include +# endif #endif #include // getpid() @@ -107,7 +112,11 @@ #if defined(PJ_HAS_SEMAPHORE) && PJ_HAS_SEMAPHORE != 0 struct pj_sem_t { +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + semaphore_t *sem; +#else sem_t *sem; +#endif char obj_name[PJ_MAX_OBJ_NAME]; }; #endif /* PJ_HAS_SEMAPHORE */ @@ -1569,35 +1578,16 @@ PJ_ASSERT_RETURN(sem, PJ_ENOMEM); #if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 - /* MacOS X doesn't support anonymous semaphore */ { - char sem_name[PJ_GUID_MAX_LENGTH+1]; - pj_str_t nam; - - /* We should use SEM_NAME_LEN, but this doesn't seem to be - * declared anywhere? The value here is just from trial and error - * to get the longest name supported. - */ -# define MAX_SEM_NAME_LEN 23 - - /* Create a unique name for the semaphore. */ - if (PJ_GUID_STRING_LENGTH <= MAX_SEM_NAME_LEN) { - nam.ptr = sem_name; - pj_generate_unique_string(&nam); - sem_name[nam.slen] = '\0'; - } else { - pj_create_random_string(sem_name, MAX_SEM_NAME_LEN); - sem_name[MAX_SEM_NAME_LEN] = '\0'; - } - - /* Create semaphore */ - sem->sem = sem_open(sem_name, O_CREAT|O_EXCL, S_IRUSR|S_IWUSR, - initial); - if (sem->sem == SEM_FAILED) - return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); - - /* And immediately release the name as we don't need it */ - sem_unlink(sem_name); + kern_return_t err; + sem->sem = PJ_POOL_ALLOC_T(pool, semaphore_t); + err = semaphore_create(mach_task_self(), sem->sem, SYNC_POLICY_FIFO, initial); + if (err != KERN_SUCCESS) { + if (err == KERN_RESOURCE_SHORTAGE) + return PJ_RETURN_OS_ERROR(ENOMEM); + else + return PJ_RETURN_OS_ERROR(EINVAL); + } } #else sem->sem = PJ_POOL_ALLOC_T(pool, sem_t); @@ -1633,6 +1623,7 @@ { #if PJ_HAS_THREADS int result; -+ int error; ++ //int error; PJ_CHECK_STACK(); PJ_ASSERT_RETURN(sem, PJ_EINVAL); @@ -1640,6 +1631,20 @@ PJ_LOG(6, (sem->obj_name, "Semaphore: thread %s is waiting", pj_thread_this()->obj_name)); +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + { + do + result = semaphore_wait(*(sem->sem)); + while (result == KERN_ABORTED); + + if (result == KERN_SUCCESS) { + result = error = 0; + } else { + result = -1; + error = EINVAL; + } + } +#else result = sem_wait( sem->sem ); if (result == 0) { @@ -1648,12 +1653,14 @@ } else { PJ_LOG(6, (sem->obj_name, "Semaphore: thread %s FAILED to acquire", pj_thread_this()->obj_name)); + error = pj_get_native_os_error(); } +#endif if (result == 0) return PJ_SUCCESS; else - return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + return PJ_RETURN_OS_ERROR(error); #else pj_assert( sem == (pj_sem_t*) 1 ); return PJ_SUCCESS; @@ -1667,20 +1674,45 @@ { #if PJ_HAS_THREADS int result; + int error; PJ_CHECK_STACK(); PJ_ASSERT_RETURN(sem, PJ_EINVAL); +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + { + mach_timespec_t interval; + kern_return_t err; + + interval.tv_sec = 0; + interval.tv_nsec = 0; + + err = semaphore_timedwait(*(sem->sem), interval); + if (err == KERN_SUCCESS) { + result = error = 0; + } else if (err == KERN_OPERATION_TIMED_OUT) { + result = -1; + error = EAGAIN; + } else { + result = -1; + error = EINVAL; + } + } +#else result = sem_trywait( sem->sem ); if (result == 0) { PJ_LOG(6, (sem->obj_name, "Semaphore acquired by thread %s", pj_thread_this()->obj_name)); + } else { + error = pj_get_native_os_error(); } +#endif + if (result == 0) return PJ_SUCCESS; else - return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + return PJ_RETURN_OS_ERROR(error); #else pj_assert( sem == (pj_sem_t*)1 ); return PJ_SUCCESS; @@ -1694,14 +1726,30 @@ { #if PJ_HAS_THREADS int result; + int error; PJ_LOG(6, (sem->obj_name, "Semaphore released by thread %s", pj_thread_this()->obj_name)); +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + { + kern_return_t err; + err = semaphore_signal(*(sem->sem)); + if (err == KERN_SUCCESS) { + result = error = 0; + } else { + result = -1; + error = EINVAL; + } + } +#else result = sem_post( sem->sem ); + if (result != 0) + error = pj_get_native_os_error(); +#endif if (result == 0) return PJ_SUCCESS; else - return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + return PJ_RETURN_OS_ERROR(error); #else pj_assert( sem == (pj_sem_t*) 1); return PJ_SUCCESS; @@ -1715,6 +1763,7 @@ { #if PJ_HAS_THREADS int result; + int error; PJ_CHECK_STACK(); PJ_ASSERT_RETURN(sem, PJ_EINVAL); @@ -1722,15 +1771,26 @@ PJ_LOG(6, (sem->obj_name, "Semaphore destroyed by thread %s", pj_thread_this()->obj_name)); #if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 - result = sem_close( sem->sem ); + { + kern_return_t err; + err = semaphore_destroy(mach_task_self(), *(sem->sem)); + if (err == KERN_SUCCESS) { + result = error = -1; + } else { + result = -1; + error = EINVAL; + } + } #else result = sem_destroy( sem->sem ); + if (result != 0) + error = pj_get_native_os_error(); #endif if (result == 0) return PJ_SUCCESS; else - return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + return PJ_RETURN_OS_ERROR(error); #else pj_assert( sem == (pj_sem_t*) 1 ); return PJ_SUCCESS; --- pjproject-2.10/pjmedia/include/pjmedia/config_auto.h.in 2021-02-05 09:00:45.241108718 +0100 +++ pjsip/pjmedia/include/pjmedia/config_auto.h.in 2021-02-22 21:46:52.232215546 +0100 @@ -36,6 +36,22 @@ #undef PJMEDIA_HAS_G711_CODEC #endif +#define PJMEDIA_HAS_VIDEO 1 +#define PJMEDIA_HAS_OPUS_CODEC 1 +#define PJMEDIA_HAS_VPX_CODEC_VP9 1 +#define PJMEDIA_HAS_FFMPEG_VID_CODEC 1 +#define PJMEDIA_HAS_LIBWEBRTC 1 +#define PJMEDIA_HAS_WEBRTC_AEC 1 + +#if defined(PJMEDIA_VIDEO_DEV_HAS_DARWIN) && (PJMEDIA_VIDEO_DEV_HAS_DARWIN != 0) +#define PJMEDIA_VIDEO_DEV_HAS_AVF 1 +#define PJMEDIA_VIDEO_HAS_VTOOLBOX 1 +#define PJMEDIA_HAS_VID_TOOLBOX_CODEC 1 +#endif + +#ifdef PJMEDIA_USE_OLD_FFMPEG +#undef PJMEDIA_USE_OLD_FFMPEG +#endif #endif /* __PJMEDIA_CONFIG_AUTO_H_ */ diff -ruN pjproject-2.10/pjmedia/build/Makefile pjproject-2.10/pjmedia/build/Makefile --- pjproject-2.10/pjmedia/build/Makefile 2021-02-05 09:00:45.241108718 +0100 +++ pjsip/pjmedia/build/Makefile 2021-02-22 21:46:52.232215546 +0100 @@ -72,8 +72,8 @@ sound_legacy.o sound_port.o stereo_port.o stream_common.o \ stream.o stream_info.o tonegen.o transport_adapter_sample.o \ transport_ice.o transport_loop.o transport_srtp.o transport_udp.o \ - types.o vid_codec.o vid_codec_util.o \ - vid_port.o vid_stream.o vid_stream_info.o vid_conf.o \ + transport_zrtp.o types.o vid_codec.o vid_codec_util.o mixer_port.o \ + vid_port.o vid_stream.o vid_tee.o vid_stream_info.o vid_conf.o \ wav_player.o wav_playlist.o wav_writer.o wave.o \ wsola.o audiodev.o videodev.o @@ -106,7 +106,7 @@ export PJMEDIA_VIDEODEV_SRCDIR = ../src/pjmedia-videodev export PJMEDIA_VIDEODEV_OBJS += videodev.o errno.o avi_dev.o ffmpeg_dev.o \ colorbar_dev.o v4l2_dev.o opengl_dev.o \ - util.o + util.o fb_dev.o null_dev.o export PJMEDIA_VIDEODEV_CFLAGS += $(_CFLAGS) export PJMEDIA_VIDEODEV_CXXFLAGS += $(_CXXFLAGS) export PJMEDIA_VIDEODEV_LDFLAGS += $(PJMEDIA_LDLIB) \ diff -ruN pjproject-2.10/pjmedia/include/pjmedia/event.h pjsip/pjmedia/include/pjmedia/event.h --- pjproject-2.10/pjmedia/include/pjmedia/event.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia/event.h 2021-02-06 16:57:17.374166159 +0100 @@ -83,6 +83,11 @@ PJMEDIA_EVENT_KEYFRAME_MISSING = PJMEDIA_FOURCC('I', 'F', 'R', 'M'), /** + * Remote video decoder asked for a keyframe. + */ + PJMEDIA_EVENT_KEYFRAME_REQUESTED = PJMEDIA_FOURCC('I', 'F', 'R', 'R'), + + /** * Video orientation has been changed event. */ PJMEDIA_EVENT_ORIENT_CHANGED = PJMEDIA_FOURCC('O', 'R', 'N', 'T'), diff -ruN pjproject-2.10/pjmedia/include/pjmedia/format.h pjsip/pjmedia/include/pjmedia/format.h --- pjproject-2.10/pjmedia/include/pjmedia/format.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia/format.h 2021-02-06 18:30:18.321176790 +0100 @@ -97,6 +97,7 @@ /** * 32bit RGB with alpha channel */ + PJMEDIA_FORMAT_ARGB = PJMEDIA_FORMAT_PACK('A', 'R', 'G', 'B'), PJMEDIA_FORMAT_RGBA = PJMEDIA_FORMAT_PACK('R', 'G', 'B', 'A'), PJMEDIA_FORMAT_BGRA = PJMEDIA_FORMAT_PACK('B', 'G', 'R', 'A'), diff -ruN pjproject-2.10/pjmedia/include/pjmedia/mixer_port.h pjsip/pjmedia/include/pjmedia/mixer_port.h --- pjproject-2.10/pjmedia/include/pjmedia/mixer_port.h 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia/mixer_port.h 2021-02-06 18:42:19.161906996 +0100 @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010 AG Projects + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __PJMEDIA_MIXER_PORT_H__ +#define __PJMEDIA_MIXER_PORT_H__ + +/** + * @file mixer_port.h + * @brief Mixer media port. + */ +#include + + + +/** + * @defgroup PJMEDIA_MIXER_PORT Mixer Port + * @ingroup PJMEDIA_PORT + * @brief The second simplest type of media port which forwards the frames it + * gets unchanged. + * @{ + */ + + +PJ_BEGIN_DECL + + +/** + * Create Mixer port. + * + * @param pool Pool to allocate memory. + * @param sampling_rate Sampling rate of the port. + * @param channel_count Number of channels. + * @param samples_per_frame Number of samples per frame. + * @param bits_per_sample Number of bits per sample. + * @param p_port Pointer to receive the port instance. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_mixer_port_create(pj_pool_t *pool, + unsigned sampling_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + pjmedia_port **p_port); + + +PJ_END_DECL + +/** + * @} + */ + + +#endif /* __PJMEDIA_MIXER_PORT_H__ */ diff -ruN pjproject-2.10/pjmedia/include/pjmedia/rtcp.h pjsip/pjmedia/include/pjmedia/rtcp.h --- pjproject-2.10/pjmedia/include/pjmedia/rtcp.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia/rtcp.h 2021-02-06 18:32:46.933482520 +0100 @@ -256,6 +256,8 @@ pjmedia_rtcp_stat stat; /**< Bidirectional stream stat. */ + pj_bool_t keyframe_requested; /** Set to true when RTCP PLI is received */ + #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) /** * Specify whether RTCP XR processing is enabled on this session. @@ -462,6 +464,23 @@ pj_size_t *length, const pj_str_t *reason); +/** + * Build an RTCP PLI packet. This packet can be appended to other RTCP + * packets, e.g: RTCP RR/SR, to compose a compound RTCP packet. + * + * @param session The RTCP session. + * @param buf The buffer to receive RTCP PLI packet. + * @param length On input, it will contain the buffer length. + * On output, it will contain the generated RTCP PLI + * packet length. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_rtcp_build_rtcp_pli( + pjmedia_rtcp_session *session, + void *buf, + pj_size_t *length); + /** * Call this function if RTCP XR needs to be enabled/disabled in the diff -ruN pjproject-2.10/pjmedia/include/pjmedia/signatures.h pjsip/pjmedia/include/pjmedia/signatures.h --- pjproject-2.10/pjmedia/include/pjmedia/signatures.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia/signatures.h 2021-02-06 18:33:45.139162846 +0100 @@ -153,6 +153,7 @@ #define PJMEDIA_SIG_PORT_ECHO PJMEDIA_SIG_CLASS_PORT_AUD('E','C') #define PJMEDIA_SIG_PORT_MEM_CAPTURE PJMEDIA_SIG_CLASS_PORT_AUD('M','C') #define PJMEDIA_SIG_PORT_MEM_PLAYER PJMEDIA_SIG_CLASS_PORT_AUD('M','P') +#define PJMEDIA_SIG_PORT_MIXER PJMEDIA_SIG_CLASS_PORT_AUD('M','X') #define PJMEDIA_SIG_PORT_NULL PJMEDIA_SIG_CLASS_PORT_AUD('N','U') #define PJMEDIA_SIG_PORT_RESAMPLE PJMEDIA_SIG_CLASS_PORT_AUD('R','E') #define PJMEDIA_SIG_PORT_SPLIT_COMB PJMEDIA_SIG_CLASS_PORT_AUD('S','C') diff -ruN pjproject-2.10/pjmedia/include/pjmedia/sound_port.h pjsip/pjmedia/include/pjmedia/sound_port.h --- pjproject-2.10/pjmedia/include/pjmedia/sound_port.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia/sound_port.h 2021-02-06 18:34:38.880711750 +0100 @@ -344,6 +344,16 @@ /** + * Reset the EC state in the sound port. + * + * @param snd_port The sound device port. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_snd_port_reset_ec_state(pjmedia_snd_port *snd_port); + + +/** * Connect a port to the sound device port. If the sound device port has a * sound recorder device, then this will start periodic function call to * the port's put_frame() function. If the sound device has a sound player diff -ruN pjproject-2.10/pjmedia/include/pjmedia/transport_ice.h pjsip/pjmedia/include/pjmedia/transport_ice.h --- pjproject-2.10/pjmedia/include/pjmedia/transport_ice.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia/transport_ice.h 2021-02-06 17:09:28.151078775 +0100 @@ -74,6 +74,28 @@ pj_status_t status, void *user_data); + /** + * This callback will be called when ICE state changes. + * + * @param tp PJMEDIA ICE transport. + * @param prev Previous state. + * @param curr Current state. + */ + void (*on_ice_state)(pjmedia_transport *tp, + pj_ice_strans_state prev, + pj_ice_strans_state curr); + + /** + * This callback will be called when ICE is stopped. + * + * @param tp PJMEDIA ICE transport. + * @param reason Reason for stopping ICE. + * @param err Error code + */ + void (*on_ice_stop)(pjmedia_transport *tp, + char *reason, + pj_status_t err); + } pjmedia_ice_cb; @@ -237,6 +259,17 @@ pjmedia_transport **p_tp); /** + * Return the ICE stream transport associated with this PJMEDIA transport + * + * @param tp Media transport instance. + * + * @return Pointer to the pj_ice_strans instance associated with this + * media transport. + */ +PJ_DECL(pj_ice_strans*) pjmedia_ice_get_strans(pjmedia_transport *tp); + + +/** * Get the group lock for the ICE media transport. * * @param tp The ICE media transport. diff -ruN pjproject-2.10/pjmedia/include/pjmedia/transport_zrtp.h pjsip/pjmedia/include/pjmedia/transport_zrtp.h --- pjproject-2.10/pjmedia/include/pjmedia/transport_zrtp.h 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia/transport_zrtp.h 2021-02-06 16:42:58.084103561 +0100 @@ -0,0 +1,647 @@ +/* $Id$ */ +/* + Copyright (C) 2010 Werner Dittmann + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef __PJMEDIA_TRANSPORT_ZRTP_H__ +#define __PJMEDIA_TRANSPORT_ZRTP_H__ + +/** + * @file transport_zrtp.h + * @brief ZRTP Media Transport Adapter + */ + +/* transport.h includes types.h -> config.h -> config_auto.h */ +#include + +#include "../../third_party/zsrtp/zrtp/zrtp/libzrtpcpp/ZrtpCWrapper.h" + +/** + * @defgroup PJMEDIA_TRANSPORT_ZRTP ZRTP Transport Adapter + * @brief This the ZRTP transport adapter. + * @{ + * + * PJMEDIA extension to support GNU ZRTP. + * + * ZRTP was developed by Phil Zimmermann and provides functions to + * negotiate keys and other necessary data (crypto data) to set-up + * the Secure RTP (SRTP) crypto context. Refer to Phil's ZRTP + * specification at his Zfone + * project site to get more detailed information about the + * capabilities of ZRTP. + * + * Short overview of the ZRTP implementation + * + * ZRTP is a specific protocol to negotiate encryption algorithms + * and the required key material. ZRTP uses a RTP session to + * exchange its protocol messages. Thus ZRTP is independent of any + * signaling protocol like SIP, XMPP and alike. + * + * A complete GNU ZRTP implementation consists of two parts, the + * GNU ZRTP core and some specific code that binds the GNU ZRTP core to + * the underlying RTP/SRTP stack and the operating system: + *
    + *
  • + * The GNU ZRTP core is independent of a specific RTP/SRTP + * stack and the operationg system and consists of the ZRTP + * protocol state engine, the ZRTP protocol messages, and the + * GNU ZRTP engine. The GNU ZRTP engine provides methods to + * setup ZRTP message and to analyze received ZRTP messages, + * to compute the crypto data required for SRTP, and to + * maintain the required hashes and HMAC. + *
  • + *
  • + * The second part of an implementation is specific + * glue code the binds the GNU ZRTP core to the + * actual RTP/SRTP implementation and other operating system + * specific services such as timers, mutexes. + *
  • + *
+ * + * The GNU ZRTP core uses callback methods (refer to + * zrtp_Callback) to access RTP/SRTP or operating specific methods, + * for example to send data via the RTP stack, to access + * timers, provide mutex handling, and to report events to the + * application. + * + * The PJMEDIA ZRTP transport + * + * ZRTP transport implements code that is specific to the pjmedia + * implementation. ZRTP transport also implements the specific code to + * provide the mutex and timeout handling to the GNU ZRTP + * core. Both, the mutex and the timeout handling, use the pjlib + * library to stay independent of the operating + * seystem. + * + * To perform its tasks ZRTP transport + *
    + *
  • implements the pjmedia transport functions and callbacks. + *
  • + *
  • implements the zrtp_Callbacks methods to provide + * access and other specific services (timer, mutex) to GNU + * ZRTP + *
  • + *
  • provides ZRTP specific methods that applications may use + * to control and setup GNU ZRTP + *
  • + *
  • can register and use an application specific callback + * class (refer to zrtp_UserCallbacks) + *
  • + *
+ * + * After instantiating a GNU ZRTP session (see below for a short + * example) applications may use the methods of + * ZRTP transport and the ZRTP engine to control and setup GNU ZRTP, + * for example enable or disable ZRTP processing or getting ZRTP status + * information. + * + * GNU ZRTP defines zrtp_UserCallback methods structure that an application + * may use and register with ZRTP transport. GNU ZRTP and ZRTP transport + * use the zrtp_UserCallback methods to report ZRTP events to the + * application. The application may display this information to + * the user or act otherwise. + * + * The following figure depicts the relationships between + * ZRTP transport, pjmedia RTP implementation, the GNU ZRTP core, + * SRTP and an application that provides zrtp_UserCallback methods. + * + @verbatim + +-----------+ + | | + | SRTP-ZRTP | + | | + +-----------+ + |C Wrapper | + +-----+-----+ + | + | uses + | + +-----------------+ +-------+--------+ +-+-----------------+ + | App (pjsua) | | | |C| | + | creates a | uses | transport_zrtp | uses | | GNU ZRTP | + | ZRTP transport +------+ implements +------+W| core | + | and implements | | zrtp_Callback | |r| implementation | + |zrtp_UserCallback| | | |a| (ZRtp et al) | + +-----------------+ +----------------+ |p| | + +-+-----------------+ + +@endverbatim + * + * The following short code snippet shows how to use ZRTP transport + * + * @code + * + * #include + * ... + * // Create media transport + * status = pjmedia_transport_udp_create(med_endpt, NULL, local_port, + * 0, &transport); + * if (status != PJ_SUCCESS) + * return status; + * + * status = pjmedia_transport_zrtp_create(med_endpt, NULL, transport, + * &zrtp_tp); + * app_perror(THIS_FILE, "Error creating zrtp", status); + * transport = zrtp_tp; + * if (dir == PJMEDIA_DIR_ENCODING) + * pjmedia_transport_zrtp_initialize(transport, "testenc.zid", 1, NULL); + * else + * pjmedia_transport_zrtp_initialize(transport, "testdec.zid", 1, NULL); + * ... + * @endcode + * + */ + +#define PJMEDIA_TRANSPORT_TYPE_ZRTP PJMEDIA_TRANSPORT_TYPE_USER+2 + +PJ_BEGIN_DECL + +/** + * ZRTP option. + */ +typedef enum pjmedia_zrtp_use +{ + /** When this flag is specified, ZRTP will be disabled. */ + PJMEDIA_NO_ZRTP = 1, + + /** When this flag is specified, PJSUA-LIB creates a ZRTP transport + * call calls back the applicaion for further process if callback is + * set. + */ + PJMEDIA_CREATE_ZRTP = 2 + +} pjmedia_zrtp_use; + +/** + * This structure specifies ZRTP transport specific info. This will fit + * into \a buffer field of pjmedia_transport_specific_info. + */ +typedef struct pjmedia_zrtp_info +{ + /** + * Specify whether the ZRTP transport is active for this session. + */ + pj_bool_t active; + + /** + * Specify the cipher being used. + */ + char cipher[128]; + +} pjmedia_zrtp_info; + +/** + * Application callback methods. + * + * The RTP stack specific part of GNU ZRTP uses these callback methods + * to report ZRTP events to the application. Thus the application that + * instantiates the RTP stack shall implement these methods and show these + * inforemation to the user. + * + * CAVEAT
+ * All user callback methods run in the context of the RTP thread. Thus + * it is of paramount importance to keep the execution time of the methods + * as short as possible. + * + * @author Werner Dittmann + */ +typedef struct pjmedia_zrtp_cb +{ + /** + * Inform user interface that security is active now. + * + * ZRTP calls this method if the sender and the receiver are + * in secure mode now. + * + * @param cipher + * Name and mode of cipher used to encrypt the SRTP stream + */ + void (*secure_on)(pjmedia_transport *tp, char* cipher); + + /** + * Inform user interface that security is not active any more. + * + * ZRTP calls this method if either the sender or the receiver + * left secure mode. + * + */ + void (*secure_off)(pjmedia_transport *tp); + + /** + * Show the Short Authentication String (SAS) on user interface. + * + * ZRTP calls this method to display the SAS and inform about the SAS + * verification status. The user interface shall enable a SAS verfication + * button (or similar UI element). The user shall click on this UI + * element after he/she confirmed the SAS code with the partner. + * + * @param sas + * The string containing the SAS. + * @param verified + * If verified is true then SAS was verified by both + * parties during a previous call, otherwise it is set to false. + */ + void (*show_sas)(pjmedia_transport *tp, char* sas, int32_t verified); + + /** + * Inform the user that ZRTP received "go clear" message from its peer. + * + * On receipt of a go clear message the user is requested to confirm + * a switch to unsecure (clear) modus. Until the user confirms ZRTP + * (and the underlying RTP) does not send any data. + * + */ + void (*confirm_go_clear)(pjmedia_transport *tp); + + /** + * Show some information to user. + * + * ZRTP calls this method to display some information to the user. + * Along with the message ZRTP provides a severity indicator that + * defines: Info, Warning, Error, and Alert. Refer to the + * MessageSeverity enum in ZrtpCodes.h. The + * UI may use this indicator to highlight messages or alike. + * + * @param sev + * Severity of the message. + * @param subCode + * The subcode identifying the reason. + */ + void (*show_message)(pjmedia_transport *tp, int32_t sev, int32_t subCode); + + /** + * ZRTP transport calls this if the negotiation failed. + * + * ZRTPQueue calls this method in case ZRTP negotiation failed. The + * parameters show the severity as well as some explanatory text. + * Refer to the MessageSeverity enum above. + * + * @param severity + * This defines the message's severity + * @param subCode + * The subcode identifying the reason. + */ + void (*negotiation_failed)(pjmedia_transport *tp, int32_t severity, int32_t subCode); + + /** + * ZRTP transport calls this method if the other side does not support ZRTP. + * + * If the other side does not answer the ZRTP Hello packets then + * ZRTP calls this method. + * + */ + void (*not_supported_by_other)(pjmedia_transport *tp); + + /** + * ZRTP transport calls this method to inform about a PBX enrollment request. + * + * Please refer to chapter 8.3 ff to get more details about PBX enrollment + * and SAS relay. + * + * @param info + * Give some information to the user about the PBX requesting an + * enrollment. + */ + void (*ask_enrollment)(pjmedia_transport *tp, int32_t info); + + /** + * ZRTP transport calls this method to inform about PBX enrollment result. + * + * Informs the use about the acceptance or denial of an PBX enrollment + * request + * + * @param info + * Give some information to the user about the result of an + * enrollment. + */ + void (*inform_enrollment)(pjmedia_transport *tp, int32_t info); + + /** + * ZRTP transport calls this method to request a SAS signature. + * + * After ZRTP core was able to compute the Short Authentication String + * (SAS) it calls this method. The client may now use an approriate + * method to sign the SAS. The client may use + * setSignatureData() of ZrtpQueue to store the signature + * data an enable signature transmission to the other peer. Refer + * to chapter 8.2 of ZRTP specification. + * + * @param sas + * The SAS string to sign. + * @see ZrtpQueue#setSignatureData + * + */ + void (*sign_sas)(pjmedia_transport *tp, uint8_t* sas); + + /** + * ZRTP transport calls this method to request a SAS signature check. + * + * After ZRTP received a SAS signature in one of the Confirm packets it + * call this method. The client may use getSignatureLength() + * and getSignatureData()of ZrtpQueue to get the signature + * data and perform the signature check. Refer to chapter 8.2 of ZRTP + * specification. + * + * If the signature check fails the client may return false to ZRTP. In + * this case ZRTP signals an error to the other peer and terminates + * the ZRTP handshake. + * + * @param sas + * The SAS string that was signed by the other peer. + * @return + * true if the signature was ok, false otherwise. + * + */ + int32_t (*check_sas_signature)(pjmedia_transport *tp, uint8_t* sas); +} pjmedia_zrtp_cb; + + +/** + * Create the transport adapter, specifying the underlying transport to be + * used to send and receive RTP/RTCP packets. + * + * @param endpt The media endpoint. + * @param timer_heap The heap where timers will be scheduled. + * @param transport The underlying media transport to send and receive + * RTP/RTCP packets. + * @param p_tp Pointer to receive the media transport instance. + * + * @param close_slave + * Close the slave transport on transport_destroy. PJSUA-LIB + * sets this to PJ_FALSE because it takes care of this. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjmedia_transport_zrtp_create( pjmedia_endpt *endpt, + pj_timer_heap_t *timer_heap, + pjmedia_transport *transport, + pjmedia_transport **p_tp, + pj_bool_t close_slave); + +/* + * Implement the specific ZRTP transport functions + */ + +/** + * Initialize the ZRTP transport. + * + * Before an application can use ZRTP it has to initialize the + * ZRTP implementation. This method opens a file that contains ZRTP specific + * information such as the applications ZID (ZRTP id) and its + * retained shared secrets. + * + * Before an application initializes the ZRTP it may use ZRTP functions + * to set specific configuration data. See the relevant documentation + * in @c ZrtpCWrapper.h . The application can peform this after + * it created transport_zrtp. + * + * If one application requires several ZRTP sessions all + * sessions use the same timeout thread and use the same ZID + * file. Therefore an application does not need to do any + * synchronisation regading ZID files or timeouts. This is + * managed by the ZRTP implementation. + * + * The current implementation of ZRTP transport does not support + * different ZID files for one application instance. This + * restriction may be removed in later versions. + * + * The application may specify its own ZID file name. If no + * ZID file name is specified it defaults to + * $HOME/.GNUccRTP.zid if the HOME + * environment variable is set. If it is not set the current + * directory is used. + * + * If the method could set up the timeout thread and open the ZID + * file then it enables ZRTP processing and returns. + * + * @param tp + * Pointer to the ZRTP transport data as returned by + * @c pjmedia_transport_zrtp_create. + * + * @param zidFilename + * The name of the ZID file, can be a relative or absolut + * filename. + * + * @param autoEnable + * if set to true the method automatically sets enableZrtp to + * true. This enables the ZRTP auto-sense mode. + * + * @param zrtp_cb + * Pointer the application's ZRTP callbacks structure. Setting + * a NULL switches off the user callbacks + * @return + * PJ_SUCCESS on success, ZRTP processing enabled, other codes + * leave ZRTP processing disabled. + * + */ +PJ_DECL(pj_status_t) pjmedia_transport_zrtp_initialize(pjmedia_transport *tp, + const char *zidFilename, + pj_bool_t autoEnable, + pjmedia_zrtp_cb *zrtp_cb); +/** + * Enable or disable ZRTP processing. + * + * Call this method to enable or disable ZRTP processing after + * calling pjmedia_transport_zrtp_initialize with the + * parameter @c autoEnable set to false. This can be done before + * using a RTP session or at any time during a RTP session. + * + * Existing SRTP sessions or currently active ZRTP processing will + * not be stopped or disconnected. + * + * If the application enables ZRTP then: + *
    + *
  • ZRTP transport starts to send ZRTP Hello packets after at least + * one RTP packet was sent and received on the associated RTP + * session. Thus if an application enables ZRTP and ZRTP transport + * detects traffic on the RTP session then ZRTP transport automatically + * starts the ZRTP protocol. This automatic start is convenient + * for applications that negotiate RTP parameters and set up RTP + * sessions but the actual RTP traffic starts some time later. + *
  • + *
  • ZRTP transport analyses incoming packets to detect ZRTP + * messages. If ZRTP was started, either via automatic start (see + * above) or explicitly via @c zrtp_startZrtp, then ZrtpQueue + * forwards ZRTP packets to the GNU ZRTP core. + *
+ * + * @param tp + * Pointer to the ZRTP transport data as returned by + * @c pjmedia_transport_zrtp_create. + * + * @param onOff + * @c 1 to enable ZRTP, @c 0 to disable ZRTP + */ +PJ_DECL(void) pjmedia_transport_zrtp_setEnableZrtp(pjmedia_transport *tp, pj_bool_t onOff); + +/** + * Return the state of ZRTP enable state. + * + * @param tp + * Pointer to the ZRTP transport data as returned by + * @c pjmedia_transport_zrtp_create. + * + * @return @c true if ZRTP processing is enabled, @c false + * otherwise. + */ +PJ_DECL(pj_bool_t) pjmedia_transport_zrtp_isEnableZrtp(pjmedia_transport *tp); + +/** + * Starts the ZRTP protocol engine. + * + * Applications may call this method to immediatly start the ZRTP protocol + * engine any time after initializing ZRTP and setting optinal parameters, + * for example client id or multi-stream parameters. + * + * If the application does not call this method but sucessfully initialized + * the ZRTP engine using @c pjmedia_transport_zrtp_initialize then ZRTP may + * also start, depending on the autoEnable parameter. + * + * @param tp + * Pointer to the ZRTP transport data as returned by + * @c pjmedia_transport_zrtp_create. + * + * @see pjmedia_transport_zrtp_initialize + */ +PJ_DECL(void) pjmedia_transport_zrtp_startZrtp(pjmedia_transport *tp); + +/** + * Stops the ZRTP protocol engine. + * + * Applications call this method to stop the ZRTP protocol + * engine. The ZRTP transport can not start or process any ZRTP + * negotiations. + * + * This call does not deactivate SRTP processing of ZRTP transport, thus + * the ZRTP transport still encrypts/decrypts data via SRTP. + * + * @param tp + * Pointer to the ZRTP transport data as returned by + * @c pjmedia_transport_zrtp_create. + * + */ +PJ_DECL(void) pjmedia_transport_zrtp_stopZrtp(pjmedia_transport *tp); + +/** + * Set the local SSRC in case of receive-only sessions. + * + * Receiver-only RTP sessions never send RTP packets, thus ZRTP cannot learn + * the local (sender) SSRC. ZRTP requires the SSRC to bind the RTP session + * to the SRTP and its handshake. In this case the application shall generate + * a SSRC value and set it. + * + * Usually an application knows if a specific RTP session is receive-only, for + * example by inspecting and parsing the SDP data. + * + * If the application later decides to switch this RTP session to full-duplex + * mode (send and receive) it shall use the generated SSRC to intialize the + * RTP session. Then the outgoing packets are encrypted by SRTP. + * + * @param tp + * Pointer to the ZRTP transport data as returned by + * @c pjmedia_transport_zrtp_create. + * + * @param ssrc + * The local ssrc value in host order. + */ +PJ_DECL(void) pjmedia_transport_zrtp_setLocalSSRC(pjmedia_transport *tp, uint32_t ssrc); + +/** + * Check the state of the MitM mode flag. + * + * If true then this ZRTP session acts as MitM, usually enabled by a PBX + * client (user agent) + * + * @return state of mitmMode + */ +PJ_DECL(pj_bool_t) pjmedia_transport_zrtp_isMitmMode(pjmedia_transport *tp); + +/** + * Set the state of the MitM mode flag. + * + * If MitM mode is set to true this ZRTP session acts as MitM, usually + * enabled by a PBX client (user agent). + * + * @param mitmMode defines the new state of the mitmMode flag + */ +PJ_DECL(void) pjmedia_transport_zrtp_setMitmMode(pjmedia_transport *tp, pj_bool_t mitmMode); + +/** + * Set / reset the SAS verification flag. + * + */ +PJ_DECL(void) pjmedia_transport_zrtp_setSASVerified(pjmedia_transport *tp, pj_bool_t verified); + +/** + * Get the peer's ZID. + * + */ +PJ_DECL(int) pjmedia_transport_zrtp_getPeerZid(pjmedia_transport *tp, unsigned char* data); + +/** + * Get the peer's name. + * + */ +PJ_DECL(char*) pjmedia_transport_zrtp_getPeerName(pjmedia_transport *tp); + +/** + * Set the peer's name. + * + */ +PJ_DECL(void) pjmedia_transport_zrtp_putPeerName(pjmedia_transport *tp, const char *name); + + +PJ_DECL(char*) pjmedia_transport_zrtp_getMultiStreamParameters(pjmedia_transport *tp, pj_int32_t *length); + +PJ_DECL(void) pjmedia_transport_zrtp_setMultiStreamParameters(pjmedia_transport *tp, const char *parameters, pj_int32_t length, pjmedia_transport *master_tp); + +/** + * Get the ZRTP context pointer. + * + * Appplications need the ZRTP context pointer if they call ZRTP specific + * methods. The ZRTP specific include file @c ZrtpCWrapper contains the + * descriptions of the ZRTP methods. + * + * @return Pointer to ZRTP context + * + * @see zrtp_setAuxSecret() + * @see zrtp_setPbxSecret() + * @see zrtp_inState() + * @see zrtp_SASVerified() + * @see zrtp_resetSASVerified() + * @see zrtp_getHelloHash() + * @see zrtp_getMultiStrParams() + * @see zrtp_setMultiStrParams() + * @see zrtp_isMultiStream() + * @see zrtp_isMultiStreamAvailable() + * @see zrtp_acceptEnrollment() + * @see zrtp_setSignatureData() + * @see zrtp_getSignatureData() + * @see zrtp_getSignatureLength() + * @see zrtp_getZid(); + */ +PJ_DECL(ZrtpContext*) pjmedia_transport_zrtp_getZrtpContext(pjmedia_transport *tp); + +PJ_END_DECL + + +/** + * @} + */ + +#endif /* __PJMEDIA_TRANSPORT_ADAPTER_SAMPLE_H__ */ + + -diff -ruN pjproject-2.10/pjmedia/include/pjmedia/vid_stream.h pjsip/pjmedia/include/pjmedia/vid_stream.h ---- pjproject-2.10/pjmedia/include/pjmedia/vid_stream.h 2020-02-14 10:48:27.000000000 +0100 -+++ pjsip/pjmedia/include/pjmedia/vid_stream.h 2021-02-06 18:35:36.234362341 +0100 -@@ -471,6 +471,16 @@ - pjmedia_vid_stream_get_rtp_session_info(pjmedia_vid_stream *stream, - pjmedia_stream_rtp_sess_info *session_info); - -+/** -+ * Send RTCP PLI for the media stream. -+ * -+ * @param stream The media stream. -+ * -+ * @return PJ_SUCCESS on success. -+ */ -+PJ_DECL(pj_status_t) pjmedia_vid_stream_send_rtcp_pli( -+ pjmedia_vid_stream *stream); -+ - - /** - * @} --- pjproject-2.10/pjmedia/include/pjmedia/audiodev.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia/audiodev.h 2021-02-06 16:53:28.395408793 +0100 @@ -24,6 +24,7 @@ * @file audiodev.h * @brief Audio device API. */ +#include #include #include #include @@ -93,6 +94,38 @@ typedef pjmedia_aud_dev_factory* (*pjmedia_aud_dev_factory_create_func_ptr)(pj_pool_factory*); +typedef enum pjmedia_aud_dev_event { + PJMEDIA_AUD_DEV_DEFAULT_INPUT_CHANGED, + PJMEDIA_AUD_DEV_DEFAULT_OUTPUT_CHANGED, + PJMEDIA_AUD_DEV_LIST_WILL_REFRESH, + PJMEDIA_AUD_DEV_LIST_DID_REFRESH +} pjmedia_aud_dev_event; + + +typedef void (*pjmedia_aud_dev_observer_callback)(pjmedia_aud_dev_event event); + +/** + * This structure specifies the parameters to set an audio device observer + */ +typedef struct pjmedia_aud_dev_observer { + pjmedia_aud_dev_observer_callback cb; + pj_pool_t *pool; + pj_mutex_t *lock; + pj_thread_t *thread; + pj_thread_desc thread_desc; +} pjmedia_aud_dev_observer; + +/** + * Set an audio device observer callback. + * + * @param cb The callback that needs to be registred, or NULL in + * in case it needs to be unregistered. Only one callback + * can be registered. + * + * @return PJ_SUCCESS on successful operation or the appropriate + * error code. + */ +PJ_DECL(pj_status_t) pjmedia_aud_dev_set_observer_cb(pjmedia_aud_dev_observer_callback cb); /* Audio driver structure */ typedef struct pjmedia_aud_driver @@ -120,6 +150,8 @@ unsigned dev_cnt; /* Total number of devices. */ pj_uint32_t dev_list[PJMEDIA_AUD_MAX_DEVS];/* Array of dev IDs. */ + pjmedia_aud_dev_observer dev_observer; + } pjmedia_aud_subsys; diff -ruN pjproject-2.10/pjmedia/include/pjmedia-audiodev/audiodev_imp.h pjsip/pjmedia/include/pjmedia-audiodev/audiodev_imp.h --- pjproject-2.10/pjmedia/include/pjmedia-audiodev/audiodev_imp.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia-audiodev/audiodev_imp.h 2021-02-06 18:37:31.685678395 +0100 @@ -29,6 +29,15 @@ * @{ */ +typedef enum pjmedia_aud_dev_change_event { + DEFAULT_INPUT_CHANGED = 1, + DEFAULT_OUTPUT_CHANGED, + DEVICE_LIST_CHANGED +} pjmedia_aud_dev_change_event; + +typedef void (*pjmedia_aud_dev_change_callback)(pjmedia_aud_dev_change_event event); + + /** * Sound device factory operations. */ @@ -99,6 +108,30 @@ */ pj_status_t (*refresh)(pjmedia_aud_dev_factory *f); + /** + * Set audio device change callback + * + * @param f The audio device factory. + * @param cb The audio device change callback. + */ + void (*set_dev_change_cb)(pjmedia_aud_dev_factory *f, + pjmedia_aud_dev_change_callback cb); + + /** + * Get default recording device index + * + * @param f The audio device factory. + */ + int (*get_default_rec_dev)(pjmedia_aud_dev_factory *f); + + /** + * Get default playback device index + * + * @param f The audio device factory. + */ + int (*get_default_play_dev)(pjmedia_aud_dev_factory *f); + + } pjmedia_aud_dev_factory_op; diff -ruN pjproject-2.10/pjmedia/include/pjmedia.h pjsip/pjmedia/include/pjmedia.h --- pjproject-2.10/pjmedia/include/pjmedia.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia.h 2021-02-06 18:40:23.766607985 +0100 @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -67,6 +68,7 @@ #include #include #include +#include #include #include #include @@ -72,7 +74,7 @@ #include #include #include -//#include +#include #include #include #include diff -ruN pjproject-2.10/pjmedia/include/pjmedia-videodev/fb_dev.h pjsip/pjmedia/include/pjmedia-videodev/fb_dev.h --- pjproject-2.10/pjmedia/include/pjmedia-videodev/fb_dev.h 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia-videodev/fb_dev.h 2021-02-06 18:43:39.220193284 +0100 @@ -0,0 +1,32 @@ +/* $Id$ */ +/* + * Copyright (C) 2014-present AG Projects + * Copyright (C) 2013-2014 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef PJMEDIA_VIDEODEV_FB_DEV_H__ +#define PJMEDIA_VIDEODEV_FB_DEV_H__ + +#include + +typedef void (*pjmedia_vid_dev_fb_frame_cb)(const pjmedia_frame *frame, const pjmedia_rect_size size, void *user_data); + +pj_status_t +pjmedia_vid_dev_fb_set_callback(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_fb_frame_cb cb, + void *user_data); + +#endif /* PJMEDIA_VIDEODEV_FB_DEV_H__ */ diff -ruN pjproject-2.10/pjmedia/include/pjmedia_videodev.h pjsip/pjmedia/include/pjmedia_videodev.h --- pjproject-2.10/pjmedia/include/pjmedia_videodev.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/include/pjmedia_videodev.h 2021-02-06 18:39:51.429682633 +0100 @@ -27,5 +27,6 @@ #include #include #include +#include #endif /* __PJMEDIA_VIDEODEV_H__ */ diff -ruN pjproject-2.10/pjmedia/src/pjmedia/converter.c pjsip/pjmedia/src/pjmedia/converter.c --- pjproject-2.10/pjmedia/src/pjmedia/converter.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/converter.c 2021-02-06 20:19:28.491163002 +0100 @@ -19,6 +19,7 @@ #include #include #include +#include #define THIS_FILE "converter.c" @@ -174,6 +175,24 @@ if (status != PJ_SUCCESS) return status; + if (param->src.type == PJMEDIA_TYPE_VIDEO) { + char src_fourcc_name[5]; + char dst_fourcc_name[5]; + PJ_LOG(4, (THIS_FILE, "Converter %p (%s) created for video: %dx%d %s -> %dx%d %s", + cv, + f->name, + param->src.det.vid.size.w, + param->src.det.vid.size.h, + pjmedia_fourcc_name(param->src.id, src_fourcc_name), + param->dst.det.vid.size.w, + param->dst.det.vid.size.h, + pjmedia_fourcc_name(param->dst.id, dst_fourcc_name))); + } else if (param->src.type == PJMEDIA_TYPE_AUDIO) { + PJ_LOG(4, (THIS_FILE, "Converter %p created for audio", cv)); + } else { + PJ_LOG(4, (THIS_FILE, "Converter %p created for unknown", cv)); + } + *p_cv = cv; return PJ_SUCCESS; @@ -188,6 +207,7 @@ PJ_DEF(void) pjmedia_converter_destroy(pjmedia_converter *cv) { + PJ_LOG(4, (THIS_FILE, "Converter %p destroyed", cv)); (*cv->op->destroy)(cv); } diff -ruN pjproject-2.10/pjmedia/src/pjmedia/echo_common.c pjsip/pjmedia/src/pjmedia/echo_common.c --- pjproject-2.10/pjmedia/src/pjmedia/echo_common.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/echo_common.c 2021-02-06 20:22:22.888063103 +0100 @@ -135,7 +135,7 @@ #if defined(PJMEDIA_HAS_WEBRTC_AEC) && PJMEDIA_HAS_WEBRTC_AEC!=0 static struct ec_operations webrtc_aec_op = { - "WebRTC AEC", + "WEBRTC AEC", &webrtc_aec_create, &webrtc_aec_destroy, &webrtc_aec_reset, diff -ruN pjproject-2.10/pjmedia/src/pjmedia/echo_webrtc_aec.c pjsip/pjmedia/src/pjmedia/echo_webrtc_aec.c --- pjproject-2.10/pjmedia/src/pjmedia/echo_webrtc_aec.c 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/echo_webrtc_aec.c 2021-02-06 18:45:18.907037776 +0100 @@ -0,0 +1,643 @@ +/** + * Copyright (C) 2011-2013 AG Projects + * Copyright (C) 2010 Regis Montoya (aka r3gis - www.r3gis.fr) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include +#include +#include +#include +#include +#include + + +#if defined(PJMEDIA_HAS_WEBRTC_AEC) && PJMEDIA_HAS_WEBRTC_AEC != 0 + +/* 0: conservative, 1: moderate, 2: aggresive */ +#ifndef PJMEDIA_WEBRTC_AEC_AGGRESSIVENESS + #define PJMEDIA_WEBRTC_AEC_AGGRESSIVENESS 2 +#endif + +/* 0: mild, 1: mediumn, 2: aggressive */ +#ifndef PJMEDIA_WEBRTC_NS_POLICY + #define PJMEDIA_WEBRTC_NS_POLICY 0 +#endif + +#define THIS_FILE "echo_webrtc_aec.c" + +#include +#include +#include +#include + +#include "echo_internal.h" + + +/* + * This file contains the implementation of an echo canceller and noise suppressor for PJSIP which uses components + * from the WebRTC project. Things to take into account: + * + * - The WebRTC engine works with 10ms frames, while in PJSIP we use 20ms frames mostly, all data fed to WebRTC elements needs + * to be chunked in 10ms chunks. + * - When a 32kHz sampling rate is used, the WebRTC engine needs frames to be passed split into low and high frequencies. PJSIP + * will give us a frame with all frequencies, so the signal processing library in WebRTC must be used to split frames into low + * and high frequencies, and combine them later. + */ + + +typedef struct AudioBuffer +{ + int samples_per_channel; + pj_bool_t is_split; + + WebRtc_Word16* data; + WebRtc_Word16 low_pass_data[160]; + WebRtc_Word16 high_pass_data[160]; + + WebRtc_Word32 analysis_filter_state1[6]; + WebRtc_Word32 analysis_filter_state2[6]; + WebRtc_Word32 synthesis_filter_state1[6]; + WebRtc_Word32 synthesis_filter_state2[6]; +} AudioBuffer; + +static WebRtc_Word16* AudioBuffer_GetData(AudioBuffer *ab); +static WebRtc_Word16* AudioBuffer_GetLowPassData(AudioBuffer *ab); +static WebRtc_Word16* AudioBuffer_GetHighPassData(AudioBuffer *ab); +static void AudioBuffer_SetData(AudioBuffer *ab, WebRtc_Word16 *data); +static void AudioBuffer_Initialize(AudioBuffer *ab, int sample_rate); +static int AudioBuffer_SamplesPerChannel(AudioBuffer *ab); + + +static WebRtc_Word16* AudioBuffer_GetData(AudioBuffer *ab) +{ + pj_assert(ab->data); + + if (ab->is_split) { + WebRtcSpl_SynthesisQMF(ab->low_pass_data, + ab->high_pass_data, + ab->data, + ab->synthesis_filter_state1, + ab->synthesis_filter_state2); + } + return ab->data; +} + + +static WebRtc_Word16* AudioBuffer_GetLowPassData(AudioBuffer *ab) +{ + if (!ab->is_split) { + return ab->data; + } else { + return ab->low_pass_data; + } +} + + +static WebRtc_Word16* AudioBuffer_GetHighPassData(AudioBuffer *ab) +{ + if (!ab->is_split) { + return ab->data; + } else { + return ab->high_pass_data; + } +} + + +static void AudioBuffer_Initialize(AudioBuffer *ab, int sample_rate) +{ + pj_bzero(ab, sizeof(AudioBuffer)); + if (sample_rate == 32000) { + ab->is_split = PJ_TRUE; + ab->samples_per_channel = 160; + } else { + ab->is_split = PJ_FALSE; + ab->samples_per_channel = sample_rate / 100; + } +} + + +static void AudioBuffer_SetData(AudioBuffer *ab, WebRtc_Word16 *data) +{ + ab->data = data; + if (ab->is_split) { + /* split data into low and high bands */ + WebRtcSpl_AnalysisQMF(ab->data, /* input data */ + ab->low_pass_data, /* pointer to low pass data storage*/ + ab->high_pass_data, /* pointer to high pass data storage*/ + ab->analysis_filter_state1, + ab->analysis_filter_state2); + } +} + + +static int AudioBuffer_SamplesPerChannel(AudioBuffer *ab) +{ + return ab->samples_per_channel; +} + + +const WebRtc_Word16 kFilterCoefficients8kHz[5] = + {3798, -7596, 3798, 7807, -3733}; + +const WebRtc_Word16 kFilterCoefficients[5] = + {4012, -8024, 4012, 8002, -3913}; + +typedef struct { + WebRtc_Word16 y[4]; + WebRtc_Word16 x[2]; + const WebRtc_Word16* ba; +} HighPassFilterState; + + +static int HighPassFilter_Initialize(HighPassFilterState* hpf, int sample_rate) { + assert(hpf != NULL); + + if (sample_rate == 8000) { + hpf->ba = kFilterCoefficients8kHz; + } else { + hpf->ba = kFilterCoefficients; + } + + WebRtcSpl_MemSetW16(hpf->x, 0, 2); + WebRtcSpl_MemSetW16(hpf->y, 0, 4); + + return 0; +} + + +static int HighPassFilter_Process(HighPassFilterState* hpf, WebRtc_Word16* data, int length) { + assert(hpf != NULL); + + int i; + WebRtc_Word32 tmp_int32 = 0; + WebRtc_Word16* y = hpf->y; + WebRtc_Word16* x = hpf->x; + const WebRtc_Word16* ba = hpf->ba; + + for (i = 0; i < length; i++) { + // y[i] = b[0] * x[i] + b[1] * x[i-1] + b[2] * x[i-2] + // + -a[1] * y[i-1] + -a[2] * y[i-2]; + + tmp_int32 = WEBRTC_SPL_MUL_16_16(y[1], ba[3]); // -a[1] * y[i-1] (low part) + tmp_int32 += WEBRTC_SPL_MUL_16_16(y[3], ba[4]); // -a[2] * y[i-2] (low part) + tmp_int32 = (tmp_int32 >> 15); + tmp_int32 += WEBRTC_SPL_MUL_16_16(y[0], ba[3]); // -a[1] * y[i-1] (high part) + tmp_int32 += WEBRTC_SPL_MUL_16_16(y[2], ba[4]); // -a[2] * y[i-2] (high part) + tmp_int32 = (tmp_int32 << 1); + + tmp_int32 += WEBRTC_SPL_MUL_16_16(data[i], ba[0]); // b[0]*x[0] + tmp_int32 += WEBRTC_SPL_MUL_16_16(x[0], ba[1]); // b[1]*x[i-1] + tmp_int32 += WEBRTC_SPL_MUL_16_16(x[1], ba[2]); // b[2]*x[i-2] + + // Update state (input part) + x[1] = x[0]; + x[0] = data[i]; + + // Update state (filtered part) + y[2] = y[0]; + y[3] = y[1]; + y[0] = (WebRtc_Word16)(tmp_int32 >> 13); + y[1] = (WebRtc_Word16)((tmp_int32 - WEBRTC_SPL_LSHIFT_W32((WebRtc_Word32)(y[0]), 13)) << 2); + + // Rounding in Q12, i.e. add 2^11 + tmp_int32 += 2048; + + // Saturate (to 2^27) so that the HP filtered signal does not overflow + tmp_int32 = WEBRTC_SPL_SAT((WebRtc_Word32)(134217727), tmp_int32, (WebRtc_Word32)(-134217728)); + + // Convert back to Q0 and use rounding + data[i] = (WebRtc_Word16)WEBRTC_SPL_RSHIFT_W32(tmp_int32, 12); + + } + + return 0; +} + + +typedef struct webrtc_ec +{ + void *AEC_inst; + void *AGC_inst; + NsHandle *NS_inst; + + pj_bool_t needs_reset; + unsigned skip_frames; + unsigned silence_frames; + + unsigned clock_rate; + unsigned echo_tail; + unsigned samples_per_frame; + unsigned samples_per_10ms_frame; + + WebRtc_Word32 mic_capture_level; + WebRtc_Word16 has_echo; + WebRtc_UWord8 is_saturated; + + HighPassFilterState hpf; + AudioBuffer capture_audio_buffer; + AudioBuffer playback_audio_buffer; + + pj_int16_t *tmp_frame; + pj_int16_t *empty_frame; +} webrtc_ec; + + +#define WEBRTC_AEC_ERROR(aec_inst, tag) \ + do { \ + unsigned status = WebRtcAec_get_error_code(aec_inst); \ + PJ_LOG(4, (THIS_FILE, "WebRTC AEC ERROR (%s) %d", tag, status)); \ + } while (0) \ + + +#define WEBRTC_AGC_ERROR(ns_inst, text) \ + do { \ + PJ_LOG(4, (THIS_FILE, "WebRTC AGC ERROR (%s)", text)); \ + } while (0) \ + + +#define WEBRTC_NS_ERROR(ns_inst, text) \ + do { \ + PJ_LOG(4, (THIS_FILE, "WebRTC NS ERROR (%s)", text)); \ + } while (0) \ + + +PJ_DEF(pj_status_t) webrtc_aec_create(pj_pool_t *pool, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned tail_ms, + unsigned options, + void **p_echo ) +{ + webrtc_ec *echo; + int status; + + *p_echo = NULL; + + if (clock_rate != 16000 && clock_rate != 32000) { + PJ_LOG(4, (THIS_FILE, "Unsupported sample rate: %d", clock_rate)); + return PJ_EINVAL; + } + + echo = PJ_POOL_ZALLOC_T(pool, webrtc_ec); + PJ_ASSERT_RETURN(echo != NULL, PJ_ENOMEM); + + status = WebRtcAec_Create(&echo->AEC_inst); + if(status != 0) { + PJ_LOG(4, (THIS_FILE, "Couldn't allocate memory for WebRTC AEC")); + goto error; + } + + status = WebRtcAec_Init(echo->AEC_inst, clock_rate, clock_rate); + if(status != 0) { + WEBRTC_AEC_ERROR(echo->AEC_inst, "initialization"); + goto error; + } + + AecConfig aec_config; + aec_config.nlpMode = PJMEDIA_WEBRTC_AEC_AGGRESSIVENESS; + aec_config.skewMode = kAecFalse; + aec_config.metricsMode = kAecFalse; + + status = WebRtcAec_set_config(echo->AEC_inst, aec_config); + if(status != 0) { + WEBRTC_AEC_ERROR(echo->AEC_inst, "config initialization"); + goto error; + } + + status = WebRtcAgc_Create(&echo->AGC_inst); + if(status != 0) { + PJ_LOG(4, (THIS_FILE, "Couldn't allocate memory for WebRTC AGC")); + goto error; + } + + status = WebRtcAgc_Init(echo->AGC_inst, 0, 255, kAgcModeAdaptiveAnalog, clock_rate); + if(status != 0) { + WEBRTC_AGC_ERROR(echo->AGC_inst, "initialization"); + goto error; + } + + WebRtcAgc_config_t agc_config; + agc_config.targetLevelDbfs = 7; + agc_config.compressionGaindB = 0; + agc_config.limiterEnable = kAgcFalse; + + status = WebRtcAgc_set_config(echo->AGC_inst, agc_config); + if(status != 0) { + WEBRTC_AGC_ERROR(echo->AGC_inst, "config initialization"); + goto error; + } + + status = WebRtcNs_Create(&echo->NS_inst); + if(status != 0) { + PJ_LOG(4, (THIS_FILE, "Couldn't allocate memory for WebRTC NS")); + goto error; + } + + status = WebRtcNs_Init(echo->NS_inst, clock_rate); + if(status != 0) { + WEBRTC_NS_ERROR(echo->NS_inst, "initialization"); + goto error; + } + + status = WebRtcNs_set_policy(echo->NS_inst, PJMEDIA_WEBRTC_NS_POLICY); + if (status != 0) { + WEBRTC_NS_ERROR(echo->NS_inst, "failed to set policy"); + } + + echo->clock_rate = clock_rate; + echo->samples_per_frame = samples_per_frame; + echo->samples_per_10ms_frame = clock_rate / 100; /* the WebRTC engine works with 10ms frames */ + echo->echo_tail = tail_ms; + echo->needs_reset = PJ_TRUE; + echo->skip_frames = 0; + echo->silence_frames = 0; + echo->mic_capture_level = 255; /* initial mic capture level, maximum */ + + /* Allocate temporary frames for echo cancellation */ + echo->tmp_frame = (pj_int16_t*) pj_pool_zalloc(pool, sizeof(pj_int16_t)*samples_per_frame); + PJ_ASSERT_RETURN(echo->tmp_frame, PJ_ENOMEM); + + echo->empty_frame = (pj_int16_t*) pj_pool_zalloc(pool, sizeof(pj_int16_t)*samples_per_frame); + PJ_ASSERT_RETURN(echo->empty_frame, PJ_ENOMEM); + + /* Initialize audio buffers */ + AudioBuffer_Initialize(&echo->capture_audio_buffer, clock_rate); + AudioBuffer_Initialize(&echo->playback_audio_buffer, clock_rate); + + /* Initialize high pass filter */ + HighPassFilter_Initialize(&echo->hpf, clock_rate); + + PJ_LOG(4, (THIS_FILE, "WebRTC AEC and NS initialized")); + *p_echo = echo; + return PJ_SUCCESS; + +error: + if (echo->AEC_inst) + WebRtcAec_Free(echo->AEC_inst); + if (echo->AGC_inst) + WebRtcAgc_Free(echo->AGC_inst); + if (echo->NS_inst) + WebRtcNs_Free(echo->NS_inst); + return PJ_EBUG; +} + + +PJ_DEF(pj_status_t) webrtc_aec_destroy(void *state ) +{ + webrtc_ec *echo = (webrtc_ec*) state; + PJ_ASSERT_RETURN(echo, PJ_EINVAL); + + if (echo->AEC_inst) { + WebRtcAec_Free(echo->AEC_inst); + echo->AEC_inst = NULL; + } + if (echo->AGC_inst) { + WebRtcAgc_Free(echo->AGC_inst); + echo->AGC_inst = NULL; + } + if (echo->NS_inst) { + WebRtcNs_Free(echo->NS_inst); + echo->NS_inst = NULL; + } + + return PJ_SUCCESS; +} + + +PJ_DEF(void) webrtc_aec_reset(void *state) +{ + /* Synchronously reset later, before processing the next frame, to avoid race conditions */ + ((webrtc_ec*)state)->needs_reset = PJ_TRUE; +} + + +static void aec_reset(webrtc_ec *echo) +{ + PJ_ASSERT_ON_FAIL(echo && echo->AEC_inst && echo->AGC_inst && echo->NS_inst, {return;}); + + int status = 0; + + /* re-initialize the AEC */ + status = WebRtcAec_Init(echo->AEC_inst, echo->clock_rate, echo->clock_rate); + if(status != 0) { + WEBRTC_AEC_ERROR(echo->AEC_inst, "re-initialization"); + return; + } + + AecConfig aec_config; + aec_config.nlpMode = PJMEDIA_WEBRTC_AEC_AGGRESSIVENESS; + aec_config.skewMode = kAecFalse; + aec_config.metricsMode = kAecFalse; + + status = WebRtcAec_set_config(echo->AEC_inst, aec_config); + if(status != 0) { + WEBRTC_AEC_ERROR(echo->AEC_inst, "configuration re-initialization"); + return; + } + + /* re-initialize the AGC */ + status = WebRtcAgc_Init(echo->AGC_inst, 0, 255, kAgcModeAdaptiveAnalog, echo->clock_rate); + if(status != 0) { + WEBRTC_AGC_ERROR(echo->AGC_inst, "initialization"); + return; + } + + WebRtcAgc_config_t agc_config; + agc_config.targetLevelDbfs = 7; + agc_config.compressionGaindB = 0; + agc_config.limiterEnable = kAgcFalse; + + status = WebRtcAgc_set_config(echo->AGC_inst, agc_config); + if(status != 0) { + WEBRTC_AGC_ERROR(echo->AGC_inst, "config initialization"); + return; + } + + /* re-initialize the NS */ + status = WebRtcNs_Init(echo->NS_inst, echo->clock_rate); + if(status != 0) { + WEBRTC_NS_ERROR(echo->NS_inst, "re-initialization"); + return; + } + + status = WebRtcNs_set_policy(echo->NS_inst, PJMEDIA_WEBRTC_NS_POLICY); + if (status != 0) { + WEBRTC_NS_ERROR(echo->NS_inst, "configuration re-initialization"); + return; + } + + /* re-initialize audio buffers */ + AudioBuffer_Initialize(&echo->capture_audio_buffer, echo->clock_rate); + AudioBuffer_Initialize(&echo->playback_audio_buffer, echo->clock_rate); + + /* re-initialize high pass filter state */ + HighPassFilter_Initialize(&echo->hpf, echo->clock_rate); + + /* re-initialize mic level */ + echo->mic_capture_level = 255; + + PJ_LOG(4, (THIS_FILE, "WebRTC AEC reset succeeded")); +} + + +/* + * Perform echo cancellation. + */ +PJ_DEF(pj_status_t) webrtc_aec_cancel_echo(void *state, + pj_int16_t *rec_frm, + const pj_int16_t *play_frm, + unsigned options, + void *reserved) +{ + webrtc_ec *echo = (webrtc_ec*) state; + pj_int16_t *capture_frame, *result_frame; + int i, status; + + /* Sanity checks */ + PJ_ASSERT_RETURN(echo && echo->AEC_inst && echo->AGC_inst && echo->NS_inst, PJ_EINVAL); + PJ_ASSERT_RETURN(rec_frm && play_frm && options==0 && reserved==NULL, PJ_EINVAL); + + /* Check if a reset is needed */ + if (echo->needs_reset) { + aec_reset(echo); + echo->needs_reset = PJ_FALSE; + echo->skip_frames = 15; + echo->silence_frames = 10; + } + + if (echo->skip_frames) { + echo->skip_frames--; + capture_frame = echo->empty_frame; + result_frame = echo->empty_frame; + } else if (echo->silence_frames) { + echo->silence_frames--; + capture_frame = rec_frm; + result_frame = echo->empty_frame; + } else { + capture_frame = rec_frm; + result_frame = echo->tmp_frame; + } + + /* Copy record frame to a temporary buffer, in case things go wrong audio will be returned unchanged */ + pjmedia_copy_samples(echo->tmp_frame, capture_frame, echo->samples_per_frame); + + for(i=0; i < echo->samples_per_frame; i+= echo->samples_per_10ms_frame) { + /* feed a 10ms frame into the audio buffers */ + AudioBuffer_SetData(&echo->capture_audio_buffer, (WebRtc_Word16 *) (&echo->tmp_frame[i])); + AudioBuffer_SetData(&echo->playback_audio_buffer, (WebRtc_Word16 *) (&play_frm[i])); + + /* Apply high pass filer */ + HighPassFilter_Process(&echo->hpf, + AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), + AudioBuffer_SamplesPerChannel(&echo->capture_audio_buffer)); + + /* Analyze capture data gain + * NOTE: if we used kAgcModeAdaptiveDigital we'd use WebRtcAgc_VirtualMic instead + */ + status = WebRtcAgc_AddMic(echo->AGC_inst, + AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), + AudioBuffer_GetHighPassData(&echo->capture_audio_buffer), + AudioBuffer_SamplesPerChannel(&echo->capture_audio_buffer)); + if(status != 0) { + WEBRTC_AGC_ERROR(echo->AGC_inst, "gain analysis"); + return PJ_EBUG; + } + + /* Feed farend buffer to AGC */ + status = WebRtcAgc_AddFarend(echo->AGC_inst, + AudioBuffer_GetLowPassData(&echo->playback_audio_buffer), + AudioBuffer_SamplesPerChannel(&echo->playback_audio_buffer)); + if(status != 0) { + WEBRTC_AGC_ERROR(echo->AGC_inst, "farend buffering"); + return PJ_EBUG; + } + + /* Feed farend buffer to AEC */ + status = WebRtcAec_BufferFarend(echo->AEC_inst, + AudioBuffer_GetLowPassData(&echo->playback_audio_buffer), + AudioBuffer_SamplesPerChannel(&echo->playback_audio_buffer)); + if(status != 0) { + WEBRTC_AEC_ERROR(echo->AEC_inst, "farend buffering"); + return PJ_EBUG; + } + + /* Noise suppression */ + status = WebRtcNs_Process(echo->NS_inst, + AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), + AudioBuffer_GetHighPassData(&echo->capture_audio_buffer), + AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), + AudioBuffer_GetHighPassData(&echo->capture_audio_buffer)); + if (status != 0) { + WEBRTC_NS_ERROR(echo->NS_inst, "ns processing"); + return PJ_EBUG; + } + + /* Process echo cancellation */ + status = WebRtcAec_Process(echo->AEC_inst, + AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), + AudioBuffer_GetHighPassData(&echo->capture_audio_buffer), + AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), + AudioBuffer_GetHighPassData(&echo->capture_audio_buffer), + AudioBuffer_SamplesPerChannel(&echo->capture_audio_buffer), + echo->echo_tail, + 0); + if(status != 0) { + WEBRTC_AEC_ERROR(echo->AEC_inst, "echo processing"); + return PJ_EBUG; + } + + WebRtcAec_get_echo_status(echo->AEC_inst, &echo->has_echo); +#if 0 + if (echo->has_echo) { + PJ_LOG(4, (THIS_FILE, "Sound might have echo")); + } +#endif + + /* Process gain control */ + status = WebRtcAgc_Process(echo->AGC_inst, + AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), + AudioBuffer_GetHighPassData(&echo->capture_audio_buffer), + AudioBuffer_SamplesPerChannel(&echo->capture_audio_buffer), + AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), + AudioBuffer_GetHighPassData(&echo->capture_audio_buffer), + echo->mic_capture_level, + &echo->mic_capture_level, + echo->has_echo, + &echo->is_saturated); + if (status != 0) { + WEBRTC_AGC_ERROR(echo->AGC_inst, "agc processing"); + return PJ_EBUG; + } +#if 0 + if (echo->is_saturated) { + PJ_LOG(4, (THIS_FILE, "Sound might be saturated")); + } +#endif + + /* finish frame processing, in case we are working at 32kHz low and high bands will be combined */ + AudioBuffer_GetData(&echo->capture_audio_buffer); + } + + /* Copy temporary buffer back to original rec_frm */ + pjmedia_copy_samples(rec_frm, result_frame, echo->samples_per_frame); + + return PJ_SUCCESS; + +} + + +#endif diff -ruN pjproject-2.10/pjmedia/src/pjmedia/endpoint.c pjsip/pjmedia/src/pjmedia/endpoint.c --- pjproject-2.10/pjmedia/src/pjmedia/endpoint.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/endpoint.c 2021-02-06 20:23:26.353848918 +0100 @@ -688,6 +688,7 @@ /* Put bandwidth info in media level using bandwidth modifier "TIAS" * (RFC3890). */ +#if 0 if (max_bitrate && pjmedia_add_bandwidth_tias_in_sdp) { const pj_str_t STR_BANDW_MODIFIER = { "TIAS", 4 }; pjmedia_sdp_bandw *b; @@ -697,6 +698,7 @@ b->value = max_bitrate; m->bandw[m->bandw_count++] = b; } +#endif *p_m = m; return PJ_SUCCESS; +--- pjproject-2.10/pjmedia/src/pjmedia/ffmpeg_util.h 2021-03-19 16:51:27.589562664 +0100 ++++ pjsip/pjmedia/src/pjmedia/ffmpeg_util.h 2021-03-19 17:00:32.821251088 +0100 +@@ -38,13 +38,13 @@ + #include + + +-#ifdef PJMEDIA_USE_OLD_FFMPEG +-# define AVPixelFormat PixelFormat +-# define AV(str) str +-# define PIX_FMT_GBRP PIX_FMT_GBR24P +-#else ++//#ifdef PJMEDIA_USE_OLD_FFMPEG ++//# define AVPixelFormat PixelFormat ++//# define AV(str) str ++//# define PIX_FMT_GBRP PIX_FMT_GBR24P ++//#else + # define AV(str) AV_ ## str +-#endif ++//#endif + + + void pjmedia_ffmpeg_add_ref(); +diff -ruN pjproject-2.10/pjmedia/src/pjmedia/ffmpeg_util.c pjsip/pjmedia/src/pjmedia/ffmpeg_util.c +--- pjproject-2.10/pjmedia/src/pjmedia/ffmpeg_util.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia/ffmpeg_util.c 2021-02-06 20:25:00.472499296 +0100 +@@ -34,10 +34,11 @@ + enum AVPixelFormat pf; + } ffmpeg_fmt_table[] = + { ++ { PJMEDIA_FORMAT_ARGB, AV(PIX_FMT_ARGB)}, + { PJMEDIA_FORMAT_RGBA, AV(PIX_FMT_RGBA)}, + { PJMEDIA_FORMAT_RGB24,AV(PIX_FMT_BGR24)}, + { PJMEDIA_FORMAT_BGRA, AV(PIX_FMT_BGRA)}, +- { PJMEDIA_FORMAT_GBRP, AV(PIX_FMT_GBRP)}, ++ { PJMEDIA_FORMAT_GBRP, AV(PIX_FMT_GBR24P)}, + + { PJMEDIA_FORMAT_AYUV, AV(PIX_FMT_NONE)}, + { PJMEDIA_FORMAT_YUY2, AV(PIX_FMT_YUYV422)}, diff -ruN pjproject-2.10/pjmedia/src/pjmedia/format.c pjsip/pjmedia/src/pjmedia/format.c --- pjproject-2.10/pjmedia/src/pjmedia/format.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/format.c 2021-02-06 20:25:25.677209428 +0100 @@ -77,6 +77,7 @@ static pjmedia_video_format_info built_in_vid_fmt_info[] = { {PJMEDIA_FORMAT_RGB24, "RGB24", PJMEDIA_COLOR_MODEL_RGB, 24, 1, &apply_packed_fmt}, + {PJMEDIA_FORMAT_ARGB, "ARGB", PJMEDIA_COLOR_MODEL_RGB, 32, 1, &apply_packed_fmt}, {PJMEDIA_FORMAT_RGBA, "RGBA", PJMEDIA_COLOR_MODEL_RGB, 32, 1, &apply_packed_fmt}, {PJMEDIA_FORMAT_BGRA, "BGRA", PJMEDIA_COLOR_MODEL_RGB, 32, 1, &apply_packed_fmt}, {PJMEDIA_FORMAT_DIB , "DIB ", PJMEDIA_COLOR_MODEL_RGB, 24, 1, &apply_packed_fmt}, diff -ruN pjproject-2.10/pjmedia/src/pjmedia/mixer_port.c pjsip/pjmedia/src/pjmedia/mixer_port.c --- pjproject-2.10/pjmedia/src/pjmedia/mixer_port.c 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/mixer_port.c 2021-02-06 18:45:36.859549796 +0100 @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2010 AG Projects + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#include +#include +#include +#include +#include + + +#define SIGNATURE PJMEDIA_SIG_PORT_MIXER +#define MIN(a, b) ((a)>(b)?(b):(a)) + +struct mixer_port +{ + pjmedia_port base; + pjmedia_frame_type last_frame_type; + pj_size_t last_frame_size; + pj_timestamp last_frame_timestamp; + pj_int16_t* buffer; + pj_size_t buffer_size; +}; + +static pj_status_t mixer_get_frame(pjmedia_port *port, pjmedia_frame *frame); +static pj_status_t mixer_put_frame(pjmedia_port *port, pjmedia_frame *frame); +static pj_status_t mixer_on_destroy(pjmedia_port *port); + + +PJ_DEF(pj_status_t) pjmedia_mixer_port_create(pj_pool_t *pool, + unsigned sampling_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + pjmedia_port **p_port) +{ + struct mixer_port *port; + const pj_str_t name = pj_str("mixer-port"); + + PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL); + + port = PJ_POOL_ZALLOC_T(pool, struct mixer_port); + PJ_ASSERT_RETURN(port != NULL, PJ_ENOMEM); + + pjmedia_port_info_init(&port->base.info, &name, SIGNATURE, sampling_rate, + channel_count, bits_per_sample, samples_per_frame); + + port->base.get_frame = &mixer_get_frame; + port->base.put_frame = &mixer_put_frame; + port->base.on_destroy = &mixer_on_destroy; + port->last_frame_type = PJMEDIA_FRAME_TYPE_NONE; + port->last_frame_size = 0; + port->last_frame_timestamp.u64 = 0; + port->buffer = (pj_int16_t*) pj_pool_calloc(pool, samples_per_frame, sizeof(pj_int16_t)); + port->buffer_size = sizeof(pj_int16_t) * samples_per_frame; + + *p_port = &port->base; + + return PJ_SUCCESS; +} + + + +/* + * Put frame to file. + */ +static pj_status_t mixer_put_frame(pjmedia_port *this_port, pjmedia_frame *frame) +{ + struct mixer_port* port = (struct mixer_port*) this_port; + + if (!frame->size || frame->type != PJMEDIA_FRAME_TYPE_AUDIO) { + port->last_frame_type = PJMEDIA_FRAME_TYPE_NONE; + port->last_frame_size = 0; + port->last_frame_timestamp.u64 = 0; + return PJ_SUCCESS; + } + + PJ_ASSERT_RETURN(frame->size <= port->buffer_size, PJ_EINVAL); + + port->last_frame_type = frame->type; + pj_get_timestamp(&port->last_frame_timestamp); + port->last_frame_size = MIN(port->buffer_size, frame->size); + memcpy(port->buffer, frame->buf, port->last_frame_size); + + return PJ_SUCCESS; +} + + +/* + * Get frame from file. + */ +static pj_status_t mixer_get_frame(pjmedia_port *this_port, pjmedia_frame *frame) +{ + struct mixer_port* port = (struct mixer_port*) this_port; + pj_timestamp now; + pj_uint32_t frame_age; + + pj_get_timestamp(&now); + frame_age = pj_elapsed_usec(&port->last_frame_timestamp, &now); + + if (port->last_frame_timestamp.u64 != 0 && frame_age <= 100000) { + frame->type = port->last_frame_type; + frame->size = port->last_frame_size; + frame->timestamp.u64 = 0; + if (port->last_frame_size > 0) { + memcpy(frame->buf, port->buffer, port->last_frame_size); + } + } else { + frame->type = PJMEDIA_FRAME_TYPE_NONE; + frame->size = 0; + frame->timestamp.u64 = 0; + } + + return PJ_SUCCESS; +} + + +/* + * Destroy port. + */ +static pj_status_t mixer_on_destroy(pjmedia_port *this_port) +{ + return PJ_SUCCESS; +} + diff -ruN pjproject-2.10/pjmedia/src/pjmedia/rtcp.c pjsip/pjmedia/src/pjmedia/rtcp.c --- pjproject-2.10/pjmedia/src/pjmedia/rtcp.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/rtcp.c 2021-02-06 20:30:29.245771690 +0100 @@ -1003,6 +1003,33 @@ sess->stat.rx.update_cnt++; } +PJ_DEF(pj_status_t) pjmedia_rtcp_build_rtcp_pli(pjmedia_rtcp_session *session, + void *buf, + pj_size_t *length) +{ + pjmedia_rtcp_common *hdr; + pj_uint8_t *p; + pj_size_t len = 12; /* pjmedia_rtcp_common + media SSRC (uint32_t) */ + + PJ_ASSERT_RETURN(session && buf && length, PJ_EINVAL); + + /* Verify buffer length */ + if (len > *length) + return PJ_ETOOSMALL; + + /* Build RTCP PLI */ + hdr = (pjmedia_rtcp_common*)buf; + pj_memcpy(hdr, &session->rtcp_sr_pkt.common, sizeof(*hdr)); + hdr->pt = RTCP_PSFB; + hdr->count = 1; /* FMT: 1 == Picture Loss Indication (PLI) */ + hdr->length = pj_htons((pj_uint16_t)(len/4 - 1)); + + p = (pj_uint8_t*)hdr + sizeof(*hdr); + pj_memset(p, 0, (pj_uint8_t*)hdr + len - p); + *length = len; + return PJ_SUCCESS; +} + PJ_DEF(pj_status_t) pjmedia_rtcp_build_rtcp_sdes( pjmedia_rtcp_session *session, diff -ruN pjproject-2.10/pjmedia/src/pjmedia/sound_port.c pjsip/pjmedia/src/pjmedia/sound_port.c --- pjproject-2.10/pjmedia/src/pjmedia/sound_port.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/sound_port.c 2021-02-06 21:42:52.101118944 +0100 @@ -98,7 +98,7 @@ if (snd_port->ec_state) { if (snd_port->ec_suspended) { snd_port->ec_suspended = PJ_FALSE; - //pjmedia_echo_state_reset(snd_port->ec_state); + pjmedia_echo_reset(snd_port->ec_state); PJ_LOG(4,(THIS_FILE, "EC activated")); } snd_port->ec_suspend_count = 0; @@ -312,14 +312,9 @@ snd_port->aud_param.ec_tail_ms)); } - status = pjmedia_snd_port_set_ec(snd_port, pool, - snd_port->aud_param.ec_tail_ms, - snd_port->prm_ec_options); - if (status != PJ_SUCCESS) { - pjmedia_aud_stream_destroy(snd_port->aud_stream); - snd_port->aud_stream = NULL; - return status; - } + pjmedia_snd_port_set_ec(snd_port, pool, + snd_port->aud_param.ec_tail_ms, + snd_port->prm_ec_options); } /* Start sound stream. */ @@ -550,6 +545,17 @@ } +/* Reset EC state */ +PJ_DEF(pj_status_t) pjmedia_snd_port_reset_ec_state( pjmedia_snd_port *snd_port ) +{ + PJ_ASSERT_RETURN(snd_port, PJ_EINVAL); + if (snd_port->ec_state) { + pjmedia_echo_reset(snd_port->ec_state); + PJ_LOG(4,(THIS_FILE, "EC reset")); + } + return PJ_SUCCESS; +} + /* * Change EC settings. */ diff -ruN pjproject-2.10/pjmedia/src/pjmedia/transport_ice.c pjsip/pjmedia/src/pjmedia/transport_ice.c --- pjproject-2.10/pjmedia/src/pjmedia/transport_ice.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/transport_ice.c 2021-02-06 21:40:50.021656143 +0100 @@ -163,6 +163,10 @@ pj_ice_strans_op op, pj_status_t status); +static void ice_on_ice_state(pj_ice_strans *ice_st, + pj_ice_strans_state prev, + pj_ice_strans_state curr); + /* * Clean up ICE resources. */ @@ -280,6 +284,7 @@ /* Configure ICE callbacks */ pj_bzero(&ice_st_cb, sizeof(ice_st_cb)); ice_st_cb.on_ice_complete = &ice_on_ice_complete; + ice_st_cb.on_ice_state = &ice_on_ice_state; ice_st_cb.on_rx_data = &ice_on_rx_data; /* Configure RTP socket buffer settings, if not set */ @@ -314,6 +319,17 @@ return PJ_SUCCESS; } +/* + * Get the ICE stream transport associated with this media transport. + */ +PJ_DEF(pj_ice_strans*) pjmedia_ice_get_strans(pjmedia_transport *tp) +{ + struct transport_ice *tp_ice; + + tp_ice = (struct transport_ice*) tp; + return tp_ice->ice_st; +} + PJ_DEF(pj_grp_lock_t *) pjmedia_ice_get_grp_lock(pjmedia_transport *tp) { PJ_ASSERT_RETURN(tp, NULL); @@ -403,6 +419,10 @@ "Stopping ICE, reason=%s", reason)); } + /* Notify application about ICE stop */ + if (tp_ice->cb.on_ice_stop) + (*tp_ice->cb.on_ice_stop)(&tp_ice->base, (char *)reason, err); + if (tp_ice->ice_st) { pj_ice_strans_stop_ice(tp_ice->ice_st); } @@ -1745,17 +1765,17 @@ if (status != PJ_SUCCESS) return status; - pj_sockaddr_cp(&info->sock_info.rtp_addr_name, &cand.addr); + pj_sockaddr_cp(&info->sock_info.rtp_addr_name, &cand.base_addr); /* Get RTCP default address */ if (tp_ice->use_rtcp_mux) { - pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, &cand.addr); + pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, &cand.base_addr); } else if (tp_ice->comp_cnt > 1) { status = pj_ice_strans_get_def_cand(tp_ice->ice_st, 2, &cand); if (status != PJ_SUCCESS) return status; - pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, &cand.addr); + pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, &cand.base_addr); } /* Set remote address originating RTP & RTCP if this transport has @@ -2115,6 +2135,19 @@ } } +static void ice_on_ice_state(pj_ice_strans *ice_st, + pj_ice_strans_state prev, + pj_ice_strans_state curr) +{ + struct transport_ice *tp_ice; + + tp_ice = (struct transport_ice*) pj_ice_strans_get_user_data(ice_st); + + /* Notify application */ + if (tp_ice->cb.on_ice_state) + (*tp_ice->cb.on_ice_state)(&tp_ice->base, prev, curr); +} + /* Simulate lost */ static pj_status_t transport_simulate_lost(pjmedia_transport *tp, diff -ruN pjproject-2.10/pjmedia/src/pjmedia/transport_zrtp.c pjsip/pjmedia/src/pjmedia/transport_zrtp.c --- pjproject-2.10/pjmedia/src/pjmedia/transport_zrtp.c 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/transport_zrtp.c 2021-02-06 16:42:04.322625052 +0100 @@ -0,0 +1,1261 @@ +/* $Id$ */ +/* + * Copyright (C) 2010 Werner Dittmann + * This is the pjmedia ZRTP transport module. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include + +#include "../../third_party/zsrtp/include/ZsrtpCWrapper.h" + +#define THIS_FILE "transport_zrtp.c" + +#define MAX_RTP_BUFFER_LEN PJMEDIA_MAX_MTU +#define MAX_RTCP_BUFFER_LEN PJMEDIA_MAX_MTU + + +/* Transport functions prototypes */ +static pj_status_t transport_get_info(pjmedia_transport *tp, + pjmedia_transport_info *info); +static pj_status_t transport_attach(pjmedia_transport *tp, + void *user_data, + const pj_sockaddr_t *rem_addr, + const pj_sockaddr_t *rem_rtcp, + unsigned addr_len, + void (*rtp_cb)(void*, + void*, + pj_ssize_t), + void (*rtcp_cb)(void*, + void*, + pj_ssize_t)); +static void transport_detach(pjmedia_transport *tp, + void *strm); +static pj_status_t transport_send_rtp(pjmedia_transport *tp, + const void *pkt, + pj_size_t size); +static pj_status_t transport_send_rtcp(pjmedia_transport *tp, + const void *pkt, + pj_size_t size); +static pj_status_t transport_send_rtcp2(pjmedia_transport *tp, + const pj_sockaddr_t *addr, + unsigned addr_len, + const void *pkt, + pj_size_t size); +static pj_status_t transport_media_create(pjmedia_transport *tp, + pj_pool_t *sdp_pool, + unsigned options, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index); +static pj_status_t transport_encode_sdp(pjmedia_transport *tp, + pj_pool_t *sdp_pool, + pjmedia_sdp_session *local_sdp, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index); +static pj_status_t transport_media_start(pjmedia_transport *tp, + pj_pool_t *pool, + const pjmedia_sdp_session *local_sdp, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index); +static pj_status_t transport_media_stop(pjmedia_transport *tp); +static pj_status_t transport_simulate_lost(pjmedia_transport *tp, + pjmedia_dir dir, + unsigned pct_lost); +static pj_status_t transport_destroy(pjmedia_transport *tp); + + +/* The transport operations */ +static struct pjmedia_transport_op tp_zrtp_op = +{ + &transport_get_info, + &transport_attach, + &transport_detach, + &transport_send_rtp, + &transport_send_rtcp, + &transport_send_rtcp2, + &transport_media_create, + &transport_encode_sdp, + &transport_media_start, + &transport_media_stop, + &transport_simulate_lost, + &transport_destroy +}; + +/* The transport zrtp instance */ +struct tp_zrtp +{ + pjmedia_transport base; + pj_pool_t *pool; + + /* Stream information. */ + void *stream_user_data; + void (*stream_rtp_cb)(void *user_data, + void *pkt, + pj_ssize_t); + void (*stream_rtcp_cb)(void *user_data, + void *pkt, + pj_ssize_t); + + /* Add your own member here.. */ + uint64_t protect; + uint64_t unprotect; + int32_t unprotect_err; + int32_t refcount; + pj_timer_heap_t* timer_heap; + pj_timer_entry timeoutEntry; + pj_mutex_t* zrtpMutex; + ZsrtpContext* srtpReceive; + ZsrtpContext* srtpSend; + ZsrtpContextCtrl* srtcpReceive; + ZsrtpContextCtrl* srtcpSend; + void* sendBuffer; + void* sendBufferCtrl; + pj_uint8_t* zrtpBuffer; +// pj_int32_t sendBufferLen; + pj_uint32_t peerSSRC; /* stored in host order */ + pj_uint32_t localSSRC; /* stored in host order */ + char* clientIdString; + pjmedia_transport *slave_tp; + pjmedia_zrtp_cb cb; + ZrtpContext* zrtpCtx; + pj_uint16_t zrtpSeq; + pj_bool_t enableZrtp; + pj_bool_t started; + pj_bool_t close_slave; + pj_bool_t mitmMode; + char cipher[128]; +}; + +/* Forward declaration of thethe ZRTP specific callback functions that this + adapter must implement */ +static int32_t zrtp_sendDataZRTP(ZrtpContext* ctx, const uint8_t* data, int32_t length) ; +static int32_t zrtp_activateTimer(ZrtpContext* ctx, int32_t time) ; +static int32_t zrtp_cancelTimer(ZrtpContext* ctx) ; +static void zrtp_sendInfo(ZrtpContext* ctx, int32_t severity, int32_t subCode) ; +static int32_t zrtp_srtpSecretsReady(ZrtpContext* ctx, C_SrtpSecret_t* secrets, int32_t part) ; +static void zrtp_srtpSecretsOff(ZrtpContext* ctx, int32_t part) ; +static void zrtp_srtpSecretsOn(ZrtpContext* ctx, char* c, char* s, int32_t verified) ; +static void zrtp_handleGoClear(ZrtpContext* ctx) ; +static void zrtp_zrtpNegotiationFailed(ZrtpContext* ctx, int32_t severity, int32_t subCode) ; +static void zrtp_zrtpNotSuppOther(ZrtpContext* ctx) ; +static void zrtp_synchEnter(ZrtpContext* ctx) ; +static void zrtp_synchLeave(ZrtpContext* ctx) ; +static void zrtp_zrtpAskEnrollment(ZrtpContext* ctx, int32_t info) ; +static void zrtp_zrtpInformEnrollment(ZrtpContext* ctx, int32_t info) ; +static void zrtp_signSAS(ZrtpContext* ctx, uint8_t* sasHash) ; +static int32_t zrtp_checkSASSignature(ZrtpContext* ctx, uint8_t* sasHash) ; + +/* The callback function structure for ZRTP */ +static zrtp_Callbacks c_callbacks = +{ + &zrtp_sendDataZRTP, + &zrtp_activateTimer, + &zrtp_cancelTimer, + &zrtp_sendInfo, + &zrtp_srtpSecretsReady, + &zrtp_srtpSecretsOff, + &zrtp_srtpSecretsOn, + &zrtp_handleGoClear, + &zrtp_zrtpNegotiationFailed, + &zrtp_zrtpNotSuppOther, + &zrtp_synchEnter, + &zrtp_synchLeave, + &zrtp_zrtpAskEnrollment, + &zrtp_zrtpInformEnrollment, + &zrtp_signSAS, + &zrtp_checkSASSignature +}; + +static void timer_callback(pj_timer_heap_t *ht, pj_timer_entry *e); + +static char clientId[] = "SIP SIMPLE Client SDK"; + +/* + * Create the ZRTP transport. + */ +PJ_DEF(pj_status_t) pjmedia_transport_zrtp_create(pjmedia_endpt *endpt, + pj_timer_heap_t *timer_heap, + pjmedia_transport *tp, + pjmedia_transport **p_tp, + pj_bool_t close_slave) +{ + pj_pool_t *pool; + struct tp_zrtp *zrtp; + + PJ_ASSERT_RETURN(endpt && tp && p_tp, PJ_EINVAL); + + /* Create the pool and initialize the adapter structure */ + pool = pjmedia_endpt_create_pool(endpt, "zrtp%p", 5*1024, 512); + zrtp = PJ_POOL_ZALLOC_T(pool, struct tp_zrtp); + zrtp->pool = pool; + + /* Initialize base pjmedia_transport */ + pj_memcpy(zrtp->base.name, pool->obj_name, PJ_MAX_OBJ_NAME); + zrtp->base.type = tp->type; + zrtp->base.op = &tp_zrtp_op; + + /* Set the timer heap to be used for timers */ + zrtp->timer_heap = timer_heap; + + /* Create the empty wrapper */ + zrtp->zrtpCtx = zrtp_CreateWrapper(); + + /* Initialize standard values */ + zrtp->clientIdString = clientId; /* Set standard name */ + zrtp->zrtpSeq = 1; /* TODO: randomize */ + pj_mutex_create_simple(zrtp->pool, "zrtp", &zrtp->zrtpMutex); + zrtp->zrtpBuffer = pj_pool_zalloc(pool, MAX_ZRTP_SIZE); + zrtp->sendBuffer = pj_pool_zalloc(pool, MAX_RTP_BUFFER_LEN); + zrtp->sendBufferCtrl = pj_pool_zalloc(pool, MAX_RTCP_BUFFER_LEN); + + zrtp->slave_tp = tp; + zrtp->close_slave = close_slave; + zrtp->mitmMode = PJ_FALSE; + + /* Done */ + zrtp->refcount++; + *p_tp = &zrtp->base; + return PJ_SUCCESS; +} + +PJ_DECL(pj_status_t) pjmedia_transport_zrtp_initialize(pjmedia_transport *tp, + const char *zidFilename, + pj_bool_t autoEnable, + pjmedia_zrtp_cb *cb) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + PJ_ASSERT_RETURN(tp, PJ_EINVAL); + + zrtp_initializeZrtpEngine(zrtp->zrtpCtx, &c_callbacks, zrtp->clientIdString, + zidFilename, zrtp, zrtp->mitmMode); + zrtp->enableZrtp = autoEnable; + if (cb) + pj_memcpy(&zrtp->cb, cb, sizeof(pjmedia_zrtp_cb)); + return PJ_SUCCESS; +} + +static void timer_callback(pj_timer_heap_t *ht, pj_timer_entry *e) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)e->user_data; + + zrtp_processTimeout(zrtp->zrtpCtx); + PJ_UNUSED_ARG(ht); +} + +/* + * Here start with callback functions that support the ZRTP core + */ +static int32_t zrtp_sendDataZRTP(ZrtpContext* ctx, const uint8_t* data, int32_t length) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + pj_uint16_t totalLen = length + 12; /* Fixed number of bytes of ZRTP header */ + pj_uint32_t crc; + pj_uint8_t* buffer = zrtp->zrtpBuffer; + pj_uint16_t* pus; + pj_uint32_t* pui; + + if ((totalLen) > MAX_ZRTP_SIZE) + return 0; + + /* Get some handy pointers */ + pus = (pj_uint16_t*)buffer; + pui = (pj_uint32_t*)buffer; + + /* set up fixed ZRTP header */ + *buffer = 0x10; /* invalid RTP version - refer to ZRTP spec chap 5 */ + *(buffer + 1) = 0; + pus[1] = pj_htons(zrtp->zrtpSeq++); + pui[1] = pj_htonl(ZRTP_MAGIC); + pui[2] = pj_htonl(zrtp->localSSRC); /* stored in host order */ + + /* Copy ZRTP message data behind the header data */ + pj_memcpy(buffer+12, data, length); + + /* Setup and compute ZRTP CRC */ + crc = zrtp_GenerateCksum(buffer, totalLen-CRC_SIZE); + + /* convert and store CRC in ZRTP packet.*/ + crc = zrtp_EndCksum(crc); + *(uint32_t*)(buffer+totalLen-CRC_SIZE) = pj_htonl(crc); + + /* Send the ZRTP packet using the slave transport */ + return (pjmedia_transport_send_rtp(zrtp->slave_tp, buffer, totalLen) == PJ_SUCCESS) ? 1 : 0; +} + +static int32_t zrtp_activateTimer(ZrtpContext* ctx, int32_t time) +{ + pj_time_val timeout; + pj_status_t status; + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + timeout.sec = time / 1000; + timeout.msec = time % 1000; + + pj_timer_entry_init(&zrtp->timeoutEntry, 0, zrtp, &timer_callback); + status = pj_timer_heap_schedule(zrtp->timer_heap, &zrtp->timeoutEntry, &timeout); + if (status == PJ_SUCCESS) + return 1; + else + return 0; +} + +static int32_t zrtp_cancelTimer(ZrtpContext* ctx) +{ + pj_status_t status; + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + status = pj_timer_heap_cancel(zrtp->timer_heap, &zrtp->timeoutEntry); + if (status == PJ_SUCCESS) + return 1; + else + return 0; +} + +static void zrtp_sendInfo(ZrtpContext* ctx, int32_t severity, int32_t subCode) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + if (zrtp->cb.show_message) + zrtp->cb.show_message(&zrtp->base, severity, subCode); + +} + +static int32_t zrtp_srtpSecretsReady(ZrtpContext* ctx, C_SrtpSecret_t* secrets, int32_t part) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + ZsrtpContext* recvCrypto; + ZsrtpContext* senderCrypto; + ZsrtpContextCtrl* recvCryptoCtrl; + ZsrtpContextCtrl* senderCryptoCtrl; + int cipher; + int authn; + int authKeyLen; + // int srtcpAuthTagLen; + + if (secrets->authAlgorithm == zrtp_Sha1) { + authn = SrtpAuthenticationSha1Hmac; + authKeyLen = 20; + // srtcpAuthTagLen = 80; // Always 80 bit for SRTCP / SHA1 + } + + if (secrets->authAlgorithm == zrtp_Skein) { + authn = SrtpAuthenticationSkeinHmac; + authKeyLen = 32; + // srtcpAuthTagLen = 64; // Always 64 bit for SRTCP / Skein + } + + if (secrets->symEncAlgorithm == zrtp_Aes) + cipher = SrtpEncryptionAESCM; + + if (secrets->symEncAlgorithm == zrtp_TwoFish) + cipher = SrtpEncryptionTWOCM; + + if (part == ForSender) { + // To encrypt packets: intiator uses initiator keys, + // responder uses responder keys + // Create a "half baked" crypto context first and store it. This is + // the main crypto context for the sending part of the connection. + if (secrets->role == Initiator) { + senderCrypto = zsrtp_CreateWrapper(zrtp->localSSRC, + 0, + 0L, // keyderivation << 48, + cipher, // encryption algo + authn, // authtentication algo + (unsigned char*)secrets->keyInitiator, // Master Key + secrets->initKeyLen / 8, // Master Key length + (unsigned char*)secrets->saltInitiator, // Master Salt + secrets->initSaltLen / 8, // Master Salt length + secrets->initKeyLen / 8, // encryption keyl + authKeyLen, // authentication key len + secrets->initSaltLen / 8, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag lenA + + senderCryptoCtrl = zsrtp_CreateWrapperCtrl(zrtp->localSSRC, + cipher, // encryption algo + authn, // authtication algo + (unsigned char*)secrets->keyInitiator, // Master Key + secrets->initKeyLen / 8, // Master Key length + (unsigned char*)secrets->saltInitiator, // Master Salt + secrets->initSaltLen / 8, // Master Salt length + secrets->initKeyLen / 8, // encryption keyl + authKeyLen, // authentication key len + secrets->initSaltLen / 8, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag len + // srtcpAuthTagLen / 8); // authentication tag len + } + else { + senderCrypto = zsrtp_CreateWrapper(zrtp->localSSRC, + 0, + 0L, // keyderivation << 48, + cipher, // encryption algo + authn, // authtentication algo + (unsigned char*)secrets->keyResponder, // Master Key + secrets->respKeyLen / 8, // Master Key length + (unsigned char*)secrets->saltResponder, // Master Salt + secrets->respSaltLen / 8, // Master Salt length + secrets->respKeyLen / 8, // encryption keyl + authKeyLen, // authentication key len + secrets->respSaltLen / 8, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag len + + senderCryptoCtrl = zsrtp_CreateWrapperCtrl(zrtp->localSSRC, + cipher, // encryption algo + authn, // authtication algo + (unsigned char*)secrets->keyResponder, // Master Key + secrets->respKeyLen / 8, // Master Key length + (unsigned char*)secrets->saltResponder, // Master Salt + secrets->respSaltLen / 8, // Master Salt length + secrets->respKeyLen / 8, // encryption keyl + authKeyLen, // authentication key len + secrets->respSaltLen / 8, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag len + // srtcpAuthTagLen / 8); // authentication tag len + } + if (senderCrypto == NULL) { + return 0; + } + // Create a SRTP crypto context for real SSRC sender stream. + // Note: key derivation can be done at this time only if the + // key derivation rate is 0 (disabled). For ZRTP this is the + // case: the key derivation is defined as 2^48 + // which is effectively 0. + zsrtp_deriveSrtpKeys(senderCrypto, 0L); + zrtp->srtpSend = senderCrypto; + + zsrtp_deriveSrtpKeysCtrl(senderCryptoCtrl); + zrtp->srtcpSend = senderCryptoCtrl; + } + if (part == ForReceiver) { + // To decrypt packets: intiator uses responder keys, + // responder initiator keys + // See comment above. + if (secrets->role == Initiator) { + recvCrypto = zsrtp_CreateWrapper(zrtp->peerSSRC, + 0, + 0L, // keyderivation << 48, + cipher, // encryption algo + authn, // authtentication algo + (unsigned char*)secrets->keyResponder, // Master Key + secrets->respKeyLen / 8, // Master Key length + (unsigned char*)secrets->saltResponder, // Master Salt + secrets->respSaltLen / 8, // Master Salt length + secrets->respKeyLen / 8, // encryption keyl + authKeyLen, // authentication key len + secrets->respSaltLen / 8, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag len + + recvCryptoCtrl = zsrtp_CreateWrapperCtrl(zrtp->peerSSRC, + cipher, // encryption algo + authn, // authtication algo + (unsigned char*)secrets->keyResponder, // Master Key + secrets->respKeyLen / 8, // Master Key length + (unsigned char*)secrets->saltResponder, // Master Salt + secrets->respSaltLen / 8, // Master Salt length + secrets->respKeyLen / 8, // encryption keyl + authKeyLen, // authentication key len + secrets->respSaltLen / 8, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag len + // srtcpAuthTagLen / 8); // authentication tag len + } + else { + recvCrypto = zsrtp_CreateWrapper(zrtp->peerSSRC, + 0, + 0L, // keyderivation << 48, + cipher, // encryption algo + authn, // authtentication algo + (unsigned char*)secrets->keyInitiator, // Master Key + secrets->initKeyLen / 8, // Master Key length + (unsigned char*)secrets->saltInitiator, // Master Salt + secrets->initSaltLen / 8, // Master Salt length + secrets->initKeyLen / 8, // encryption keyl + authKeyLen, // authentication key len + secrets->initSaltLen / 8, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag len + + recvCryptoCtrl = zsrtp_CreateWrapperCtrl(zrtp->peerSSRC, + cipher, // encryption algo + authn, // authtication algo + (unsigned char*)secrets->keyInitiator, // Master Key + secrets->initKeyLen / 8, // Master Key length + (unsigned char*)secrets->saltInitiator, // Master Salt + secrets->initSaltLen / 8, // Master Salt length + secrets->initKeyLen / 8, // encryption keyl + authKeyLen, // authentication key len + secrets->initSaltLen / 8, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag len + // srtcpAuthTagLen / 8); // authentication tag len + } + if (recvCrypto == NULL) { + return 0; + } + // Create a SRTP crypto context for real SSRC input stream. + // If the sender didn't provide a SSRC just insert the template + // into the queue. After we received the first packet the real + // crypto context will be created. + // + // Note: key derivation can be done at this time only if the + // key derivation rate is 0 (disabled). For ZRTP this is the + // case: the key derivation is defined as 2^48 + // which is effectively 0. + zsrtp_deriveSrtpKeys(recvCrypto, 0L); + zrtp->srtpReceive = recvCrypto; + + zsrtp_deriveSrtpKeysCtrl(recvCryptoCtrl); + zrtp->srtcpReceive = recvCryptoCtrl; + } + return 1; +} + +static void zrtp_srtpSecretsOff(ZrtpContext* ctx, int32_t part) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + if (part == ForSender) + { + zsrtp_DestroyWrapper(zrtp->srtpSend); + zsrtp_DestroyWrapperCtrl(zrtp->srtcpSend); + zrtp->srtpSend = NULL; + zrtp->srtcpSend = NULL; + } + if (part == ForReceiver) + { + zsrtp_DestroyWrapper(zrtp->srtpReceive); + zsrtp_DestroyWrapperCtrl(zrtp->srtcpReceive); + zrtp->srtpReceive = NULL; + zrtp->srtcpReceive = NULL; + } + + if (zrtp->cb.secure_off) + zrtp->cb.secure_off(&zrtp->base); +} + +static void zrtp_srtpSecretsOn(ZrtpContext* ctx, char* c, char* s, int32_t verified) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + int len; + + len = strlen(c); + if (len > sizeof(zrtp->cipher) - 1) + len = sizeof(zrtp->cipher) - 1; + memcpy(zrtp->cipher, c, len); + zrtp->cipher[len] = '\0'; + + if (zrtp->cb.secure_on) + zrtp->cb.secure_on(&zrtp->base, c); + + if (s && strlen(s) > 0 && zrtp->cb.show_sas) + zrtp->cb.show_sas(&zrtp->base, s, verified); +} + +static void zrtp_handleGoClear(ZrtpContext* ctx) +{ + /* TODO: implement */ +} + +static void zrtp_zrtpNegotiationFailed(ZrtpContext* ctx, int32_t severity, int32_t subCode) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + if (zrtp->cb.negotiation_failed) + zrtp->cb.negotiation_failed(&zrtp->base, severity, subCode); +} + +static void zrtp_zrtpNotSuppOther(ZrtpContext* ctx) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + if (zrtp->cb.not_supported_by_other) + zrtp->cb.not_supported_by_other(&zrtp->base); +} + +static void zrtp_synchEnter(ZrtpContext* ctx) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + pj_mutex_lock(zrtp->zrtpMutex); +} + +static void zrtp_synchLeave(ZrtpContext* ctx) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + pj_mutex_unlock(zrtp->zrtpMutex); +} + +static void zrtp_zrtpAskEnrollment(ZrtpContext* ctx, int32_t info) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + if (zrtp->cb.ask_enrollment) + zrtp->cb.ask_enrollment(&zrtp->base, info); +} + +static void zrtp_zrtpInformEnrollment(ZrtpContext* ctx, int32_t info) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + if (zrtp->cb.inform_enrollment) + zrtp->cb.inform_enrollment(&zrtp->base, info); +} + +static void zrtp_signSAS(ZrtpContext* ctx, uint8_t* sasHash) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + if (zrtp->cb.sign_sas) + zrtp->cb.sign_sas(&zrtp->base, sasHash); +} + +static int32_t zrtp_checkSASSignature(ZrtpContext* ctx, uint8_t* sasHash) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; + + if (zrtp->cb.check_sas_signature) + return zrtp->cb.check_sas_signature(&zrtp->base, sasHash); + return 0; +} + +/* + * Implement the specific ZRTP transport functions + */ +PJ_DEF(void) pjmedia_transport_zrtp_setEnableZrtp(pjmedia_transport *tp, pj_bool_t onOff) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp); + + zrtp->enableZrtp = onOff; +} + +PJ_DEF(pj_bool_t) pjmedia_transport_zrtp_isEnableZrtp(pjmedia_transport *tp) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + PJ_ASSERT_RETURN(tp, PJ_FALSE); + + return zrtp->enableZrtp; + +} + +PJ_DEF(void) pjmedia_transport_zrtp_startZrtp(pjmedia_transport *tp) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp && zrtp->zrtpCtx); + + zrtp_startZrtpEngine(zrtp->zrtpCtx); + zrtp->started = 1; +} + +PJ_DEF(void) pjmedia_transport_zrtp_stopZrtp(pjmedia_transport *tp) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp && zrtp->zrtpCtx); + + zrtp_stopZrtpEngine(zrtp->zrtpCtx); + zrtp->started = 0; +} + +PJ_DEF(void) pjmedia_transport_zrtp_setLocalSSRC(pjmedia_transport *tp, uint32_t ssrc) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp); + + zrtp->localSSRC = ssrc; +} + +PJ_DEF(pj_bool_t) pjmedia_transport_zrtp_isMitmMode(pjmedia_transport *tp) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp); + + return zrtp->mitmMode; +} + +PJ_DEF(void) pjmedia_transport_zrtp_setMitmMode(pjmedia_transport *tp, pj_bool_t mitmMode) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp); + + zrtp->mitmMode = mitmMode; +} + +PJ_DEF(ZrtpContext*) pjmedia_transport_zrtp_getZrtpContext(pjmedia_transport *tp) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + PJ_ASSERT_RETURN(tp, NULL); + + return zrtp->zrtpCtx; +} + +PJ_DEF(void) pjmedia_transport_zrtp_setSASVerified(pjmedia_transport *tp, pj_bool_t verified) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp); + + if (verified) + zrtp_SASVerified(zrtp->zrtpCtx); + else + zrtp_resetSASVerified(zrtp->zrtpCtx); +} + +PJ_DEF(int) pjmedia_transport_zrtp_getPeerZid(pjmedia_transport *tp, unsigned char* data) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp); + + return zrtp_getPeerZid(zrtp->zrtpCtx, data); +} + +PJ_DEF(char*) pjmedia_transport_zrtp_getPeerName(pjmedia_transport *tp) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp); + + return zrtp_getPeerName(zrtp->zrtpCtx); +} + +PJ_DEF(void) pjmedia_transport_zrtp_putPeerName(pjmedia_transport *tp, const char *name) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp); + + zrtp_putPeerName(zrtp->zrtpCtx, name); +} + +PJ_DEF(char*) pjmedia_transport_zrtp_getMultiStreamParameters(pjmedia_transport *tp, pj_int32_t *length) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_assert(tp); + + return zrtp_getMultiStrParams(zrtp->zrtpCtx, length); +} + +PJ_DEF(void) pjmedia_transport_zrtp_setMultiStreamParameters(pjmedia_transport *tp, const char *parameters, pj_int32_t length, pjmedia_transport *master_tp) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + struct tp_zrtp *master_zrtp = (struct tp_zrtp*)master_tp; + pj_assert(tp); + pj_assert(master_tp); + + zrtp_setMultiStrParams(zrtp->zrtpCtx, (char*) parameters, length, master_zrtp->zrtpCtx); +} + +/* + * get_info() is called to get the transport addresses to be put + * in SDP c= line and a=rtcp line. + */ +static pj_status_t transport_get_info(pjmedia_transport *tp, + pjmedia_transport_info *info) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pjmedia_zrtp_info zrtp_info; + int spc_info_idx; + + PJ_ASSERT_RETURN(tp && info, PJ_EINVAL); + PJ_ASSERT_RETURN(info->specific_info_cnt < + PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXCNT, PJ_ETOOMANY); + + zrtp_info.active = zrtp_inState(zrtp->zrtpCtx, SecureState) ? PJ_TRUE : PJ_FALSE; + if (zrtp_info.active) + memcpy(zrtp_info.cipher, zrtp->cipher, sizeof(zrtp->cipher)); + else + zrtp_info.cipher[0] = '\0'; + + spc_info_idx = info->specific_info_cnt++; + info->spc_info[spc_info_idx].type = PJMEDIA_TRANSPORT_TYPE_ZRTP; + + pj_memcpy(&info->spc_info[spc_info_idx].buffer, &zrtp_info, + sizeof(zrtp_info)); + + return pjmedia_transport_get_info(zrtp->slave_tp, info); +} + +/* This is our RTP callback, that is called by the slave transport when it + * receives RTP packet. + */ +static void transport_rtp_cb(void *user_data, void *pkt, pj_ssize_t size) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)user_data; + + pj_uint8_t* buffer = (pj_uint8_t*)pkt; + int32_t newLen = 0; + pj_status_t rc = PJ_SUCCESS; + + pj_assert(zrtp && zrtp->stream_rtcp_cb && pkt); + + // check if this could be a real RTP/SRTP packet. + if ((*buffer & 0xf0) != 0x10) + { + // Could be real RTP, check if we are in secure mode + if (zrtp->srtpReceive == NULL || size < 0) + { + zrtp->stream_rtp_cb(zrtp->stream_user_data, pkt, size); + } + else + { + rc = zsrtp_unprotect(zrtp->srtpReceive, pkt, size, &newLen); + if (rc == 1) + { + zrtp->unprotect++; + zrtp->stream_rtp_cb(zrtp->stream_user_data, pkt, + newLen); + zrtp->unprotect_err = 0; + } + else + { + if (zrtp->cb.show_message) + { + if (rc == -1) + zrtp->cb.show_message(&zrtp->base, zrtp_Warning, zrtp_WarningSRTPauthError); + else + zrtp->cb.show_message(&zrtp->base, zrtp_Warning, zrtp_WarningSRTPreplayError); + } + zrtp->unprotect_err = rc; + /* We failed to decrypt the packet, but forward it regardless to the slave + * transport, it might not have been encrypted after all */ + zrtp->stream_rtp_cb(zrtp->stream_user_data, pkt, size); + } + } + if (!zrtp->started && zrtp->enableZrtp) + pjmedia_transport_zrtp_startZrtp((pjmedia_transport *)zrtp); + + return; + } + + // We assume all other packets are ZRTP packets here. Process + // if ZRTP processing is enabled. Because valid RTP packets are + // already handled we delete any packets here after processing. + if (zrtp->enableZrtp && zrtp->zrtpCtx != NULL) + { + // Get CRC value into crc (see above how to compute the offset) + pj_uint16_t temp = size - CRC_SIZE; + pj_uint32_t crc = *(uint32_t*)(buffer + temp); + crc = pj_ntohl(crc); + + if (!zrtp_CheckCksum(buffer, temp, crc)) + { + if (zrtp->cb.show_message) + zrtp->cb.show_message(&zrtp->base, zrtp_Warning, zrtp_WarningCRCmismatch); + return; + } + + pj_uint32_t magic = *(pj_uint32_t*)(buffer + 4); + magic = pj_ntohl(magic); + + // Check if it is really a ZRTP packet, return, no further processing + if (magic != ZRTP_MAGIC) + return; + + // cover the case if the other party sends _only_ ZRTP packets at the + // beginning of a session. Start ZRTP in this case as well. + if (!zrtp->started) + { + pjmedia_transport_zrtp_startZrtp((pjmedia_transport *)zrtp); + } + // this now points beyond the undefined and length field. + // We need them, thus adjust + unsigned char* zrtpMsg = (buffer + 12); + + // store peer's SSRC in host order, used when creating the CryptoContext + zrtp->peerSSRC = *(pj_uint32_t*)(buffer + 8); + zrtp->peerSSRC = pj_ntohl(zrtp->peerSSRC); + zrtp_processZrtpMessage(zrtp->zrtpCtx, zrtpMsg, zrtp->peerSSRC, size); + } +} + + +/* This is our RTCP callback, that is called by the slave transport when it + * receives RTCP packet. + */ +static void transport_rtcp_cb(void *user_data, void *pkt, pj_ssize_t size) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)user_data; + int32_t newLen = 0; + pj_status_t rc = PJ_SUCCESS; + + pj_assert(zrtp && zrtp->stream_rtcp_cb); + + if (zrtp->srtcpReceive == NULL || size < 0) + { + zrtp->stream_rtcp_cb(zrtp->stream_user_data, pkt, size); + } + else + { + rc = zsrtp_unprotectCtrl(zrtp->srtcpReceive, pkt, size, &newLen); + + if (rc == 1) + { + /* Call stream's callback */ + zrtp->stream_rtcp_cb(zrtp->stream_user_data, pkt, newLen); + } + else + { + // Testing: print some error output + } + } +} + + +/* + * attach() is called by stream to register callbacks that we should + * call on receipt of RTP and RTCP packets. + */ +static pj_status_t transport_attach(pjmedia_transport *tp, + void *user_data, + const pj_sockaddr_t *rem_addr, + const pj_sockaddr_t *rem_rtcp, + unsigned addr_len, + void (*rtp_cb)(void*, + void*, + pj_ssize_t), + void (*rtcp_cb)(void*, + void*, + pj_ssize_t)) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_status_t status; + + PJ_ASSERT_RETURN(tp && rem_addr && addr_len, PJ_EINVAL); + + /* In this example, we will save the stream information and callbacks + * to our structure, and we will register different RTP/RTCP callbacks + * instead. + */ + pj_assert(zrtp->stream_user_data == NULL); + zrtp->stream_user_data = user_data; + zrtp->stream_rtp_cb = rtp_cb; + zrtp->stream_rtcp_cb = rtcp_cb; + + status = pjmedia_transport_attach(zrtp->slave_tp, zrtp, rem_addr, + rem_rtcp, addr_len, &transport_rtp_cb, + &transport_rtcp_cb); + if (status != PJ_SUCCESS) + { + zrtp->stream_user_data = NULL; + zrtp->stream_rtp_cb = NULL; + zrtp->stream_rtcp_cb = NULL; + return status; + } + + return PJ_SUCCESS; +} + +/* + * detach() is called when the media is terminated, and the stream is + * to be disconnected from us. + */ +static void transport_detach(pjmedia_transport *tp, void *strm) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + + PJ_UNUSED_ARG(strm); + PJ_ASSERT_ON_FAIL(tp, return); + + if (zrtp->stream_user_data != NULL) + { + pjmedia_transport_detach(zrtp->slave_tp, zrtp); + zrtp->stream_user_data = NULL; + zrtp->stream_rtp_cb = NULL; + zrtp->stream_rtcp_cb = NULL; + } +} + + +/* + * send_rtp() is called to send RTP packet. The "pkt" and "size" argument + * contain both the RTP header and the payload. + */ +static pj_status_t transport_send_rtp(pjmedia_transport *tp, + const void *pkt, + pj_size_t size) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_uint32_t* pui = (pj_uint32_t*)pkt; + int32_t newLen = 0; + pj_status_t rc = PJ_SUCCESS; + + PJ_ASSERT_RETURN(tp && pkt, PJ_EINVAL); + + + if (!zrtp->started && zrtp->enableZrtp) + { + if (zrtp->localSSRC == 0) + zrtp->localSSRC = pj_ntohl(pui[2]); /* Learn own SSRC before starting ZRTP */ + + pjmedia_transport_zrtp_startZrtp((pjmedia_transport *)zrtp); + } + + if (zrtp->srtpSend == NULL) + { + return pjmedia_transport_send_rtp(zrtp->slave_tp, pkt, size); + } + else + { + if (size+80 > MAX_RTP_BUFFER_LEN) + return PJ_ETOOBIG; + + pj_memcpy(zrtp->sendBuffer, pkt, size); + rc = zsrtp_protect(zrtp->srtpSend, zrtp->sendBuffer, size, &newLen); + zrtp->protect++; + + if (rc == 1) + return pjmedia_transport_send_rtp(zrtp->slave_tp, zrtp->sendBuffer, newLen); + else + return PJ_EIGNORED; + } +} + + +/* + * send_rtcp() is called to send RTCP packet. The "pkt" and "size" argument + * contain the RTCP packet. + */ +static pj_status_t transport_send_rtcp(pjmedia_transport *tp, + const void *pkt, + pj_size_t size) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_status_t rc = PJ_SUCCESS; + int32_t newLen = 0; + PJ_ASSERT_RETURN(tp, PJ_EINVAL); + + /* You may do some processing to the RTCP packet here if you want. */ + if (zrtp->srtcpSend == NULL) + { + return pjmedia_transport_send_rtcp(zrtp->slave_tp, pkt, size); + } + else + { + if (size+80 > MAX_RTCP_BUFFER_LEN) + return PJ_ETOOBIG; + + pj_memcpy(zrtp->sendBufferCtrl, pkt, size); + rc = zsrtp_protectCtrl(zrtp->srtcpSend, zrtp->sendBufferCtrl, size, &newLen); + + if (rc == 1) + return pjmedia_transport_send_rtcp(zrtp->slave_tp, zrtp->sendBufferCtrl, newLen); + else + return PJ_EIGNORED; + } + + /* Send the packet using the slave transport */ +// return pjmedia_transport_send_rtcp(zrtp->slave_tp, pkt, size); +} + + +/* + * This is another variant of send_rtcp(), with the alternate destination + * address in the argument. + */ +static pj_status_t transport_send_rtcp2(pjmedia_transport *tp, + const pj_sockaddr_t *addr, + unsigned addr_len, + const void *pkt, + pj_size_t size) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + PJ_ASSERT_RETURN(tp, PJ_EINVAL); + + return pjmedia_transport_send_rtcp2(zrtp->slave_tp, addr, addr_len, + pkt, size); +} + +/* + * The media_create() is called when the transport is about to be used for + * a new call. + */ +static pj_status_t transport_media_create(pjmedia_transport *tp, + pj_pool_t *sdp_pool, + unsigned options, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + PJ_ASSERT_RETURN(tp, PJ_EINVAL); + + /* if "rem_sdp" is not NULL, it means we are UAS. You may do some + * inspections on the incoming SDP to verify that the SDP is acceptable + * for us. If the SDP is not acceptable, we can reject the SDP by + * returning non-PJ_SUCCESS. + */ + if (rem_sdp) + { + /* Do your stuff.. */ + } + + /* Once we're done with our initialization, pass the call to the + * slave transports to let it do it's own initialization too. + */ + return pjmedia_transport_media_create(zrtp->slave_tp, sdp_pool, options, + rem_sdp, media_index); +} + +/* + * The encode_sdp() is called when we're about to send SDP to remote party, + * either as SDP offer or as SDP answer. + */ +static pj_status_t transport_encode_sdp(pjmedia_transport *tp, + pj_pool_t *sdp_pool, + pjmedia_sdp_session *local_sdp, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + int32_t numVersions, i; + + PJ_ASSERT_RETURN(tp, PJ_EINVAL); + + /* If "rem_sdp" is not NULL, it means we're encoding SDP answer. You may + * do some more checking on the SDP's once again to make sure that + * everything is okay before we send SDP. + */ + if (rem_sdp) + { + /* Do checking stuffs here.. */ + } + + /* Add zrtp-hash attributes to both INVITE and 200 OK. */ + numVersions = zrtp_getNumberSupportedVersions(zrtp->zrtpCtx); + for (i = 0; i < numVersions; i++) { + char *zrtp_hello_hash = zrtp_getHelloHash(zrtp->zrtpCtx, i); + if (zrtp_hello_hash && *zrtp_hello_hash) { + int zrtp_hello_hash_len = strlen(zrtp_hello_hash); + pj_str_t *zrtp_hash_str = PJ_POOL_ALLOC_T(sdp_pool, pj_str_t); + pjmedia_sdp_attr *zrtp_hash = NULL; + + zrtp_hash_str->ptr = zrtp_hello_hash; + zrtp_hash_str->slen = zrtp_hello_hash_len; + + zrtp_hash = pjmedia_sdp_attr_create(sdp_pool, "zrtp-hash", zrtp_hash_str); + if (zrtp_hash && + pjmedia_sdp_attr_add(&local_sdp->media[media_index]->attr_count, local_sdp->media[media_index]->attr, zrtp_hash) == PJ_SUCCESS) { + PJ_LOG(4, (THIS_FILE, "attribute added: a=zrtp-hash:%s", zrtp_hello_hash)); + } + else { + PJ_LOG(4, (THIS_FILE, "error adding attribute: a=zrtp-hash:%s", zrtp_hello_hash)); + } + } + } + + /* You may do anything to the local_sdp, e.g. adding new attributes, or + * even modifying the SDP if you want. + */ + if (0) + { + /* Say we add a proprietary attribute here.. */ + pjmedia_sdp_attr *my_attr; + + my_attr = PJ_POOL_ALLOC_T(sdp_pool, pjmedia_sdp_attr); + pj_strdup2(sdp_pool, &my_attr->name, "X-zrtp"); + pj_strdup2(sdp_pool, &my_attr->value, "some value"); + + pjmedia_sdp_attr_add(&local_sdp->media[media_index]->attr_count, + local_sdp->media[media_index]->attr, + my_attr); + } + + /* And then pass the call to slave transport to let it encode its + * information in the SDP. You may choose to call encode_sdp() to slave + * first before adding your custom attributes if you want. + */ + return pjmedia_transport_encode_sdp(zrtp->slave_tp, sdp_pool, local_sdp, rem_sdp, media_index); +} + +/* + * The media_start() is called once both local and remote SDP have been + * negotiated successfully, and the media is ready to start. Here we can start + * committing our processing. + */ +static pj_status_t transport_media_start(pjmedia_transport *tp, + pj_pool_t *pool, + const pjmedia_sdp_session *local_sdp, + const pjmedia_sdp_session *rem_sdp, + unsigned media_index) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + PJ_ASSERT_RETURN(tp, PJ_EINVAL); + + /* Do something.. */ + + /* And pass the call to the slave transport */ + return pjmedia_transport_media_start(zrtp->slave_tp, pool, local_sdp, + rem_sdp, media_index); +} + +/* + * The media_stop() is called when media has been stopped. + */ +static pj_status_t transport_media_stop(pjmedia_transport *tp) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + PJ_ASSERT_RETURN(tp, PJ_EINVAL); + + /* Do something.. */ + PJ_LOG(4, (THIS_FILE, "Media stop - encrypted packets: %ld, decrypted packets: %ld", + zrtp->protect, zrtp->unprotect)); + + /* And pass the call to the slave transport */ + return pjmedia_transport_media_stop(zrtp->slave_tp); +} + +/* + * simulate_lost() is called to simulate packet lost + */ +static pj_status_t transport_simulate_lost(pjmedia_transport *tp, + pjmedia_dir dir, + unsigned pct_lost) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + + PJ_ASSERT_RETURN(tp, PJ_EINVAL); + + return pjmedia_transport_simulate_lost(zrtp->slave_tp, dir, pct_lost); +} + +/* + * destroy() is called when the transport is no longer needed. + */ +static pj_status_t transport_destroy(pjmedia_transport *tp) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + + PJ_ASSERT_RETURN(tp, PJ_EINVAL); + + PJ_LOG(4, (THIS_FILE, "Destroy - encrypted packets: %ld, decrypted packets: %ld", + zrtp->protect, zrtp->unprotect)); + + /* close the slave transport in case */ + if (zrtp->close_slave && zrtp->slave_tp) + pjmedia_transport_close(zrtp->slave_tp); + + /* Self destruct.. */ + zrtp_stopZrtpEngine(zrtp->zrtpCtx); + zrtp_DestroyWrapper(zrtp->zrtpCtx); + zrtp->zrtpCtx = NULL; + + /* In case mutex is being acquired by other thread */ + pj_mutex_lock(zrtp->zrtpMutex); + pj_mutex_unlock(zrtp->zrtpMutex); + pj_mutex_destroy(zrtp->zrtpMutex); + + pj_pool_release(zrtp->pool); + + return PJ_SUCCESS; +} + + + + -diff -ruN pjproject-2.10/pjmedia/src/pjmedia/vid_stream.c pjsip/pjmedia/src/pjmedia/vid_stream.c ---- pjproject-2.10/pjmedia/src/pjmedia/vid_stream.c 2020-02-14 10:48:27.000000000 +0100 -+++ pjsip/pjmedia/src/pjmedia/vid_stream.c 2021-02-06 21:17:10.577365678 +0100 -@@ -139,9 +139,15 @@ - unsigned dec_delay_cnt; /**< Decoding delay (in frames).*/ - pjmedia_event fmt_event; /**< Buffered fmt_changed event - to avoid deadlock */ -+ pjmedia_event found_keyframe_event; -+ /**< Buffered found keyframe -+ event for delayed republish*/ - pjmedia_event miss_keyframe_event; - /**< Buffered missing keyframe - event for delayed republish*/ -+ pjmedia_event keyframe_req_event; -+ /**< Buffered keyframe request -+ event for delayed republish*/ - - unsigned frame_size; /**< Size of encoded base frame.*/ - unsigned frame_ts_len; /**< Frame length in timestamp. */ -@@ -410,6 +416,11 @@ - pj_memcpy(&stream->fmt_event, event, sizeof(*event)); - return PJ_SUCCESS; - -+ case PJMEDIA_EVENT_KEYFRAME_FOUND: -+ /* Republish this event later from get_frame(). */ -+ pj_memcpy(&stream->found_keyframe_event, event, sizeof(*event)); -+ return PJ_SUCCESS; -+ - case PJMEDIA_EVENT_KEYFRAME_MISSING: - /* Republish this event later from get_frame(). */ - pj_memcpy(&stream->miss_keyframe_event, event, sizeof(*event)); -@@ -523,7 +534,7 @@ - pkt_len); - - /* Send RTCP */ -- send_rtcp(stream, PJ_TRUE, PJ_FALSE); -+ send_rtcp(stream, PJ_TRUE, PJ_FALSE, PJ_FALSE, PJ_FALSE); - - /* Update stats in case the stream is paused */ - stream->rtcp.stat.rtp_tx_last_seq = pj_ntohs(stream->enc->rtp.out_hdr.seq); -@@ -1028,6 +1039,14 @@ - } - - pjmedia_rtcp_rx_rtcp(&stream->rtcp, pkt, bytes_read); -+ -+ /* XXX: posting some event from the RTCP session might be a better option */ -+ if (stream->rtcp.keyframe_requested) { -+ pjmedia_event event; -+ pjmedia_event_init(&event, PJMEDIA_EVENT_KEYFRAME_REQUESTED, NULL, stream); -+ pj_memcpy(&stream->keyframe_req_event, &event, sizeof(event)); -+ } -+ - } - - static pj_status_t put_frame(pjmedia_port *port, -@@ -1419,8 +1438,8 @@ - new_fps.denum = ts_diff; - } - -- /* Only apply the new FPS when it is >0, <=100, and increasing */ -- if (new_fps.num/new_fps.denum <= 100 && -+ /* Only apply the new FPS when it is >0, <=60, and increasing */ -+ if (new_fps.num/new_fps.denum <= 60 && - new_fps.num/new_fps.denum > 0 && - new_fps.num*1.0/new_fps.denum > - stream->dec_max_fps.num*1.0/stream->dec_max_fps.denum) -@@ -1516,8 +1535,10 @@ - /* Override the framerate to be 1.5x higher in the event - * for the renderer. - */ -+#if 0 - fmt_chg_data->new_fmt.det.vid.fps.num *= 3; - fmt_chg_data->new_fmt.det.vid.fps.num /= 2; -+#endif - } else { - pjmedia_format_copy(&stream->info.codec_param->enc_fmt, - &fmt_chg_data->new_fmt); -@@ -1534,12 +1555,25 @@ - stream->fmt_event.type = PJMEDIA_EVENT_NONE; - } - -+ if (stream->found_keyframe_event.type != PJMEDIA_EVENT_NONE) { -+ pjmedia_event_publish(NULL, port, &stream->found_keyframe_event, -+ PJMEDIA_EVENT_PUBLISH_POST_EVENT); -+ stream->found_keyframe_event.type = PJMEDIA_EVENT_NONE; -+ } -+ - if (stream->miss_keyframe_event.type != PJMEDIA_EVENT_NONE) { - pjmedia_event_publish(NULL, port, &stream->miss_keyframe_event, - PJMEDIA_EVENT_PUBLISH_POST_EVENT); - stream->miss_keyframe_event.type = PJMEDIA_EVENT_NONE; - } - -+ if (stream->keyframe_req_event.type != PJMEDIA_EVENT_NONE) { -+ pjmedia_event_publish(NULL, port, &stream->keyframe_req_event, -+ PJMEDIA_EVENT_PUBLISH_POST_EVENT); -+ stream->keyframe_req_event.type = PJMEDIA_EVENT_NONE; -+ } -+ -+ - pj_mutex_lock( stream->jb_mutex ); - - if (stream->dec_frame.size == 0) { -@@ -1846,7 +1880,9 @@ - * local renderer clock) or video setup lag. Note that the actual framerate - * will be continuously calculated based on the incoming RTP timestamps. - */ -+#if 0 - vfd_dec->fps.num = vfd_dec->fps.num * 3 / 2; -+#endif - stream->dec_max_fps = vfd_dec->fps; - - /* Create decoder channel */ -@@ -2362,6 +2398,46 @@ - - return PJ_SUCCESS; - } -+ -+/* -+ * Send RTCP PLI. -+ */ -+PJ_DEF(pj_status_t) pjmedia_vid_stream_send_rtcp_pli( -+ pjmedia_vid_stream *stream) -+{ -+ PJ_ASSERT_RETURN(stream, PJ_EINVAL); -+ -+ if (stream->enc && stream->transport) { -+ void *sr_rr_pkt; -+ pj_uint8_t *pkt; -+ int len, max_len; -+ pj_status_t status; -+ pj_size_t pli_len; -+ -+ /* Build RTCP RR/SR packet */ -+ pjmedia_rtcp_build_rtcp(&stream->rtcp, &sr_rr_pkt, &len); -+ -+ pkt = (pj_uint8_t*) stream->out_rtcp_pkt; -+ pj_memcpy(pkt, sr_rr_pkt, len); -+ max_len = stream->out_rtcp_pkt_size; -+ -+ /* Build RTCP PLI packet */ -+ pli_len = max_len - len; -+ status = pjmedia_rtcp_build_rtcp_pli(&stream->rtcp, pkt+len, &pli_len); -+ if (status != PJ_SUCCESS) { -+ PJ_PERROR(4,(stream->name.ptr, status, "Error generating RTCP PLI")); -+ } else { -+ len += (int)pli_len; -+ } -+ -+ /* Send! */ -+ status = pjmedia_transport_send_rtcp(stream->transport, pkt, len); -+ -+ return status; -+ } -+ -+ return PJ_SUCCESS; -+} - - - /* diff -ruN pjproject-2.10/pjmedia/src/pjmedia/vid_tee.c pjsip/pjmedia/src/pjmedia/vid_tee.c --- pjproject-2.10/pjmedia/src/pjmedia/vid_tee.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia/vid_tee.c 2021-02-06 21:28:30.516675163 +0100 @@ -52,6 +52,7 @@ unsigned dst_port_cnt; vid_tee_dst_port *dst_ports; pj_uint8_t *put_frm_flag; + pj_mutex_t *lock; struct vid_tee_conv_t { pjmedia_converter *conv; @@ -86,6 +87,11 @@ tee->pf = pool->factory; tee->pool = pj_pool_create(tee->pf, "video tee", 500, 500, NULL); + /* Create lock */ + status = pj_mutex_create_simple(pool, "vid-tee-mutex", &tee->lock); + if (status != PJ_SUCCESS) + return status; + /* Initialize video tee structure */ tee->dst_port_maxcnt = max_dst_cnt; tee->dst_ports = (vid_tee_dst_port*) @@ -100,14 +106,16 @@ /* Initialize video tee buffer, its size is one frame */ vfi = pjmedia_get_video_format_info(NULL, fmt->id); - if (vfi == NULL) - return PJMEDIA_EBADFMT; + if (vfi == NULL) { + status = PJMEDIA_EBADFMT; + goto on_error; + } pj_bzero(&vafp, sizeof(vafp)); vafp.size = fmt->det.vid.size; status = vfi->apply_fmt(vfi, &vafp); if (status != PJ_SUCCESS) - return status; + goto on_error; tee->buf_size = vafp.framebytes; @@ -118,7 +126,7 @@ PJMEDIA_DIR_ENCODING, fmt); if (status != PJ_SUCCESS) - return status; + goto on_error;; tee->base.get_frame = &tee_get_frame; tee->base.put_frame = &tee_put_frame; @@ -128,6 +136,12 @@ *p_vid_tee = &tee->base; return PJ_SUCCESS; + +on_error: + pj_mutex_destroy(tee->lock); + tee->lock = NULL; + return status; + } static void realloc_buf(vid_tee_port *vid_tee, @@ -169,21 +183,29 @@ { vid_tee_port *tee = (vid_tee_port*)vid_tee; pjmedia_video_format_detail *vfd; + pj_status_t status; PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN, PJ_EINVAL); - if (tee->dst_port_cnt >= tee->dst_port_maxcnt) - return PJ_ETOOMANY; - - if (vid_tee->info.fmt.id != port->info.fmt.id) - return PJMEDIA_EBADFMT; + pj_mutex_lock(tee->lock); + + if (tee->dst_port_cnt >= tee->dst_port_maxcnt) { + status = PJ_ETOOMANY; + goto end; + } + + if (vid_tee->info.fmt.id != port->info.fmt.id) { + status = PJMEDIA_EBADFMT; + goto end; + } vfd = pjmedia_format_get_video_format_detail(&port->info.fmt, PJ_TRUE); if (vfd->size.w != vid_tee->info.fmt.det.vid.size.w || vfd->size.h != vid_tee->info.fmt.det.vid.size.h) { - return PJMEDIA_EBADFMT; + status = PJMEDIA_EBADFMT; + goto end; } realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)? @@ -194,7 +216,12 @@ tee->dst_ports[tee->dst_port_cnt].option = option; ++tee->dst_port_cnt; - return PJ_SUCCESS; + status = PJ_SUCCESS; + +end: + pj_mutex_unlock(tee->lock); + return status; + } @@ -208,12 +235,17 @@ { vid_tee_port *tee = (vid_tee_port*)vid_tee; pjmedia_video_format_detail *vfd; + pj_status_t status; PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN, PJ_EINVAL); - - if (tee->dst_port_cnt >= tee->dst_port_maxcnt) - return PJ_ETOOMANY; + + pj_mutex_lock(tee->lock); + + if (tee->dst_port_cnt >= tee->dst_port_maxcnt) { + status = PJ_ETOOMANY; + goto end; + } pj_bzero(&tee->tee_conv[tee->dst_port_cnt], sizeof(tee->tee_conv[0])); @@ -226,17 +258,18 @@ const pjmedia_video_format_info *vfi; pjmedia_video_apply_fmt_param vafp; pjmedia_conversion_param conv_param; - pj_status_t status; vfi = pjmedia_get_video_format_info(NULL, port->info.fmt.id); - if (vfi == NULL) + if (vfi == NULL) { - return PJMEDIA_EBADFMT; + status = PJMEDIA_EBADFMT; + goto end; + } pj_bzero(&vafp, sizeof(vafp)); vafp.size = port->info.fmt.det.vid.size; status = vfi->apply_fmt(vfi, &vafp); if (status != PJ_SUCCESS) - return status; + goto end; realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)? 2: 1, vafp.framebytes); @@ -248,7 +280,7 @@ NULL, tee->pool, &conv_param, &tee->tee_conv[tee->dst_port_cnt].conv); if (status != PJ_SUCCESS) - return status; + goto end; tee->tee_conv[tee->dst_port_cnt].conv_buf_size = vafp.framebytes; } else { @@ -259,8 +291,12 @@ tee->dst_ports[tee->dst_port_cnt].dst = port; tee->dst_ports[tee->dst_port_cnt].option = option; ++tee->dst_port_cnt; - - return PJ_SUCCESS; + + status = PJ_SUCCESS; + +end: + pj_mutex_unlock(tee->lock); + return status; } @@ -276,6 +312,8 @@ PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN, PJ_EINVAL); + pj_mutex_lock(tee->lock); + for (i = 0; i < tee->dst_port_cnt; ++i) { if (tee->dst_ports[i].dst == port) { if (tee->tee_conv[i].conv) @@ -286,10 +324,13 @@ pj_array_erase(tee->tee_conv, sizeof(tee->tee_conv[0]), tee->dst_port_cnt, i); --tee->dst_port_cnt; + + pj_mutex_unlock(tee->lock); return PJ_SUCCESS; } } + pj_mutex_unlock(tee->lock); return PJ_ENOTFOUND; } @@ -300,6 +341,12 @@ unsigned i, j; const pj_uint8_t PUT_FRM_DONE = 1; + if (pj_mutex_trylock(tee->lock) != PJ_SUCCESS) { + /* we are busy adding / removing consumers */ + return PJ_SUCCESS; + } + + pj_bzero(tee->put_frm_flag, tee->dst_port_cnt * sizeof(tee->put_frm_flag[0])); @@ -364,6 +411,7 @@ } } + pj_mutex_unlock(tee->lock); return PJ_SUCCESS; } @@ -383,6 +431,11 @@ PJ_ASSERT_RETURN(port && port->info.signature==TEE_PORT_SIGN, PJ_EINVAL); + if (tee->lock) { + pj_mutex_destroy(tee->lock); + tee->lock = NULL; + } + pj_pool_release(tee->pool); if (tee->buf_pool) pj_pool_release(tee->buf_pool); diff -ruN pjproject-2.10/pjmedia/src/pjmedia-audiodev/alsa_dev.c pjsip/pjmedia/src/pjmedia-audiodev/alsa_dev.c --- pjproject-2.10/pjmedia/src/pjmedia-audiodev/alsa_dev.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia-audiodev/alsa_dev.c 2021-02-06 23:08:42.203153000 +0100 @@ -43,7 +43,7 @@ #define ALSASOUND_CAPTURE 2 #define MAX_SOUND_CARDS 5 #define MAX_SOUND_DEVICES_PER_CARD 5 -#define MAX_DEVICES 32 +#define MAX_DEVICES 128 #define MAX_MIX_NAME_LEN 64 /* Set to 1 to enable tracing */ @@ -74,6 +74,10 @@ pjmedia_aud_play_cb play_cb, void *user_data, pjmedia_aud_stream **p_strm); +static void alsa_factory_set_observer(pjmedia_aud_dev_factory *f, + pjmedia_aud_dev_change_callback cb); +static int alsa_factory_get_default_rec_dev(pjmedia_aud_dev_factory *f); +static int alsa_factory_get_default_play_dev(pjmedia_aud_dev_factory *f); /* * Stream prototypes @@ -90,6 +94,15 @@ static pj_status_t alsa_stream_stop(pjmedia_aud_stream *strm); static pj_status_t alsa_stream_destroy(pjmedia_aud_stream *strm); +/* alsa device info */ +struct alsa_dev_info +{ + pjmedia_aud_dev_info info; + char alsa_name[64]; + int input_count; + int output_count; +}; + struct alsa_factory { @@ -99,7 +112,7 @@ pj_pool_t *base_pool; unsigned dev_cnt; - pjmedia_aud_dev_info devs[MAX_DEVICES]; + struct alsa_dev_info devs[MAX_DEVICES]; char pb_mixer_name[MAX_MIX_NAME_LEN]; }; @@ -140,7 +153,10 @@ &alsa_factory_get_dev_info, &alsa_factory_default_param, &alsa_factory_create_stream, - &alsa_factory_refresh + &alsa_factory_refresh, + &alsa_factory_set_observer, + &alsa_factory_get_default_rec_dev, + &alsa_factory_get_default_play_dev }; static pjmedia_aud_stream_op alsa_stream_op = @@ -214,9 +230,9 @@ } -static pj_status_t add_dev (struct alsa_factory *af, const char *dev_name) +static pj_status_t add_dev (struct alsa_factory *af, const char *dev_name, const char *dev_desc) { - pjmedia_aud_dev_info *adi; + struct alsa_dev_info *adi; snd_pcm_t* pcm; int pb_result, ca_result; @@ -258,23 +274,63 @@ pj_bzero(adi, sizeof(*adi)); /* Set device name */ - strncpy(adi->name, dev_name, sizeof(adi->name)); + strncpy(adi->alsa_name, dev_name, sizeof(adi->alsa_name)); + + /* Set comprehensive device name */ + int name_size = sizeof(adi->info.name); + if (dev_desc) { + pj_bool_t name_set = PJ_FALSE; + if (strncmp("sysdefault", dev_name, 10) == 0) { + /* Only use first line for default device*/ + char *ptr = strstr(dev_desc, "\n"); + if (ptr) { + int len = ptr - dev_desc; + strncpy(adi->info.name, dev_desc, (len >= name_size-1)?name_size:len); + name_set = PJ_TRUE; + } + } else if (strncmp("iec958", dev_name, 6) == 0) { + /* Mangle name for SPDIF devices*/ + char *ptr = strstr(dev_desc, ","); + if (ptr) { + int len = ptr - dev_desc; + if (len + 18 < name_size) { + strncpy(adi->info.name, dev_desc, len); + strncpy(adi->info.name+len, ", Digital (S/PDIF)", 18); + name_set = PJ_TRUE; + } + } + } + + if (!name_set) { + /* Use the entire description for other device names */ + int i = 0; + while (i < name_size-1 && dev_desc[i] != '\0') { + if (dev_desc[i] == '\n' || dev_desc[i] == '\r') + adi->info.name[i] = ' '; + else + adi->info.name[i] = dev_desc[i]; + i++; + } + } + } else { + strncpy(adi->info.name, dev_name, name_size); + } /* Check the number of playback channels */ - adi->output_count = (pb_result>=0) ? 1 : 0; + adi->info.output_count = (pb_result>=0) ? 1 : 0; /* Check the number of capture channels */ - adi->input_count = (ca_result>=0) ? 1 : 0; + adi->info.input_count = (ca_result>=0) ? 1 : 0; /* Set the default sample rate */ - adi->default_samples_per_sec = 8000; + adi->info.default_samples_per_sec = 8000; /* Driver name */ - strcpy(adi->driver, "ALSA"); + strcpy(adi->info.driver, "ALSA"); ++af->dev_cnt; - PJ_LOG (5,(THIS_FILE, "Added sound device %s", adi->name)); + PJ_LOG (5,(THIS_FILE, "Added sound device %s", adi->alsa_name)); return PJ_SUCCESS; } @@ -399,10 +455,26 @@ n = hints; while (*n != NULL) { char *name = snd_device_name_get_hint(*n, "NAME"); - if (name != NULL) { - if (0 != strcmp("null", name)) - add_dev(af, name); + char *desc = snd_device_name_get_hint(*n, "DESC"); + if (name != NULL) { + if (strncmp("null", name, 4) == 0 || + strncmp("front", name, 5) == 0 || + strncmp("rear", name, 4) == 0 || + strncmp("side", name, 4) == 0 || + strncmp("dmix", name, 4) == 0 || + strncmp("dsnoop", name, 6) == 0 || + strncmp("hw", name, 2) == 0 || + strncmp("plughw", name, 6) == 0 || + strncmp("center_lfe", name, 10) == 0 || + strncmp("surround", name, 8) == 0 || + (strncmp("default", name, 7) == 0 && strstr(name, ":CARD=") != NULL)) { + /* skip these devices, 'sysdefault' always contains the relevant information */ + ; + } else { + add_dev(af, name, desc); + } free(name); + free(desc); } n++; } @@ -440,7 +512,7 @@ PJ_ASSERT_RETURN(index>=0 && indexdev_cnt, PJ_EINVAL); - pj_memcpy(info, &af->devs[index], sizeof(*info)); + pj_memcpy(info, &af->devs[index].info, sizeof(*info)); info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; return PJ_SUCCESS; @@ -452,22 +524,22 @@ pjmedia_aud_param *param) { struct alsa_factory *af = (struct alsa_factory*)f; - pjmedia_aud_dev_info *adi; + struct alsa_dev_info *adi; PJ_ASSERT_RETURN(index>=0 && indexdev_cnt, PJ_EINVAL); adi = &af->devs[index]; pj_bzero(param, sizeof(*param)); - if (adi->input_count && adi->output_count) { + if (adi->info.input_count && adi->info.output_count) { param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; param->rec_id = index; param->play_id = index; - } else if (adi->input_count) { + } else if (adi->info.input_count) { param->dir = PJMEDIA_DIR_CAPTURE; param->rec_id = index; param->play_id = PJMEDIA_AUD_INVALID_DEV; - } else if (adi->output_count) { + } else if (adi->info.output_count) { param->dir = PJMEDIA_DIR_PLAYBACK; param->play_id = index; param->rec_id = PJMEDIA_AUD_INVALID_DEV; @@ -475,11 +547,11 @@ return PJMEDIA_EAUD_INVDEV; } - param->clock_rate = adi->default_samples_per_sec; + param->clock_rate = adi->info.default_samples_per_sec; param->channel_count = 1; - param->samples_per_frame = adi->default_samples_per_sec * 20 / 1000; + param->samples_per_frame = adi->info.default_samples_per_sec * 20 / 1000; param->bits_per_sample = 16; - param->flags = adi->caps; + param->flags = adi->info.caps; param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY; param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY; @@ -626,9 +698,9 @@ /* Open PCM for playback */ PJ_LOG (5,(THIS_FILE, "open_playback: Open playback device '%s'", - stream->af->devs[param->play_id].name)); + stream->af->devs[param->play_id].alsa_name)); result = snd_pcm_open (&stream->pb_pcm, - stream->af->devs[param->play_id].name, + stream->af->devs[param->play_id].alsa_name, SND_PCM_STREAM_PLAYBACK, 0); if (result < 0) @@ -722,7 +794,7 @@ PJ_LOG (5,(THIS_FILE, "Opened device alsa(%s) for playing, sample rate=%d" ", ch=%d, bits=%d, period size=%d frames, latency=%d ms", - stream->af->devs[param->play_id].name, + stream->af->devs[param->play_id].alsa_name, rate, param->channel_count, param->bits_per_sample, stream->pb_frames, (int)stream->param.output_latency_ms)); @@ -746,9 +818,9 @@ /* Open PCM for capture */ PJ_LOG (5,(THIS_FILE, "open_capture: Open capture device '%s'", - stream->af->devs[param->rec_id].name)); + stream->af->devs[param->rec_id].alsa_name)); result = snd_pcm_open (&stream->ca_pcm, - stream->af->devs[param->rec_id].name, + stream->af->devs[param->rec_id].alsa_name, SND_PCM_STREAM_CAPTURE, 0); if (result < 0) @@ -842,7 +914,7 @@ PJ_LOG (5,(THIS_FILE, "Opened device alsa(%s) for capture, sample rate=%d" ", ch=%d, bits=%d, period size=%d frames, latency=%d ms", - stream->af->devs[param->rec_id].name, + stream->af->devs[param->rec_id].alsa_name, rate, param->channel_count, param->bits_per_sample, stream->ca_frames, (int)stream->param.input_latency_ms)); @@ -903,6 +975,27 @@ return PJ_SUCCESS; } +/* API: set audio device change observer */ +static void alsa_factory_set_observer(pjmedia_aud_dev_factory *f, + pjmedia_aud_dev_change_callback cb) +{ + PJ_UNUSED_ARG(f); + PJ_UNUSED_ARG(cb); +} + +/* API: get default recording device */ +static int alsa_factory_get_default_rec_dev(pjmedia_aud_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return -1; +} + +/* API: get default playback device */ +static int alsa_factory_get_default_play_dev(pjmedia_aud_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return -1; +} /* API: get running parameter */ static pj_status_t alsa_stream_get_param(pjmedia_aud_stream *s, diff -ruN pjproject-2.10/pjmedia/src/pjmedia-audiodev/coreaudio_dev.m pjsip/pjmedia/src/pjmedia-audiodev/coreaudio_dev.m --- pjproject-2.10/pjmedia/src/pjmedia-audiodev/coreaudio_dev.m 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia-audiodev/coreaudio_dev.m 2021-02-06 22:51:16.641714862 +0100 @@ -173,6 +173,11 @@ void *user_data, pjmedia_aud_stream **p_aud_strm); +static void ca_factory_set_observer(pjmedia_aud_dev_factory *f, + pjmedia_aud_dev_change_callback cb); +static int ca_factory_get_default_rec_dev(pjmedia_aud_dev_factory *f); +static int ca_factory_get_default_play_dev(pjmedia_aud_dev_factory *f); + static pj_status_t ca_stream_get_param(pjmedia_aud_stream *strm, pjmedia_aud_param *param); static pj_status_t ca_stream_get_cap(pjmedia_aud_stream *strm, @@ -206,7 +211,10 @@ &ca_factory_get_dev_info, &ca_factory_default_param, &ca_factory_create_stream, - &ca_factory_refresh + &ca_factory_refresh, + &ca_factory_set_observer, + &ca_factory_get_default_rec_dev, + &ca_factory_get_default_play_dev }; static pjmedia_aud_stream_op stream_op = @@ -717,6 +725,169 @@ return PJ_SUCCESS; } +static OSStatus property_listener_proc(AudioObjectID objectID, + UInt32 numberAddresses, + const AudioObjectPropertyAddress inAddresses[], + void *clientData) +{ + pjmedia_aud_dev_change_callback cb = (pjmedia_aud_dev_change_callback)clientData; + pjmedia_aud_dev_change_event event; + UInt32 i; + + for(i = 0; i < numberAddresses; i++) { + event = 0; + switch (inAddresses[i].mSelector) { + case kAudioHardwarePropertyDefaultInputDevice: + event = DEFAULT_INPUT_CHANGED; + break; + case kAudioHardwarePropertyDefaultOutputDevice: + event = DEFAULT_OUTPUT_CHANGED; + break; + case kAudioHardwarePropertyDevices: + event = DEVICE_LIST_CHANGED; + break; + default: + break; + } + if (event > 0) { + (cb)(event); + } + } + + return noErr; +} + +/* API: set audio device change observer */ +static void ca_factory_set_observer(pjmedia_aud_dev_factory *f, + pjmedia_aud_dev_change_callback cb) +{ + AudioObjectPropertyAddress addr; + OSStatus ostatus; + + /* observer for devices list */ + addr.mSelector = kAudioHardwarePropertyDevices; + addr.mScope = kAudioObjectPropertyScopeGlobal; + addr.mElement = kAudioObjectPropertyElementMaster; + + if (cb) { + ostatus = AudioObjectAddPropertyListener(kAudioObjectSystemObject, + &addr, + property_listener_proc, + cb); + } else { + ostatus = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, + &addr, + property_listener_proc, + cb); + } + if (ostatus != noErr) { + PJ_LOG(5,(THIS_FILE, "Error %sregistering devices list observer", cb==NULL ? "un-" : "")); + } + + /* observer for default input device */ + addr.mSelector = kAudioHardwarePropertyDefaultInputDevice; + + if (cb) { + ostatus = AudioObjectAddPropertyListener(kAudioObjectSystemObject, + &addr, + property_listener_proc, + cb); + } else { + ostatus = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, + &addr, + property_listener_proc, + cb); + } + if (ostatus != noErr) { + PJ_LOG(5,(THIS_FILE, "Error %sregistering default input device observer", cb==NULL ? "un-" : "")); + } + + /* observer for default output device */ + addr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + + if (cb) { + ostatus = AudioObjectAddPropertyListener(kAudioObjectSystemObject, + &addr, + property_listener_proc, + cb); + } else { + ostatus = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, + &addr, + property_listener_proc, + cb); + } + if (ostatus != noErr) { + PJ_LOG(5,(THIS_FILE, "Error %sregistering default output device observer", cb==NULL ? "un-" : "")); + } + +} + +/* API: get default recording device */ +static int ca_factory_get_default_rec_dev(pjmedia_aud_dev_factory *f) +{ + AudioDeviceID dev_id = kAudioObjectUnknown; + AudioObjectPropertyAddress addr; + UInt32 size; + OSStatus ostatus; + int i; + int idx = -1; + struct coreaudio_factory *cf = (struct coreaudio_factory*)f; + + /* Find default audio input device */ + addr.mSelector = kAudioHardwarePropertyDefaultInputDevice; + addr.mScope = kAudioObjectPropertyScopeGlobal; + addr.mElement = kAudioObjectPropertyElementMaster; + size = sizeof(dev_id); + + ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &addr, 0, NULL, + &size, (void *)&dev_id); + if (ostatus == noErr) { + for (i = 0; i < cf->dev_count; i++) { + struct coreaudio_dev_info *cdi; + cdi = &cf->dev_info[i]; + if (cdi->dev_id == dev_id) { + idx = i; + break; + } + } + } + return idx; +} + +/* API: get default playback device */ +static int ca_factory_get_default_play_dev(pjmedia_aud_dev_factory *f) +{ + AudioDeviceID dev_id = kAudioObjectUnknown; + AudioObjectPropertyAddress addr; + UInt32 size; + OSStatus ostatus; + int i; + int idx = -1; + struct coreaudio_factory *cf = (struct coreaudio_factory*)f; + + /* Find default audio output device */ + addr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + addr.mScope = kAudioObjectPropertyScopeGlobal; + addr.mElement = kAudioObjectPropertyElementMaster; + size = sizeof(dev_id); + + ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &addr, 0, NULL, + &size, (void *)&dev_id); + if (ostatus == noErr) { + for (i = 0; i < cf->dev_count; i++) { + struct coreaudio_dev_info *cdi; + cdi = &cf->dev_info[i]; + if (cdi->dev_id == dev_id) { + idx = i; + break; + } + } + } + return idx; +} + OSStatus resampleProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, @@ -1862,7 +2033,6 @@ { strm->param.input_latency_ms = (latency + latency2) * 1000 / strm->param.clock_rate; - strm->param.input_latency_ms++; } } #else @@ -1870,7 +2040,6 @@ strm->param.input_latency_ms = (unsigned)(([strm->sess inputLatency] + [strm->sess IOBufferDuration]) * 1000); - strm->param.input_latency_ms++; } else return PJMEDIA_EAUD_INVCAP; #endif @@ -1903,7 +2072,6 @@ { strm->param.output_latency_ms = (latency + latency2) * 1000 / strm->param.clock_rate; - strm->param.output_latency_ms++; } } #else @@ -1911,11 +2079,10 @@ strm->param.output_latency_ms = (unsigned)(([strm->sess outputLatency] + [strm->sess IOBufferDuration]) * 1000); - strm->param.output_latency_ms++; } else return PJMEDIA_EAUD_INVCAP; #endif - *(unsigned*)pval = (++strm->param.output_latency_ms * 2); + *(unsigned*)pval = strm->param.output_latency_ms; return PJ_SUCCESS; } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING && (strm->param.dir & PJMEDIA_DIR_PLAYBACK)) diff -ruN pjproject-2.10/pjmedia/src/pjmedia-audiodev/wmme_dev.c pjsip/pjmedia/src/pjmedia-audiodev/wmme_dev.c --- pjproject-2.10/pjmedia/src/pjmedia-audiodev/wmme_dev.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia-audiodev/wmme_dev.c 2021-02-06 22:51:16.641714862 +0100 @@ -32,6 +32,7 @@ #endif #include +#include #include #include @@ -69,6 +70,15 @@ #define THIS_FILE "wmme_dev.c" +/* WMME device change observer */ +struct wmme_dev_observer +{ + pj_thread_t *thread; + pj_pool_t *pool; + pjmedia_aud_dev_change_callback cb; + HWND hWnd; +}; + /* WMME device info */ struct wmme_dev_info { @@ -87,6 +97,8 @@ unsigned dev_count; struct wmme_dev_info *dev_info; + + struct wmme_dev_observer dev_observer; }; @@ -151,6 +163,11 @@ pjmedia_aud_play_cb play_cb, void *user_data, pjmedia_aud_stream **p_aud_strm); +static void factory_set_observer(pjmedia_aud_dev_factory *f, + pjmedia_aud_dev_change_callback cb); +static int factory_get_default_rec_dev(pjmedia_aud_dev_factory *f); +static int factory_get_default_play_dev(pjmedia_aud_dev_factory *f); + static pj_status_t stream_get_param(pjmedia_aud_stream *strm, pjmedia_aud_param *param); @@ -174,7 +191,10 @@ &factory_get_dev_info, &factory_default_param, &factory_create_stream, - &factory_refresh + &factory_refresh, + &factory_set_observer, + &factory_get_default_rec_dev, + &factory_get_default_play_dev }; static pjmedia_aud_stream_op stream_op = @@ -1336,6 +1356,201 @@ return PJ_SUCCESS; } +/* Processes OS messages arriving at the hWnd window */ +INT_PTR WINAPI ProcessOSMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + /* wf is used in order to query the number of audio devices currently handled */ + static struct wmme_factory *wf = NULL; + + switch( message ) + { + case WM_CREATE: + /* Initialize wf pointer on the first run */ + if (wf == NULL) + { + CREATESTRUCT *CrtStrPtr = (CREATESTRUCT *) lParam; + wf = (struct wmme_factory *)(CrtStrPtr->lpCreateParams); + } + break; + case WM_DEVICECHANGE: + /* Possible insertion or removal of device. There's some issues: + + - Some devices/drivers does not trigger arrival nor + removecomplete events, but only devnodes_changed events. + Therefore, we process all of those type of events. + + - Some hardware can send many devnodes_changed events at the + same time (up to ~15 of such events). These batches are + detected using temporal locality, using constMaxBatchPeriod_. + Once the device is detected, the rest of redundant events + are discarded. In order to know if there's a new device or not, + actual audio devices count is compared to stored audio devices + count (via wf->dev_count). + + - Hardware takes some time to settle and be recognized by + drivers. A small window of time is given in order to account + for this (constMaxSettleTime_); + + Settle time should be slightly lower than batch period. + */ + if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE || wParam == DBT_DEVNODES_CHANGED) { + const int constMaxBatchPeriod_ = 3; /* seconds */ + const int constMaxSettleTime_ = (constMaxBatchPeriod_ * 1000) - 500; /* milliseconds */ + + /* Loop that allows hardware to settle */ + int settleTimeLeft = constMaxSettleTime_; + while (settleTimeLeft > 0) { + /* Check if actual devices lists (I/O) sizes have actually + changed before notifying upper levels. Consider input + devices, output devices and a WAVE MAPPER device for each. + */ + if(waveInGetNumDevs() + waveOutGetNumDevs() + 2 != wf->dev_count) { + /* Hardware changed */ + if (wf->dev_observer.cb) { + wf->dev_observer.cb(DEVICE_LIST_CHANGED); + } + break; + } else { + /* Hardware is settling... */ + Sleep(250); + settleTimeLeft -= 250; + } + } + } + break; + case WM_CLOSE: + if (!DestroyWindow(hWnd)) { + PJ_LOG(4,(THIS_FILE, "Couldn't destroy message window")); + } + break; + case WM_DESTROY: + PostQuitMessage(0); + break; + default: + break; + } + + return 1; +} + +static pj_status_t create_os_messages_window(struct wmme_factory *wf) +{ + pj_status_t status = PJ_EBUG; + WNDCLASSEX wndClass; + HWND hWnd; + + /* Set up and register window class */ + ZeroMemory(&wndClass, sizeof(WNDCLASSEX)); + wndClass.cbSize = sizeof(WNDCLASSEX); + wndClass.style = CS_OWNDC; + wndClass.lpfnWndProc = (WNDPROC)(ProcessOSMessage); + wndClass.hInstance = (HINSTANCE)(GetModuleHandle(0)); + wndClass.lpszClassName = "DeviceChangeMessageWindow"; + + if (RegisterClassEx(&wndClass)) { + /* Create the window that will receive OS messages */ + hWnd = CreateWindowEx( 0, "DeviceChangeMessageWindow", NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, (LPVOID)(wf)); + if (hWnd != NULL) { + wf->dev_observer.hWnd = hWnd; + if (UpdateWindow(hWnd) != 0) { + status = PJ_SUCCESS; + } + } else { + PJ_LOG(4,(THIS_FILE, "Error creating window to receive device change events")); + } + } + + return status; + +} + +static pj_status_t dispatch_os_messages(void) +{ + pj_status_t status = PJ_SUCCESS; + MSG msg; + int ret; + + /* Process OS messages with low cpu-usage wait loop */ + while((ret = GetMessage(&msg, NULL, 0, 0)) != 0) { + if (ret == -1) { + PJ_LOG(4,(THIS_FILE, "Couldn't process OS message")); + status = PJ_EBUG; + break; + } else { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + return status; + +} + +/* WMME device observer thread thread. */ +static int PJ_THREAD_FUNC wmme_dev_observer_thread(void *arg) +{ + struct wmme_factory *wf = (struct wmme_factory*)arg; + pj_status_t status; + + status = create_os_messages_window(wf); + if (status == PJ_SUCCESS) { + status = dispatch_os_messages(); + if (status != PJ_SUCCESS) { + PJ_LOG(4,(THIS_FILE, "Error dispatching device detection window events")); + } + } else { + PJ_LOG(4,(THIS_FILE, "Failed to create window for receiving device detection events")); + } + + return status; +} + +/* API: set audio device change observer */ +static void factory_set_observer(pjmedia_aud_dev_factory *f, + pjmedia_aud_dev_change_callback cb) +{ + struct wmme_factory *wf = (struct wmme_factory*)f; + pj_pool_t *pool; + pj_status_t status; + + if (cb) { + pool = pj_pool_create(wf->pf, "wmme-dev-observer", 1000, 1000, NULL); + PJ_ASSERT_ON_FAIL(pool != NULL, {return;}); + status = pj_thread_create(pool, "wmme_observer", &wmme_dev_observer_thread, wf, 0, 0, &wf->dev_observer.thread); + if (status != PJ_SUCCESS) { + PJ_LOG(4,(THIS_FILE, "Failed to create WMME device detection thread")); + wf->dev_observer.thread = NULL; + return; + } + wf->dev_observer.cb = cb; + } else { + wf->dev_observer.cb = NULL; + if (wf->dev_observer.hWnd) { + CloseWindow(wf->dev_observer.hWnd); + wf->dev_observer.hWnd = NULL; + } + pj_thread_join(wf->dev_observer.thread); + pj_thread_destroy(wf->dev_observer.thread); + wf->dev_observer.thread = NULL; + } +} + +/* API: get default recording device */ +static int factory_get_default_rec_dev(pjmedia_aud_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + /* Let PJMEDIA pick the first one available */ + return -1; +} + +/* API: get default playback device */ +static int factory_get_default_play_dev(pjmedia_aud_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + /* Let PJMEDIA pick the first one available */ + return -1; +} + /* API: Get stream info. */ static pj_status_t stream_get_param(pjmedia_aud_stream *s, pjmedia_aud_param *pi) --- pjproject-2.10/pjmedia/src/pjmedia-codec/opus.c 2021-03-19 04:55:21.491267756 +0100 +++ pjsip/pjmedia/src/pjmedia-codec/opus.c 2021-03-19 05:48:19.457507442 +0100 @@ -542,7 +542,7 @@ static pj_status_t factory_enum_codecs( pjmedia_codec_factory *factory, */ codecs[0].encoding_name = pj_str("opus"); codecs[0].clock_rate = 48000; - codecs[0].channel_cnt = 2; + codecs[0].channel_cnt = 1; *count = 1; } +diff -ruN pjproject-2.10/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c pjsip/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c +--- pjproject-2.10/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c 2021-02-06 22:51:16.641714862 +0100 +@@ -62,16 +62,7 @@ + #endif + + #if LIBAVCODEC_VER_AT_LEAST(53,61) +-# if LIBAVCODEC_VER_AT_LEAST(54,59) +- /* Not sure when AVCodec::encode is obsoleted/removed. */ +-# define AVCODEC_HAS_ENCODE(c) (c->encode2) +-# else +- /* Not sure when AVCodec::encode2 is introduced. It appears in +- * libavcodec 53.61 where some codecs actually still use AVCodec::encode +- * (e.g: H263, H264). +- */ +-# define AVCODEC_HAS_ENCODE(c) (c->encode || c->encode2) +-# endif ++# define AVCODEC_HAS_ENCODE(c) (c->encode2) + # define AV_OPT_SET(obj,name,val,opt) (av_opt_set(obj,name,val,opt)==0) + # define AV_OPT_SET_INT(obj,name,val) (av_opt_set_int(obj,name,val,0)==0) + #else +@@ -266,6 +257,7 @@ + /* H264 constants */ + #define PROFILE_H264_BASELINE 66 + #define PROFILE_H264_MAIN 77 ++#define PROFILE_H264_HIGH 100 + + /* Codec specific functions */ + #if PJMEDIA_HAS_FFMPEG_CODEC_H264 +@@ -404,6 +396,9 @@ + case PROFILE_H264_MAIN: + profile = "main"; + break; ++ case PROFILE_H264_HIGH: ++ profile = "high"; ++ break; + default: + break; + } +@@ -440,11 +435,11 @@ + /* Misc x264 settings (performance, quality, latency, etc). + * Let's just use the x264 predefined preset & tune. + */ +- if (!AV_OPT_SET(ctx->priv_data, "preset", "veryfast", 0)) { ++ if (!AV_OPT_SET(ctx->priv_data, "preset", "ultrafast", 0)) { + PJ_LOG(3, (THIS_FILE, "Failed to set x264 preset 'veryfast'")); + } +- if (!AV_OPT_SET(ctx->priv_data, "tune", "animation+zerolatency", 0)) { +- PJ_LOG(3, (THIS_FILE, "Failed to set x264 tune 'zerolatency'")); ++ if (!AV_OPT_SET(ctx->priv_data, "tune", "fastdecode+zerolatency", 0)) { ++ PJ_LOG(3, (THIS_FILE, "Failed to set x264 tune 'fastdecode+zerolatency'")); + } + } + +@@ -1430,12 +1425,12 @@ + /* Check if encoder has been opened */ + PJ_ASSERT_RETURN(ff->enc_ctx, PJ_EINVALIDOP); + +-#ifdef PJMEDIA_USE_OLD_FFMPEG +- avcodec_get_frame_defaults(&avframe); +-#else ++//#ifdef PJMEDIA_USE_OLD_FFMPEG ++// avcodec_get_frame_defaults(&avframe); ++//#else + pj_bzero(&avframe, sizeof(avframe)); + av_frame_unref(&avframe); +-#endif ++//#endif + + // Let ffmpeg manage the timestamps + /* +@@ -1692,12 +1687,12 @@ + * whole decoding session, and seems to be freed when the codec context + * closed). + */ +-#ifdef PJMEDIA_USE_OLD_FFMPEG +- avcodec_get_frame_defaults(&avframe); +-#else ++//#ifdef PJMEDIA_USE_OLD_FFMPEG ++// avcodec_get_frame_defaults(&avframe); ++//#else + pj_bzero(&avframe, sizeof(avframe)); + av_frame_unref(&avframe); +-#endif ++//#endif + + /* Init packet, the container of the encoded data */ + av_init_packet(&avpacket); +@@ -1705,12 +1700,12 @@ + avpacket.size = (int)input->size; + + /* ffmpeg warns: +- * - input buffer padding, at least FF_INPUT_BUFFER_PADDING_SIZE ++ * - input buffer padding, at least AV_INPUT_BUFFER_PADDING_SIZE + * - null terminated + * Normally, encoded buffer is allocated more than needed, so lets just + * bzero the input buffer end/pad, hope it will be just fine. + */ +- pj_bzero(avpacket.data+avpacket.size, FF_INPUT_BUFFER_PADDING_SIZE); ++ pj_bzero(avpacket.data+avpacket.size, AV_INPUT_BUFFER_PADDING_SIZE); + + output->bit_info = 0; + output->timestamp = input->timestamp; diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/avf_dev.m pjsip/pjmedia/src/pjmedia-videodev/avf_dev.m --- pjproject-2.10/pjmedia/src/pjmedia-videodev/avf_dev.m 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia-videodev/avf_dev.m 2021-02-06 18:46:39.045322874 +0100 @@ -0,0 +1,682 @@ +/* + * Copyright (C) 2014-present AG Projects (http://ag-projects.com) + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +#if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \ + defined(PJMEDIA_VIDEO_DEV_HAS_AVF) && PJMEDIA_VIDEO_DEV_HAS_AVF != 0 + +#include +#include +#include +#include + +#define THIS_FILE "avf_dev.c" +#define DEFAULT_CLOCK_RATE 90000 +#define DEFAULT_WIDTH 640 +#define DEFAULT_HEIGHT 480 +#define DEFAULT_FPS 15 + + +typedef struct avf_fmt_info +{ + pjmedia_format_id pjmedia_format; + unsigned avf_format; +} avf_fmt_info; + +static avf_fmt_info avf_fmts[] = +{ + {PJMEDIA_FORMAT_BGRA, kCVPixelFormatType_32BGRA}, + {PJMEDIA_FORMAT_YUY2, kCVPixelFormatType_422YpCbCr8_yuvs}, + {PJMEDIA_FORMAT_UYVY, kCVPixelFormatType_422YpCbCr8}, +}; + +/* avf device info */ +struct avf_dev_info +{ + pjmedia_vid_dev_info info; + AVCaptureDevice *dev; +}; + +/* avf factory */ +struct avf_factory +{ + pjmedia_vid_dev_factory base; + pj_pool_t *pool; + pj_pool_t *dev_pool; + pj_pool_factory *pf; + + unsigned dev_count; + struct avf_dev_info *dev_info; +}; + +struct avf_stream; /* forward declaration */ +typedef void (*func_ptr)(struct avf_stream *strm); + +@interface AVFDelegate: NSObject +{ +@public + struct avf_stream *stream; +} +@end + + +/* Video stream. */ +struct avf_stream +{ + pjmedia_vid_dev_stream base; /**< Base stream */ + pjmedia_vid_dev_param param; /**< Settings */ + pj_pool_t *pool; /**< Memory pool. */ + + pj_timestamp cap_frame_ts; /**< Captured frame tstamp */ + unsigned cap_ts_inc; /**< Increment */ + + pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */ + void *user_data; /**< Application data. */ + + pjmedia_rect_size size; + + pj_bool_t cap_thread_initialized; + pj_thread_desc cap_thread_desc; + pj_thread_t *cap_thread; + pj_bool_t cap_exited; + + struct avf_factory *af; + pj_status_t status; + pj_bool_t is_running; + + dispatch_queue_t video_ops_queue; + + AVCaptureSession *cap_session; + AVCaptureDeviceInput *dev_input; + AVCaptureVideoDataOutput *video_output; + AVFDelegate *delegate; +}; + + +/* Prototypes */ +static pj_status_t avf_factory_init(pjmedia_vid_dev_factory *f); +static pj_status_t avf_factory_destroy(pjmedia_vid_dev_factory *f); +static pj_status_t avf_factory_refresh(pjmedia_vid_dev_factory *f); +static unsigned avf_factory_get_dev_count(pjmedia_vid_dev_factory *f); +static pj_status_t avf_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info); +static pj_status_t avf_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param); +static pj_status_t avf_factory_create_stream(pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm); + +static pj_status_t avf_stream_get_param(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_param *param); +static pj_status_t avf_stream_get_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + void *value); +static pj_status_t avf_stream_set_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + const void *value); +static pj_status_t avf_stream_start(pjmedia_vid_dev_stream *strm); +static pj_status_t avf_stream_stop(pjmedia_vid_dev_stream *strm); +static pj_status_t avf_stream_destroy(pjmedia_vid_dev_stream *strm); + +/* Operations */ +static pjmedia_vid_dev_factory_op factory_op = +{ + &avf_factory_init, + &avf_factory_destroy, + &avf_factory_get_dev_count, + &avf_factory_get_dev_info, + &avf_factory_default_param, + &avf_factory_create_stream, + &avf_factory_refresh +}; + +static pjmedia_vid_dev_stream_op stream_op = +{ + &avf_stream_get_param, + &avf_stream_get_cap, + &avf_stream_set_cap, + &avf_stream_start, + NULL, + NULL, + &avf_stream_stop, + &avf_stream_destroy +}; + + +/**************************************************************************** + * Factory operations + */ +/* + * Init avf video driver. + */ +pjmedia_vid_dev_factory* pjmedia_avf_factory(pj_pool_factory *pf) +{ + struct avf_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "avf video", 4000, 4000, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct avf_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + + +/* API: init factory */ +static pj_status_t avf_factory_init(pjmedia_vid_dev_factory *f) +{ + return avf_factory_refresh(f); +} + +/* API: destroy factory */ +static pj_status_t avf_factory_destroy(pjmedia_vid_dev_factory *f) +{ + struct avf_factory *af = (struct avf_factory*)f; + pj_pool_t *pool = af->pool; + + if (af->dev_pool) + pj_pool_release(af->dev_pool); + af->pool = NULL; + if (pool) + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +/* API: refresh the list of devices */ +static pj_status_t avf_factory_refresh(pjmedia_vid_dev_factory *f) +{ + struct avf_factory *af = (struct avf_factory*)f; + struct avf_dev_info *di; + unsigned dev_count = 0; + NSAutoreleasePool *apool = [[NSAutoreleasePool alloc]init]; + NSArray *dev_array; + + if (af->dev_pool) { + pj_pool_release(af->dev_pool); + af->dev_pool = NULL; + } + + dev_array = [AVCaptureDevice devices]; + for (AVCaptureDevice *device in dev_array) { + if ([device hasMediaType:AVMediaTypeVideo] && ![device isSuspended]) { + dev_count++; + } + } + + /* Initialize input and output devices here */ + af->dev_count = 0; + af->dev_pool = pj_pool_create(af->pf, "avf video", 500, 500, NULL); + + af->dev_info = (struct avf_dev_info*) pj_pool_calloc(af->dev_pool, dev_count, sizeof(struct avf_dev_info)); + for (AVCaptureDevice *device in dev_array) { + if (![device hasMediaType:AVMediaTypeVideo] || [device isSuspended]) { + continue; + } + + di = &af->dev_info[af->dev_count++]; + pj_bzero(di, sizeof(*di)); + di->dev = device; + pj_ansi_strncpy(di->info.name, [device.localizedName UTF8String], sizeof(di->info.name)); + pj_ansi_strncpy(di->info.driver, "AVF", sizeof(di->info.driver)); + di->info.dir = PJMEDIA_DIR_CAPTURE; + di->info.has_callback = PJ_TRUE; + di->info.fmt_cnt = 0; + di->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT; + + PJ_LOG(4, (THIS_FILE, " dev: %s", di->info.name)); + + for (AVCaptureDeviceFormat* f in [device formats]) { + unsigned i; + CMFormatDescriptionRef desc = [f formatDescription]; + for (i = 0; i < PJ_ARRAY_SIZE(avf_fmts); i++) { + if (CMFormatDescriptionGetMediaSubType(desc) == avf_fmts[i].avf_format) { + char fmt_name[5]; + CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(desc); + if (dim.width < 640) + continue; + pjmedia_fourcc_name(avf_fmts[i].pjmedia_format, fmt_name); + PJ_LOG(4, (THIS_FILE, " detected resolution %dx%d (%s)", dim.width, dim.height, fmt_name)); + pjmedia_format *fmt = &di->info.fmt[di->info.fmt_cnt++]; + pjmedia_format_init_video(fmt, + avf_fmts[i].pjmedia_format, + dim.width, + dim.height, + DEFAULT_FPS, 1); + } + } + } + + if (di->info.fmt_cnt == 0) { + PJ_LOG(4, (THIS_FILE, " there are no compatible formats, using default")); + pjmedia_format *fmt = &di->info.fmt[di->info.fmt_cnt++]; + pjmedia_format_init_video(fmt, + avf_fmts[0].pjmedia_format, + DEFAULT_WIDTH, + DEFAULT_HEIGHT, + DEFAULT_FPS, 1); + } + } + + [apool release]; + + PJ_LOG(4, (THIS_FILE, "avf video has %d devices", af->dev_count)); + + return PJ_SUCCESS; +} + +/* API: get number of devices */ +static unsigned avf_factory_get_dev_count(pjmedia_vid_dev_factory *f) +{ + struct avf_factory *af = (struct avf_factory*)f; + return af->dev_count; +} + +/* API: get device info */ +static pj_status_t avf_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info) +{ + struct avf_factory *af = (struct avf_factory*)f; + PJ_ASSERT_RETURN(index < af->dev_count, PJMEDIA_EVID_INVDEV); + + pj_memcpy(info, &af->dev_info[index].info, sizeof(*info)); + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t avf_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param) +{ + struct avf_factory *af = (struct avf_factory*)f; + struct avf_dev_info *di = &af->dev_info[index]; + + PJ_ASSERT_RETURN(index < af->dev_count, PJMEDIA_EVID_INVDEV); + PJ_UNUSED_ARG(pool); + + pj_bzero(param, sizeof(*param)); + param->dir = PJMEDIA_DIR_CAPTURE; + param->cap_id = index; + param->rend_id = PJMEDIA_VID_INVALID_DEV; + param->flags = PJMEDIA_VID_DEV_CAP_FORMAT; + param->clock_rate = DEFAULT_CLOCK_RATE; + pj_memcpy(¶m->fmt, &di->info.fmt[0], sizeof(param->fmt)); + + return PJ_SUCCESS; +} + +static avf_fmt_info* get_avf_format_info(pjmedia_format_id id) +{ + unsigned i; + + for (i = 0; i < PJ_ARRAY_SIZE(avf_fmts); i++) { + if (avf_fmts[i].pjmedia_format == id) + return &avf_fmts[i]; + } + + return NULL; +} + + +@implementation AVFDelegate +- (void)captureOutput:(AVCaptureOutput *)captureOutput + didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)connection +{ + pjmedia_frame frame = {0}; + CVImageBufferRef img; + CVReturn ret; + OSType type; + size_t width, height; + + /* Register thread if needed */ + if (stream->cap_thread_initialized == 0 || !pj_thread_is_registered()) { + pj_bzero(stream->cap_thread_desc, sizeof(pj_thread_desc)); + pj_thread_register("avf_cap", stream->cap_thread_desc, &stream->cap_thread); + stream->cap_thread_initialized = 1; + } + + if (!sampleBuffer) + return; + + /* Get a CMSampleBuffer's Core Video image buffer for the media data */ + img = CMSampleBufferGetImageBuffer(sampleBuffer); + if (!img) + return; + + /* Check for supported formats */ + type = CVPixelBufferGetPixelFormatType(img); + switch(type) { + case kCVPixelFormatType_32BGRA: + case kCVPixelFormatType_422YpCbCr8_yuvs: + case kCVPixelFormatType_422YpCbCr8: + break; + default: + PJ_LOG(4, (THIS_FILE, "Unsupported image format! %c%c%c%c", type>>24, type>>16, type>>8, type>>0)); + return; + } + + /* Lock the base address of the pixel buffer */ + ret = CVPixelBufferLockBaseAddress(img, kCVPixelBufferLock_ReadOnly); + if (ret != kCVReturnSuccess) + return; + + width = CVPixelBufferGetWidth(img); + height = CVPixelBufferGetHeight(img); + + /* Prepare frame */ + frame.type = PJMEDIA_FRAME_TYPE_VIDEO; + frame.timestamp.u64 = stream->cap_frame_ts.u64; + frame.buf = CVPixelBufferGetBaseAddress(img); + frame.size = CVPixelBufferGetBytesPerRow(img) * height; + + if (stream->size.w != width || stream->size.h != height) { + PJ_LOG(4, (THIS_FILE, "AVF image size changed, before: %dx%d, after: %dx%d", stream->size.w, stream->size.h, width, height)); + } + + if (stream->vid_cb.capture_cb) { + (*stream->vid_cb.capture_cb)(&stream->base, stream->user_data, &frame); + } + + stream->cap_frame_ts.u64 += stream->cap_ts_inc; + + /* Unlock the pixel buffer */ + CVPixelBufferUnlockBaseAddress(img, kCVPixelBufferLock_ReadOnly); +} +@end + + +static void init_avf_stream(struct avf_stream *strm) +{ + const pjmedia_video_format_info *vfi; + pjmedia_video_format_detail *vfd; + avf_fmt_info *fi = get_avf_format_info(strm->param.fmt.id); + NSError *error; + pj_status_t status; + + if (!fi) { + strm->status = PJMEDIA_EVID_BADFORMAT; + return; + } + + strm->cap_session = [[AVCaptureSession alloc] init]; + if (!strm->cap_session) { + strm->status = PJ_ENOMEM; + return; + } + + strm->cap_session.sessionPreset = AVCaptureSessionPresetHigh; + vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE); + pj_assert(vfd); + vfi = pjmedia_get_video_format_info(NULL, strm->param.fmt.id); + pj_assert(vfi); + vfd->size = strm->size; + + PJ_LOG(4, (THIS_FILE, "Opening video device at %dx%d resolution", vfd->size.w, vfd->size.h)); + + /* Add the video device to the session as a device input */ + AVCaptureDevice *videoDevice = strm->af->dev_info[strm->param.cap_id].dev; + strm->dev_input = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error: &error]; + if (!strm->dev_input) { + status = PJMEDIA_EVID_SYSERR; + return; + } + + [strm->cap_session addInput:strm->dev_input]; + + strm->video_output = [[AVCaptureVideoDataOutput alloc] init]; + if (!strm->video_output) { + status = PJMEDIA_EVID_SYSERR; + return; + } + [strm->cap_session addOutput:strm->video_output]; + + /* Configure the video output */ + strm->video_output.alwaysDiscardsLateVideoFrames = YES; + /* The Apple provided documentation says the only supported key is kCVPixelBufferPixelFormatTypeKey, + * but it turns out kCVPixelBufferWidthKey and kCVPixelBufferHeightKey are also required. Thanks + * Chromium, for figuring it out.*/ + strm->video_output.videoSettings = + [NSDictionary dictionaryWithObjectsAndKeys: @(fi->avf_format), + kCVPixelBufferPixelFormatTypeKey, + @(vfd->size.w), + kCVPixelBufferWidthKey, + @(vfd->size.h), + kCVPixelBufferHeightKey, + nil]; + strm->delegate = [[AVFDelegate alloc] init]; + strm->delegate->stream = strm; + dispatch_queue_t queue = dispatch_queue_create("AVFQueue", NULL); + [strm->video_output setSampleBufferDelegate:strm->delegate queue:queue]; + dispatch_release(queue); +} + +static void run_func_on_video_queue(struct avf_stream *strm, func_ptr func) +{ + dispatch_sync(strm->video_ops_queue, ^{ + (*func)(strm); + }); +} + +/* API: create stream */ +static pj_status_t avf_factory_create_stream(pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm) +{ + struct avf_factory *af = (struct avf_factory*)f; + pj_pool_t *pool; + struct avf_stream *strm; + const pjmedia_video_format_info *vfi; + pjmedia_video_format_detail *vfd; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL); + PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO && + param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO && + param->dir == PJMEDIA_DIR_CAPTURE, + PJ_EINVAL); + + vfi = pjmedia_get_video_format_info(NULL, param->fmt.id); + if (!vfi) + return PJMEDIA_EVID_BADFORMAT; + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(af->pf, "avf-dev", 4000, 4000, NULL); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct avf_stream); + pj_memcpy(&strm->param, param, sizeof(*param)); + strm->pool = pool; + pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); + strm->user_data = user_data; + strm->af = af; + + vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE); + pj_memcpy(&strm->size, &vfd->size, sizeof(vfd->size)); + pj_assert(vfd->fps.num); + strm->cap_ts_inc = PJMEDIA_SPF2(strm->param.clock_rate, &vfd->fps, 1); + + /* Create dispatch queue */ + strm->video_ops_queue = dispatch_queue_create("AVF Video Ops", DISPATCH_QUEUE_SERIAL); + + /* Create capture stream here */ + strm->status = PJ_SUCCESS; + run_func_on_video_queue(strm, init_avf_stream); + status = strm->status; + if (status != PJ_SUCCESS) { + dispatch_release(strm->video_ops_queue); + avf_stream_destroy((pjmedia_vid_dev_stream *)strm); + return status; + } + + /* Update param as output */ + param->fmt = strm->param.fmt; + + /* Done */ + strm->base.op = &stream_op; + *p_vid_strm = &strm->base; + + return PJ_SUCCESS; +} + +/* API: Get stream info. */ +static pj_status_t avf_stream_get_param(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_param *pi) +{ + struct avf_stream *strm = (struct avf_stream*)s; + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t avf_stream_get_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + void *pval) +{ + struct avf_stream *strm = (struct avf_stream*)s; + + PJ_UNUSED_ARG(strm); + PJ_UNUSED_ARG(cap); + PJ_UNUSED_ARG(pval); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + return PJMEDIA_EVID_INVCAP; +} + +/* API: set capability */ +static pj_status_t avf_stream_set_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + const void *pval) +{ + struct avf_stream *strm = (struct avf_stream*)s; + + PJ_UNUSED_ARG(strm); + PJ_UNUSED_ARG(cap); + PJ_UNUSED_ARG(pval); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + return PJMEDIA_EVID_INVCAP; +} + +static void start_avf(struct avf_stream *strm) +{ + [strm->cap_session startRunning]; +} + +static void stop_avf(struct avf_stream *strm) +{ + [strm->cap_session stopRunning]; +} + +/* API: Start stream. */ +static pj_status_t avf_stream_start(pjmedia_vid_dev_stream *strm) +{ + struct avf_stream *stream = (struct avf_stream*)strm; + + PJ_LOG(4, (THIS_FILE, "Starting avf video stream")); + + if (stream->cap_session) { + run_func_on_video_queue(stream, start_avf); + if (![stream->cap_session isRunning]) + return PJMEDIA_EVID_NOTREADY; + stream->is_running = PJ_TRUE; + } + + return PJ_SUCCESS; +} + +/* API: Stop stream. */ +static pj_status_t avf_stream_stop(pjmedia_vid_dev_stream *strm) +{ + struct avf_stream *stream = (struct avf_stream*)strm; + + PJ_LOG(4, (THIS_FILE, "Stopping avf video stream")); + + if (stream->cap_session && [stream->cap_session isRunning]) { + int i; + stream->cap_exited = PJ_FALSE; + run_func_on_video_queue(stream, stop_avf); + stream->is_running = PJ_FALSE; + for (i = 50; i >= 0 && !stream->cap_exited; i--) { + pj_thread_sleep(10); + } + } + + return PJ_SUCCESS; +} + +static void destroy_avf(struct avf_stream *strm) +{ + if (strm->cap_session) { + [strm->cap_session removeInput:strm->dev_input]; + [strm->cap_session removeOutput:strm->video_output]; + [strm->cap_session release]; + strm->cap_session = NULL; + } + + if (strm->delegate) { + [strm->delegate release]; + strm->delegate = NULL; + } + + if (strm->dev_input) { + strm->dev_input = NULL; + } + if (strm->video_output) { + strm->video_output = NULL; + } +} + +/* API: Destroy stream. */ +static pj_status_t avf_stream_destroy(pjmedia_vid_dev_stream *strm) +{ + struct avf_stream *stream = (struct avf_stream*)strm; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + avf_stream_stop(strm); + run_func_on_video_queue(stream, destroy_avf); + + dispatch_release(stream->video_ops_queue); + pj_pool_release(stream->pool); + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_VIDEO_DEV_HAS_AVF */ diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/dshow_dev.c pjsip/pjmedia/src/pjmedia-videodev/dshow_dev.c --- pjproject-2.10/pjmedia/src/pjmedia-videodev/dshow_dev.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia-videodev/dshow_dev.c 2021-02-06 22:51:16.641714862 +0100 @@ -20,30 +20,24 @@ #include #include #include -#include #if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \ defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0 - -#ifdef _MSC_VER -# pragma warning(push, 3) -#endif - #include #define COBJMACROS #include #include +#include -#ifdef _MSC_VER -# pragma warning(pop) +#ifndef DIBSIZE +# define WIDTHBYTES(BTIS) ((DWORD)(((BTIS)+31) & (~31)) / 8) +# define DIBWIDTHBYTES(BI) (DWORD)(BI).biBitCount) * (DWORD)WIDTHBYTES((DWORD)(BI).biWidth +# define _DIBSIZE(BI) (DIBWIDTHBYTES(BI) * (DWORD)(BI).biHeight) +# define DIBSIZE(BI) ((BI).biHeight < 0 ? (-1)*(_DIBSIZE(BI)) : _DIBSIZE(BI)) #endif -#pragma comment(lib, "Strmiids.lib") -#pragma comment(lib, "Rpcrt4.lib") -#pragma comment(lib, "Quartz.lib") - #define THIS_FILE "dshow_dev.c" #define DEFAULT_CLOCK_RATE 90000 #define DEFAULT_WIDTH 640 @@ -57,10 +51,6 @@ typedef struct NullRenderer NullRenderer; IBaseFilter* NullRenderer_Create(input_callback input_cb, void *user_data); -typedef struct SourceFilter SourceFilter; -IBaseFilter* SourceFilter_Create(SourceFilter **pSrc); -HRESULT SourceFilter_Deliver(SourceFilter *src, void *buf, long size); -void SourceFilter_SetMediaType(SourceFilter *src, AM_MEDIA_TYPE *pmt); typedef struct dshow_fmt_info { @@ -121,7 +111,6 @@ { IFilterGraph *filter_graph; IMediaFilter *media_filter; - SourceFilter *csource_filter; IBaseFilter *source_filter; IBaseFilter *rend_filter; AM_MEDIA_TYPE *mediatype; @@ -160,8 +149,6 @@ pjmedia_vid_dev_cap cap, const void *value); static pj_status_t dshow_stream_start(pjmedia_vid_dev_stream *strm); -static pj_status_t dshow_stream_put_frame(pjmedia_vid_dev_stream *strm, - const pjmedia_frame *frame); static pj_status_t dshow_stream_stop(pjmedia_vid_dev_stream *strm); static pj_status_t dshow_stream_destroy(pjmedia_vid_dev_stream *strm); @@ -184,7 +171,7 @@ &dshow_stream_set_cap, &dshow_stream_start, NULL, - &dshow_stream_put_frame, + NULL, &dshow_stream_stop, &dshow_stream_destroy }; @@ -213,19 +200,12 @@ /* API: init factory */ static pj_status_t dshow_factory_init(pjmedia_vid_dev_factory *f) { - HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); - if (hr == RPC_E_CHANGED_MODE) { - /* When using apartment mode, Dshow object would not be accessible from - * other thread. Take this into consideration when implementing native - * renderer using Dshow. - */ - hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); - if (FAILED(hr)) { - PJ_LOG(4,(THIS_FILE, "Failed initializing DShow: " - "COM library already initialized with " - "incompatible concurrency model")); - return PJMEDIA_EVID_INIT; - } + HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + if (hr == RPC_E_CHANGED_MODE) { + PJ_LOG(4,(THIS_FILE, "Failed initializing DShow: " + "COM library already initialized with " + "incompatible concurrency model")); + return PJMEDIA_EVID_INIT; } return dshow_factory_refresh(f); @@ -459,13 +439,21 @@ if (SUCCEEDED(hr) && var_name.bstrVal) { WCHAR *wszDisplayName = NULL; IBaseFilter *filter; + pj_ssize_t len; ddi = &df->dev_info[df->dev_count++]; pj_bzero(ddi, sizeof(*ddi)); - pj_unicode_to_ansi(var_name.bstrVal, - wcslen(var_name.bstrVal), - ddi->info.name, - sizeof(ddi->info.name)); + + len = wcslen(var_name.bstrVal), + len = WideCharToMultiByte(CP_ACP, + 0, + var_name.bstrVal, + (int)len, + ddi->info.name, + sizeof(ddi->info.name), + NULL, + NULL); + ddi->info.name[len] = '\0'; hr = IMoniker_GetDisplayName(moniker, NULL, NULL, &wszDisplayName); @@ -502,26 +490,6 @@ ICreateDevEnum_Release(dev_enum); } -#if HAS_VMR - ddi = &df->dev_info[df->dev_count++]; - pj_bzero(ddi, sizeof(*ddi)); - pj_ansi_strncpy(ddi->info.name, "Video Mixing Renderer", - sizeof(ddi->info.name)); - ddi->info.name[sizeof(ddi->info.name)-1] = '\0'; - pj_ansi_strncpy(ddi->info.driver, "dshow", sizeof(ddi->info.driver)); - ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0'; - ddi->info.dir = PJMEDIA_DIR_RENDER; - ddi->info.has_callback = PJ_FALSE; - ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT; -// TODO: -// ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW; - - ddi->info.fmt_cnt = 1; - pjmedia_format_init_video(&ddi->info.fmt[0], dshow_fmts[0].pjmedia_format, - DEFAULT_WIDTH, DEFAULT_HEIGHT, - DEFAULT_FPS, 1); -#endif - PJ_LOG(4, (THIS_FILE, "DShow has %d devices:", df->dev_count)); for (c = 0; c < df->dev_count; ++c) { @@ -574,10 +542,6 @@ param->dir = PJMEDIA_DIR_CAPTURE; param->cap_id = index; param->rend_id = PJMEDIA_VID_INVALID_DEV; - } else if (di->info.dir & PJMEDIA_DIR_RENDER) { - param->dir = PJMEDIA_DIR_RENDER; - param->rend_id = index; - param->cap_id = PJMEDIA_VID_INVALID_DEV; } else { return PJMEDIA_EVID_INVDEV; } @@ -645,26 +609,6 @@ (*strm->vid_cb.capture_cb)(&strm->base, strm->user_data, &frame); } -/* API: Put frame from stream */ -static pj_status_t dshow_stream_put_frame(pjmedia_vid_dev_stream *strm, - const pjmedia_frame *frame) -{ - struct dshow_stream *stream = (struct dshow_stream*)strm; - HRESULT hr; - - if (stream->quit_flag) { - stream->rend_thread_exited = PJ_TRUE; - return PJ_SUCCESS; - } - - hr = SourceFilter_Deliver(stream->dgraph.csource_filter, - frame->buf, (long)frame->size); - if (FAILED(hr)) - return hr; - - return PJ_SUCCESS; -} - static dshow_fmt_info* get_dshow_format_info(pjmedia_format_id id) { unsigned i; @@ -689,16 +633,24 @@ IEnumPins *pEnum; IPin *srcpin = NULL; IPin *sinkpin = NULL; - AM_MEDIA_TYPE *mediatype= NULL, mtype; + AM_MEDIA_TYPE *mediatype = NULL; VIDEOINFOHEADER *video_info, *vi = NULL; pjmedia_video_format_detail *vfd; const pjmedia_video_format_info *vfi; + PJ_ASSERT_RETURN(dir == PJMEDIA_DIR_CAPTURE, PJ_EINVAL); + vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(), strm->param.fmt.id); if (!vfi) return PJMEDIA_EVID_BADFORMAT; + hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + if (FAILED(hr)) { + PJ_LOG(4,(THIS_FILE, "Error: CoInitializeEx")); + goto on_error; + } + hr = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC, &IID_IFilterGraph, (LPVOID *)&graph->filter_graph); if (FAILED(hr)) { @@ -711,14 +663,10 @@ goto on_error; } - if (dir == PJMEDIA_DIR_CAPTURE) { hr = get_cap_device(df, id, &graph->source_filter); if (FAILED(hr)) { goto on_error; } - } else { - graph->source_filter = SourceFilter_Create(&graph->csource_filter); - } hr = IFilterGraph_AddFilter(graph->filter_graph, graph->source_filter, L"capture"); @@ -726,16 +674,7 @@ goto on_error; } - if (dir == PJMEDIA_DIR_CAPTURE) { - graph->rend_filter = NullRenderer_Create(input_cb, strm); - } else { - hr = CoCreateInstance(&CLSID_VideoMixingRenderer, NULL, - CLSCTX_INPROC, &IID_IBaseFilter, - (LPVOID *)&graph->rend_filter); - if (FAILED (hr)) { - goto on_error; - } - } + graph->rend_filter = NullRenderer_Create(input_cb, strm); IBaseFilter_EnumPins(graph->rend_filter, &pEnum); if (SUCCEEDED(hr)) { @@ -763,34 +702,6 @@ (use_def_size? 0: vfd->size.h), &srcpin, NULL); graph->mediatype = mediatype; - if (srcpin && dir == PJMEDIA_DIR_RENDER) { - mediatype = graph->mediatype = &mtype; - - memset (mediatype, 0, sizeof(AM_MEDIA_TYPE)); - mediatype->majortype = MEDIATYPE_Video; - mediatype->subtype = *(get_dshow_format_info(strm->param.fmt.id)-> - dshow_format); - mediatype->bFixedSizeSamples = TRUE; - mediatype->bTemporalCompression = FALSE; - - vi = (VIDEOINFOHEADER *) - CoTaskMemAlloc(sizeof(VIDEOINFOHEADER)); - memset (vi, 0, sizeof(VIDEOINFOHEADER)); - mediatype->formattype = FORMAT_VideoInfo; - mediatype->cbFormat = sizeof(VIDEOINFOHEADER); - mediatype->pbFormat = (BYTE *)vi; - - vi->rcSource.bottom = vfd->size.h; - vi->rcSource.right = vfd->size.w; - vi->rcTarget.bottom = vfd->size.h; - vi->rcTarget.right = vfd->size.w; - - vi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - vi->bmiHeader.biPlanes = 1; - vi->bmiHeader.biBitCount = vfi->bpp; - vi->bmiHeader.biCompression = strm->param.fmt.id; - } - if (!srcpin || !sinkpin || !mediatype) { hr = VFW_E_TYPE_NOT_ACCEPTED; goto on_error; @@ -809,9 +720,6 @@ } video_info->bmiHeader.biSizeImage = DIBSIZE(video_info->bmiHeader); mediatype->lSampleSize = DIBSIZE(video_info->bmiHeader); - if (graph->csource_filter) - SourceFilter_SetMediaType(graph->csource_filter, - mediatype); hr = IFilterGraph_AddFilter(graph->filter_graph, (IBaseFilter *)graph->rend_filter, @@ -893,9 +801,9 @@ pj_pool_t *pool; struct dshow_stream *strm; pj_status_t status; + const pjmedia_video_format_detail *vfd; - PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_CAPTURE || - param->dir == PJMEDIA_DIR_RENDER, PJ_EINVAL); + PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_CAPTURE, PJ_EINVAL); if (!get_dshow_format_info(param->fmt.id)) return PJMEDIA_EVID_BADFORMAT; @@ -910,9 +818,6 @@ pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); strm->user_data = user_data; - if (param->dir & PJMEDIA_DIR_CAPTURE) { - const pjmedia_video_format_detail *vfd; - /* Create capture stream here */ status = create_filter_graph(PJMEDIA_DIR_CAPTURE, param->cap_id, PJ_FALSE, PJ_FALSE, df, strm, @@ -944,21 +849,6 @@ vfd = pjmedia_format_get_video_format_detail(¶m->fmt, PJ_TRUE); strm->cap_ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1); - } else if (param->dir & PJMEDIA_DIR_RENDER) { - /* Create render stream here */ - status = create_filter_graph(PJMEDIA_DIR_RENDER, param->rend_id, - PJ_FALSE, PJ_FALSE, df, strm, - &strm->dgraph); - if (status != PJ_SUCCESS) - goto on_error; - } - - /* Apply the remaining settings */ - if (param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) { - dshow_stream_set_cap(&strm->base, - PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW, - ¶m->window); - } /* Done */ strm->base.op = &stream_op; diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/dshow_filter.cpp pjsip/pjmedia/src/pjmedia-videodev/dshow_filter.cpp --- pjproject-2.10/pjmedia/src/pjmedia-videodev/dshow_filter.cpp 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia-videodev/dshow_filter.cpp 2021-02-06 18:47:09.030177557 +0100 @@ -0,0 +1,83 @@ +/* $Id: dshowclasses.cpp 4062 2012-04-19 06:36:57Z ming $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + + +#if defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0 + +#include +#include +#include + +typedef void (*input_callback)(void *user_data, IMediaSample *pMediaSample); + +const GUID CLSID_NullRenderer = {0xF9168C5E, 0xCEB2, 0x4FAA, {0xB6, 0xBF, + 0x32, 0x9B, 0xF3, 0x9F, 0xA1, 0xE4}}; + +class NullRenderer: public CBaseRenderer +{ +public: + NullRenderer(HRESULT *pHr); + virtual ~NullRenderer(); + + virtual HRESULT CheckMediaType(const CMediaType *pmt); + virtual HRESULT DoRenderSample(IMediaSample *pMediaSample); + + input_callback input_cb; + void *user_data; +}; + +NullRenderer::NullRenderer(HRESULT *pHr): CBaseRenderer(CLSID_NullRenderer, + "NullRenderer", + NULL, pHr) +{ + input_cb = NULL; +} + +NullRenderer::~NullRenderer() +{ +} + +HRESULT NullRenderer::CheckMediaType(const CMediaType *pmt) +{ + return S_OK; +} + +HRESULT NullRenderer::DoRenderSample(IMediaSample *pMediaSample) +{ + if (input_cb) + input_cb(user_data, pMediaSample); + + return S_OK; +} + +extern "C" IBaseFilter* NullRenderer_Create(input_callback input_cb, + void *user_data) +{ + HRESULT hr; + NullRenderer *renderer = new NullRenderer(&hr); + renderer->AddRef(); + renderer->input_cb = input_cb; + renderer->user_data = user_data; + + return (CBaseFilter *)renderer; +} + +#endif /* PJMEDIA_VIDEO_DEV_HAS_DSHOW */ diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/fb_dev.c pjsip/pjmedia/src/pjmedia-videodev/fb_dev.c --- pjproject-2.10/pjmedia/src/pjmedia-videodev/fb_dev.c 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia-videodev/fb_dev.c 2021-02-06 18:47:25.010632996 +0100 @@ -0,0 +1,459 @@ +/* + * Copyright (C) 2014-present AG Projects + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +#if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \ + defined(PJMEDIA_VIDEO_DEV_HAS_FB) && PJMEDIA_VIDEO_DEV_HAS_FB != 0 + +#include + +#define THIS_FILE "fb_dev.c" +#define DEFAULT_CLOCK_RATE 90000 +#define DEFAULT_WIDTH 640 +#define DEFAULT_HEIGHT 480 +#define DEFAULT_FPS 25 + + +/* Supported formats */ +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 +static pjmedia_format_id fb_fmts[] = {PJMEDIA_FORMAT_ARGB}; +#else +static pjmedia_format_id fb_fmts[] = {PJMEDIA_FORMAT_BGRA}; +#endif + + +/* fb device info */ +struct fb_dev_info +{ + pjmedia_vid_dev_info info; +}; + + +/* factory */ +struct fb_factory +{ + pjmedia_vid_dev_factory base; + pj_pool_t *pool; + pj_pool_factory *pf; + + unsigned dev_count; + struct fb_dev_info *dev_info; +}; + + +/* Video stream. */ +struct fb_stream +{ + pjmedia_vid_dev_stream base; /**< Base stream */ + pjmedia_vid_dev_param param; /**< Settings */ + pj_pool_t *pool; /**< Memory pool. */ + + pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */ + void *user_data; /**< Application data. */ + + struct fb_factory *ff; + pj_bool_t is_running; + pjmedia_rect_size vid_size; + + struct { + pjmedia_vid_dev_fb_frame_cb cb; + void *user_data; + } frame_handler; +}; + + +/* Prototypes */ +static pj_status_t fb_factory_init(pjmedia_vid_dev_factory *f); +static pj_status_t fb_factory_destroy(pjmedia_vid_dev_factory *f); +static pj_status_t fb_factory_refresh(pjmedia_vid_dev_factory *f); +static unsigned fb_factory_get_dev_count(pjmedia_vid_dev_factory *f); +static pj_status_t fb_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info); +static pj_status_t fb_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param); +static pj_status_t fb_factory_create_stream(pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm); + +static pj_status_t fb_stream_get_param(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_param *param); +static pj_status_t fb_stream_get_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + void *value); +static pj_status_t fb_stream_set_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + const void *value); +static pj_status_t fb_stream_put_frame(pjmedia_vid_dev_stream *strm, + const pjmedia_frame *frame); +static pj_status_t fb_stream_start(pjmedia_vid_dev_stream *strm); +static pj_status_t fb_stream_stop(pjmedia_vid_dev_stream *strm); +static pj_status_t fb_stream_destroy(pjmedia_vid_dev_stream *strm); + + +/* Operations */ +static pjmedia_vid_dev_factory_op factory_op = +{ + &fb_factory_init, + &fb_factory_destroy, + &fb_factory_get_dev_count, + &fb_factory_get_dev_info, + &fb_factory_default_param, + &fb_factory_create_stream, + &fb_factory_refresh +}; + +static pjmedia_vid_dev_stream_op stream_op = +{ + &fb_stream_get_param, + &fb_stream_get_cap, + &fb_stream_set_cap, + &fb_stream_start, + NULL, + &fb_stream_put_frame, + &fb_stream_stop, + &fb_stream_destroy +}; + + +/**************************************************************************** + * Factory operations + */ +/* + * Init FB video driver. + */ +pjmedia_vid_dev_factory* pjmedia_fb_factory(pj_pool_factory *pf) +{ + struct fb_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "fb video", 1000, 1000, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct fb_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + + +/* API: init factory */ +static pj_status_t fb_factory_init(pjmedia_vid_dev_factory *f) +{ + struct fb_factory *ff = (struct fb_factory*)f; + struct fb_dev_info *di; + unsigned i, l; + + /* Initialize input and output devices here */ + ff->dev_info = (struct fb_dev_info*) + pj_pool_calloc(ff->pool, 1, sizeof(struct fb_dev_info)); + + ff->dev_count = 0; + di = &ff->dev_info[ff->dev_count++]; + pj_bzero(di, sizeof(*di)); + strcpy(di->info.name, "FrameBuffer renderer"); + strcpy(di->info.driver, "FrameBuffer"); + di->info.dir = PJMEDIA_DIR_RENDER; + di->info.has_callback = PJ_FALSE; + di->info.caps = 0; + + for (i = 0; i < ff->dev_count; i++) { + di = &ff->dev_info[i]; + di->info.fmt_cnt = PJ_ARRAY_SIZE(fb_fmts); + di->info.caps |= PJMEDIA_VID_DEV_CAP_FORMAT; + + for (l = 0; l < PJ_ARRAY_SIZE(fb_fmts); l++) { + pjmedia_format *fmt = &di->info.fmt[l]; + pjmedia_format_init_video(fmt, + fb_fmts[l], + DEFAULT_WIDTH, + DEFAULT_HEIGHT, + DEFAULT_FPS, 1); + } + } + + PJ_LOG(4, (THIS_FILE, "FrameBuffer initialized")); + + return PJ_SUCCESS; +} + + +/* API: destroy factory */ +static pj_status_t fb_factory_destroy(pjmedia_vid_dev_factory *f) +{ + struct fb_factory *ff = (struct fb_factory*)f; + pj_pool_t *pool = ff->pool; + + ff->pool = NULL; + pj_pool_release(pool); + + return PJ_SUCCESS; +} + + +/* API: refresh the list of devices */ +static pj_status_t fb_factory_refresh(pjmedia_vid_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return PJ_SUCCESS; +} + + +/* API: get number of devices */ +static unsigned fb_factory_get_dev_count(pjmedia_vid_dev_factory *f) +{ + struct fb_factory *ff = (struct fb_factory*)f; + return ff->dev_count; +} + + +/* API: get device info */ +static pj_status_t fb_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info) +{ + struct fb_factory *ff = (struct fb_factory*)f; + + PJ_ASSERT_RETURN(index < ff->dev_count, PJMEDIA_EVID_INVDEV); + pj_memcpy(info, &ff->dev_info[index].info, sizeof(*info)); + + return PJ_SUCCESS; +} + + +/* API: create default device parameter */ +static pj_status_t fb_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param) +{ + struct fb_factory *ff = (struct fb_factory*)f; + struct fb_dev_info *di = &ff->dev_info[index]; + + PJ_ASSERT_RETURN(index < ff->dev_count, PJMEDIA_EVID_INVDEV); + PJ_UNUSED_ARG(pool); + + pj_bzero(param, sizeof(*param)); + param->dir = PJMEDIA_DIR_RENDER; + param->rend_id = index; + param->cap_id = PJMEDIA_VID_INVALID_DEV; + + /* Set the device capabilities here */ + param->flags = PJMEDIA_VID_DEV_CAP_FORMAT; + param->clock_rate = DEFAULT_CLOCK_RATE; + pj_memcpy(¶m->fmt, &di->info.fmt[0], sizeof(param->fmt)); + + return PJ_SUCCESS; +} + + +/* API: Put frame from stream */ +static pj_status_t fb_stream_put_frame(pjmedia_vid_dev_stream *strm, + const pjmedia_frame *frame) +{ + struct fb_stream *stream = (struct fb_stream*)strm; + + if (!stream->is_running) + return PJ_EINVALIDOP; + + if (frame->size==0 || frame->buf==NULL) + return PJ_SUCCESS; + + if (stream->frame_handler.cb) + stream->frame_handler.cb(frame, stream->vid_size, stream->frame_handler.user_data); + + return PJ_SUCCESS; +} + +/* API: create stream */ +static pj_status_t fb_factory_create_stream(pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm) +{ + struct fb_factory *ff = (struct fb_factory*)f; + pj_pool_t *pool; + pj_status_t status; + struct fb_stream *strm; + const pjmedia_video_format_info *vfi; + + PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL); + PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_RENDER, PJ_EINVAL); + PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO && + param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO && + param->dir == PJMEDIA_DIR_RENDER, + PJ_EINVAL); + + vfi = pjmedia_get_video_format_info(NULL, param->fmt.id); + if (!vfi) + return PJMEDIA_EVID_BADFORMAT; + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(ff->pf, "fb-dev", 1000, 1000, NULL); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct fb_stream); + pj_memcpy(&strm->param, param, sizeof(*param)); + strm->pool = pool; + strm->ff = ff; + pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); + strm->user_data = user_data; + + status = fb_stream_set_cap(&strm->base, PJMEDIA_VID_DEV_CAP_FORMAT, ¶m->fmt); + if (status != PJ_SUCCESS) { + fb_stream_destroy((pjmedia_vid_dev_stream *)strm); + return status; + } + + /* Done */ + strm->base.op = &stream_op; + *p_vid_strm = &strm->base; + + return PJ_SUCCESS; +} + + +/* API: Get stream info. */ +static pj_status_t fb_stream_get_param(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_param *pi) +{ + struct fb_stream *strm = (struct fb_stream*)s; + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + + return PJ_SUCCESS; +} + + +/* API: get capability */ +static pj_status_t fb_stream_get_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + void *pval) +{ + struct fb_stream *strm = (struct fb_stream*)s; + + PJ_UNUSED_ARG(strm); + PJ_UNUSED_ARG(cap); + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + return PJMEDIA_EVID_INVCAP; +} + + +/* API: set capability */ +static pj_status_t fb_stream_set_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + const void *pval) +{ + struct fb_stream *strm = (struct fb_stream*)s; + + PJ_UNUSED_ARG(strm); + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap == PJMEDIA_VID_DEV_CAP_FORMAT) { + const pjmedia_video_format_info *vfi; + pjmedia_video_format_detail *vfd; + pjmedia_format *fmt = (pjmedia_format *)pval; + + vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(), fmt->id); + if (!vfi) + return PJMEDIA_EVID_BADFORMAT; + + pjmedia_format_copy(&strm->param.fmt, fmt); + + vfd = pjmedia_format_get_video_format_detail(fmt, PJ_TRUE); + pj_memcpy(&strm->vid_size, &vfd->size, sizeof(vfd->size)); + if (strm->param.disp_size.w == 0 || strm->param.disp_size.h == 0) + pj_memcpy(&strm->param.disp_size, &vfd->size, sizeof(vfd->size)); + + return PJ_SUCCESS; + } + + return PJMEDIA_EVID_INVCAP; +} + + +/* API: Start stream. */ +static pj_status_t fb_stream_start(pjmedia_vid_dev_stream *strm) +{ + struct fb_stream *stream = (struct fb_stream*)strm; + PJ_UNUSED_ARG(strm); + + PJ_LOG(4, (THIS_FILE, "Starting FB video stream")); + stream->is_running = PJ_TRUE; + + return PJ_SUCCESS; +} + + +/* API: Stop stream. */ +static pj_status_t fb_stream_stop(pjmedia_vid_dev_stream *strm) +{ + struct fb_stream *stream = (struct fb_stream*)strm; + PJ_UNUSED_ARG(strm); + + PJ_LOG(4, (THIS_FILE, "Stopping FB video stream")); + stream->is_running = PJ_FALSE; + + return PJ_SUCCESS; +} + + +/* API: Destroy stream. */ +static pj_status_t fb_stream_destroy(pjmedia_vid_dev_stream *strm) +{ + struct fb_stream *stream = (struct fb_stream*)strm; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + fb_stream_stop(strm); + pj_pool_release(stream->pool); + + return PJ_SUCCESS; +} + + +/* API: set callback for handling frames */ +pj_status_t +pjmedia_vid_dev_fb_set_callback(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_fb_frame_cb cb, + void *user_data) +{ + struct fb_stream *stream = (struct fb_stream*)strm; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + if (stream->is_running) + return PJ_EBUSY; + + stream->frame_handler.cb = cb; + stream->frame_handler.user_data = user_data; + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_VIDEO_DEV_HAS_FB */ diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/null_dev.c pjsip/pjmedia/src/pjmedia-videodev/null_dev.c --- pjproject-2.10/pjmedia/src/pjmedia-videodev/null_dev.c 1970-01-01 01:00:00.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia-videodev/null_dev.c 2021-02-06 18:47:47.343269399 +0100 @@ -0,0 +1,440 @@ +/* $Id: colorbar_dev.c 4158 2012-06-06 09:56:14Z nanang $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + + +#if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \ + defined(PJMEDIA_VIDEO_DEV_HAS_NULL) && \ + PJMEDIA_VIDEO_DEV_HAS_NULL != 0 + + +#define THIS_FILE "null_dev.c" +#define DEFAULT_CLOCK_RATE 90000 +#define DEFAULT_WIDTH 640 +#define DEFAULT_HEIGHT 480 +#define DEFAULT_FPS 5 + +/* null_ device info */ +struct null_dev_info +{ + pjmedia_vid_dev_info info; +}; + +/* null_ factory */ +struct null_factory +{ + pjmedia_vid_dev_factory base; + pj_pool_t *pool; + pj_pool_factory *pf; + + unsigned dev_count; + struct null_dev_info *dev_info; +}; + +struct null_fmt_info { + pjmedia_format_id fmt_id; /* Format ID */ +}; + +/* Null video source supports */ +static struct null_fmt_info null_fmts[] = +{ + { PJMEDIA_FORMAT_BGRA }, +}; + +/* Video stream. */ +struct null_stream +{ + pjmedia_vid_dev_stream base; /**< Base stream */ + pjmedia_vid_dev_param param; /**< Settings */ + pj_pool_t *pool; /**< Memory pool. */ + + pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */ + void *user_data; /**< Application data. */ + + const struct null_fmt_info *cbfi; + const pjmedia_video_format_info *vfi; + pjmedia_video_apply_fmt_param vafp; + pj_uint8_t *first_line[PJMEDIA_MAX_VIDEO_PLANES]; + pj_timestamp ts; + unsigned ts_inc; +}; + + +/* Prototypes */ +static pj_status_t null_factory_init(pjmedia_vid_dev_factory *f); +static pj_status_t null_factory_destroy(pjmedia_vid_dev_factory *f); +static pj_status_t null_factory_refresh(pjmedia_vid_dev_factory *f); +static unsigned null_factory_get_dev_count(pjmedia_vid_dev_factory *f); +static pj_status_t null_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info); +static pj_status_t null_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param); +static pj_status_t null_factory_create_stream( + pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm); + +static pj_status_t null_stream_get_param(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_param *param); +static pj_status_t null_stream_get_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + void *value); +static pj_status_t null_stream_set_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + const void *value); +static pj_status_t null_stream_get_frame(pjmedia_vid_dev_stream *strm, + pjmedia_frame *frame); +static pj_status_t null_stream_start(pjmedia_vid_dev_stream *strm); +static pj_status_t null_stream_stop(pjmedia_vid_dev_stream *strm); +static pj_status_t null_stream_destroy(pjmedia_vid_dev_stream *strm); + +/* Operations */ +static pjmedia_vid_dev_factory_op factory_op = +{ + &null_factory_init, + &null_factory_destroy, + &null_factory_get_dev_count, + &null_factory_get_dev_info, + &null_factory_default_param, + &null_factory_create_stream, + &null_factory_refresh +}; + +static pjmedia_vid_dev_stream_op stream_op = +{ + &null_stream_get_param, + &null_stream_get_cap, + &null_stream_set_cap, + &null_stream_start, + &null_stream_get_frame, + NULL, + &null_stream_stop, + &null_stream_destroy +}; + + +/**************************************************************************** + * Factory operations + */ +/* + * Init null_ video driver. + */ +pjmedia_vid_dev_factory* pjmedia_null_factory(pj_pool_factory *pf) +{ + struct null_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "null video", 512, 512, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct null_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + + +/* API: init factory */ +static pj_status_t null_factory_init(pjmedia_vid_dev_factory *f) +{ + struct null_factory *cf = (struct null_factory*)f; + struct null_dev_info *ddi; + unsigned i; + + cf->dev_count = 1; + cf->dev_info = (struct null_dev_info*) + pj_pool_calloc(cf->pool, cf->dev_count, + sizeof(struct null_dev_info)); + + ddi = &cf->dev_info[0]; + pj_bzero(ddi, sizeof(*ddi)); + pj_ansi_strncpy(ddi->info.name, "Null video device", + sizeof(ddi->info.name)); + ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0'; + pj_ansi_strncpy(ddi->info.driver, "Null", sizeof(ddi->info.driver)); + ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0'; + ddi->info.dir = PJMEDIA_DIR_CAPTURE; + ddi->info.has_callback = PJ_FALSE; + + ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT; + ddi->info.fmt_cnt = sizeof(null_fmts)/sizeof(null_fmts[0]); + for (i = 0; i < ddi->info.fmt_cnt; i++) { + pjmedia_format *fmt = &ddi->info.fmt[i]; + pjmedia_format_init_video(fmt, null_fmts[i].fmt_id, + DEFAULT_WIDTH, DEFAULT_HEIGHT, + DEFAULT_FPS, 1); + } + + PJ_LOG(4, (THIS_FILE, "Null video src initialized with %d device(s):", cf->dev_count)); + for (i = 0; i < cf->dev_count; i++) { + PJ_LOG(4, (THIS_FILE, "%2d: %s", i, cf->dev_info[i].info.name)); + } + + return PJ_SUCCESS; +} + +/* API: destroy factory */ +static pj_status_t null_factory_destroy(pjmedia_vid_dev_factory *f) +{ + struct null_factory *cf = (struct null_factory*)f; + pj_pool_t *pool = cf->pool; + + cf->pool = NULL; + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +/* API: refresh the list of devices */ +static pj_status_t null_factory_refresh(pjmedia_vid_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return PJ_SUCCESS; +} + +/* API: get number of devices */ +static unsigned null_factory_get_dev_count(pjmedia_vid_dev_factory *f) +{ + struct null_factory *cf = (struct null_factory*)f; + return cf->dev_count; +} + +/* API: get device info */ +static pj_status_t null_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info) +{ + struct null_factory *cf = (struct null_factory*)f; + + PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV); + + pj_memcpy(info, &cf->dev_info[index].info, sizeof(*info)); + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t null_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param) +{ + struct null_factory *cf = (struct null_factory*)f; + struct null_dev_info *di = &cf->dev_info[index]; + + PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV); + + PJ_UNUSED_ARG(pool); + + pj_bzero(param, sizeof(*param)); + param->dir = PJMEDIA_DIR_CAPTURE; + param->cap_id = index; + param->rend_id = PJMEDIA_VID_INVALID_DEV; + param->flags = PJMEDIA_VID_DEV_CAP_FORMAT; + param->clock_rate = DEFAULT_CLOCK_RATE; + pj_memcpy(¶m->fmt, &di->info.fmt[0], sizeof(param->fmt)); + + return PJ_SUCCESS; +} + +static const struct null_fmt_info* get_null_fmt_info(pjmedia_format_id id) +{ + unsigned i; + + for (i = 0; i < sizeof(null_fmts)/sizeof(null_fmts[0]); i++) { + if (null_fmts[i].fmt_id == id) + return &null_fmts[i]; + } + + return NULL; +} + + +/* API: create stream */ +static pj_status_t null_factory_create_stream( + pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm) +{ + struct null_factory *cf = (struct null_factory*)f; + pj_pool_t *pool; + struct null_stream *strm; + const pjmedia_video_format_detail *vfd; + const pjmedia_video_format_info *vfi; + pjmedia_video_apply_fmt_param vafp; + const struct null_fmt_info *cbfi; + unsigned i; + + PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL); + PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO && + param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO && + param->dir == PJMEDIA_DIR_CAPTURE, + PJ_EINVAL); + + pj_bzero(&vafp, sizeof(vafp)); + + vfd = pjmedia_format_get_video_format_detail(¶m->fmt, PJ_TRUE); + vfi = pjmedia_get_video_format_info(NULL, param->fmt.id); + cbfi = get_null_fmt_info(param->fmt.id); + if (!vfi || !cbfi) + return PJMEDIA_EVID_BADFORMAT; + + vafp.size = param->fmt.det.vid.size; + if (vfi->apply_fmt(vfi, &vafp) != PJ_SUCCESS) + return PJMEDIA_EVID_BADFORMAT; + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(cf->pf, "null-dev", 512, 512, NULL); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct null_stream); + pj_memcpy(&strm->param, param, sizeof(*param)); + strm->pool = pool; + pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); + strm->user_data = user_data; + strm->vfi = vfi; + strm->cbfi = cbfi; + pj_memcpy(&strm->vafp, &vafp, sizeof(vafp)); + strm->ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1); + + for (i = 0; i < vfi->plane_cnt; ++i) { + strm->first_line[i] = pj_pool_alloc(pool, vafp.strides[i]); + pj_memset(strm->first_line[i], 0, vafp.strides[i]); + } + + /* Done */ + strm->base.op = &stream_op; + *p_vid_strm = &strm->base; + + return PJ_SUCCESS; +} + +/* API: Get stream info. */ +static pj_status_t null_stream_get_param(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_param *pi) +{ + struct null_stream *strm = (struct null_stream*)s; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t null_stream_get_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + void *pval) +{ + struct null_stream *strm = (struct null_stream*)s; + + PJ_UNUSED_ARG(strm); + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + return PJMEDIA_EVID_INVCAP; +} + +/* API: set capability */ +static pj_status_t null_stream_set_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + const void *pval) +{ + struct null_stream *strm = (struct null_stream*)s; + + PJ_UNUSED_ARG(strm); + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + return PJMEDIA_EVID_INVCAP; +} + + +/* API: Get frame from stream */ +static pj_status_t null_stream_get_frame(pjmedia_vid_dev_stream *strm, + pjmedia_frame *frame) +{ + struct null_stream *stream = (struct null_stream*)strm; + unsigned i; + pj_uint8_t *ptr = frame->buf; + + frame->type = PJMEDIA_FRAME_TYPE_VIDEO; + frame->bit_info = 0; + frame->timestamp = stream->ts; + stream->ts.u64 += stream->ts_inc; + + /* paint subsequent lines */ + for (i=0; ivfi->plane_cnt; ++i) { + pj_uint8_t *plane_end; + plane_end = ptr + stream->vafp.plane_bytes[i]; + while (ptr < plane_end) { + pj_memcpy(ptr, stream->first_line[i], stream->vafp.strides[i]); + ptr += stream->vafp.strides[i]; + } + } + + return PJ_SUCCESS; +} + +/* API: Start stream. */ +static pj_status_t null_stream_start(pjmedia_vid_dev_stream *strm) +{ + struct null_stream *stream = (struct null_stream*)strm; + + PJ_UNUSED_ARG(stream); + + PJ_LOG(4, (THIS_FILE, "Starting null video stream")); + + return PJ_SUCCESS; +} + +/* API: Stop stream. */ +static pj_status_t null_stream_stop(pjmedia_vid_dev_stream *strm) +{ + struct null_stream *stream = (struct null_stream*)strm; + + PJ_UNUSED_ARG(stream); + + PJ_LOG(4, (THIS_FILE, "Stopping null video stream")); + + return PJ_SUCCESS; +} + + +/* API: Destroy stream. */ +static pj_status_t null_stream_destroy(pjmedia_vid_dev_stream *strm) +{ + struct null_stream *stream = (struct null_stream*)strm; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + null_stream_stop(strm); + + pj_pool_release(stream->pool); + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_VIDEO_DEV_HAS_NULL */ diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/videodev.c pjsip/pjmedia/src/pjmedia-videodev/videodev.c --- pjproject-2.10/pjmedia/src/pjmedia-videodev/videodev.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjmedia/src/pjmedia-videodev/videodev.c 2021-02-06 23:01:33.883264555 +0100 @@ -51,6 +51,10 @@ pjmedia_vid_dev_factory* pjmedia_qt_factory(pj_pool_factory *pf); #endif +#if PJMEDIA_VIDEO_DEV_HAS_AVF +pjmedia_vid_dev_factory* pjmedia_avf_factory(pj_pool_factory *pf); +#endif + #if PJMEDIA_VIDEO_DEV_HAS_DARWIN pjmedia_vid_dev_factory* pjmedia_darwin_factory(pj_pool_factory *pf); #endif @@ -59,6 +63,14 @@ pjmedia_vid_dev_factory* pjmedia_opengl_factory(pj_pool_factory *pf); #endif +#if PJMEDIA_VIDEO_DEV_HAS_FB +pjmedia_vid_dev_factory* pjmedia_fb_factory(pj_pool_factory *pf); +#endif + +#if PJMEDIA_VIDEO_DEV_HAS_NULL +pjmedia_vid_dev_factory* pjmedia_null_factory(pj_pool_factory *pf); +#endif + #if PJMEDIA_VIDEO_DEV_HAS_ANDROID pjmedia_vid_dev_factory* pjmedia_and_factory(pj_pool_factory *pf); #endif @@ -98,6 +110,9 @@ #if PJMEDIA_VIDEO_DEV_HAS_QT vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_qt_factory; #endif +#if PJMEDIA_VIDEO_DEV_HAS_AVF + vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_avf_factory; +#endif #if PJMEDIA_VIDEO_DEV_HAS_OPENGL vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_opengl_factory; #endif @@ -122,6 +137,12 @@ */ vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_cbar_factory; #endif +#if PJMEDIA_VIDEO_DEV_HAS_FB + vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_fb_factory; +#endif +#if PJMEDIA_VIDEO_DEV_HAS_NULL + vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_null_factory; +#endif /* Initialize each factory and build the device ID list */ for (i=0; idrv_cnt; ++i) { diff -ruN pjproject-2.10/pjnath/include/pjnath/ice_strans.h pjsip/pjnath/include/pjnath/ice_strans.h --- pjproject-2.10/pjnath/include/pjnath/ice_strans.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjnath/include/pjnath/ice_strans.h 2021-02-06 17:37:27.238934521 +0100 @@ -144,6 +144,52 @@ } pj_ice_strans_op; + +/** + * ICE stream transport's state. + */ +typedef enum pj_ice_strans_state +{ + /** + * ICE stream transport is not created. + */ + PJ_ICE_STRANS_STATE_NULL, + + /** + * ICE candidate gathering process is in progress. + */ + PJ_ICE_STRANS_STATE_INIT, + + /** + * ICE stream transport initialization/candidate gathering process is + * complete, ICE session may be created on this stream transport. + */ + PJ_ICE_STRANS_STATE_READY, + + /** + * New session has been created and the session is ready. + */ + PJ_ICE_STRANS_STATE_SESS_READY, + + /** + * ICE negotiation is in progress. + */ + PJ_ICE_STRANS_STATE_NEGO, + + /** + * ICE negotiation has completed successfully and media is ready + * to be used. + */ + PJ_ICE_STRANS_STATE_RUNNING, + + /** + * ICE negotiation has completed with failure. + */ + PJ_ICE_STRANS_STATE_FAILED + +} pj_ice_strans_state; + + /** * This structure contains callbacks that will be called by the * ICE stream transport. @@ -192,6 +238,18 @@ pj_ice_strans_op op, pj_status_t status); + /** + * Callback to report ICE state changes. + * + * @param ice_st The ICE stream transport. + * @param prev Previous state. + * @param curr Current state. + */ + void (*on_ice_state)(pj_ice_strans *ice_st, + pj_ice_strans_state prev, + pj_ice_strans_state curr); + + } pj_ice_strans_cb; @@ -522,51 +580,6 @@ } pj_ice_strans_cfg; -/** - * ICE stream transport's state. - */ -typedef enum pj_ice_strans_state -{ - /** - * ICE stream transport is not created. - */ - PJ_ICE_STRANS_STATE_NULL, - - /** - * ICE candidate gathering process is in progress. - */ - PJ_ICE_STRANS_STATE_INIT, - - /** - * ICE stream transport initialization/candidate gathering process is - * complete, ICE session may be created on this stream transport. - */ - PJ_ICE_STRANS_STATE_READY, - - /** - * New session has been created and the session is ready. - */ - PJ_ICE_STRANS_STATE_SESS_READY, - - /** - * ICE negotiation is in progress. - */ - PJ_ICE_STRANS_STATE_NEGO, - - /** - * ICE negotiation has completed successfully and media is ready - * to be used. - */ - PJ_ICE_STRANS_STATE_RUNNING, - - /** - * ICE negotiation has completed with failure. - */ - PJ_ICE_STRANS_STATE_FAILED - -} pj_ice_strans_state; - - /** * Initialize ICE transport configuration with default values. * @@ -921,6 +934,27 @@ unsigned comp_id); /** + * Retrieve the ICE session associated with this transport + * + * @param ice_st The ICE stream transport. + * + * @return The ICE session associated with this transport + */ +PJ_DECL(pj_ice_sess*) +pj_ice_strans_get_session(const pj_ice_strans *ice_st); + +/** + * Retrieve the ICE start time + * + * @param ice_st The ICE stream transport. + * + * @return The ICE start time + */ +PJ_DECL(pj_time_val) +pj_ice_strans_get_start_time(const pj_ice_strans *ice_st); + + +/** * Stop and destroy the ICE session inside this media transport. Application * needs to call this function once the media session is over (the call has * been disconnected). diff -ruN pjproject-2.10/pjnath/src/pjnath/ice_strans.c pjsip/pjnath/src/pjnath/ice_strans.c --- pjproject-2.10/pjnath/src/pjnath/ice_strans.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjnath/src/pjnath/ice_strans.c 2021-02-06 17:27:03.341194358 +0100 @@ -240,6 +240,19 @@ } sock_user_data; +/* Set ICE state*/ +static void set_ice_state(pj_ice_strans *ice_st, pj_ice_strans_state state) +{ + pj_ice_strans_state prev = ice_st->state; + + if (prev != state) { + ice_st->state = state; + if (ice_st->cb.on_ice_state) + (*ice_st->cb.on_ice_state)(ice_st, prev, state); + } +} + + /* Validate configuration */ static pj_status_t pj_ice_strans_cfg_check_valid(const pj_ice_strans_cfg *cfg) { @@ -928,7 +941,7 @@ pj_pool_calloc(pool, comp_cnt, sizeof(pj_ice_strans_comp*)); /* Move state to candidate gathering */ - ice_st->state = PJ_ICE_STRANS_STATE_INIT; + set_ice_state(ice_st, PJ_ICE_STRANS_STATE_INIT); /* Acquire initialization mutex to prevent callback to be * called before we finish initialization. @@ -1123,7 +1136,7 @@ * candidate for a component. */ ice_st->cb_called = PJ_TRUE; - ice_st->state = PJ_ICE_STRANS_STATE_READY; + set_ice_state(ice_st, PJ_ICE_STRANS_STATE_READY); if (ice_st->cb.on_ice_complete) (*ice_st->cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_INIT, status); @@ -1289,7 +1302,7 @@ } /* ICE session is ready for negotiation */ - ice_st->state = PJ_ICE_STRANS_STATE_SESS_READY; + set_ice_state(ice_st, PJ_ICE_STRANS_STATE_SESS_READY); return PJ_SUCCESS; @@ -1518,7 +1531,7 @@ return status; } - ice_st->state = PJ_ICE_STRANS_STATE_NEGO; + set_ice_state(ice_st, PJ_ICE_STRANS_STATE_NEGO); return status; } @@ -1539,6 +1552,25 @@ } /* + * Get ICE session. + */ +PJ_DEF(pj_ice_sess*) +pj_ice_strans_get_session(const pj_ice_strans *ice_st) +{ + return ice_st->ice; +} + +/* + * Get ICE start time. + */ +PJ_DEF(pj_time_val) +pj_ice_strans_get_start_time(const pj_ice_strans *ice_st) +{ + + return ice_st->start_time; +} + +/* * Stop ICE! */ PJ_DEF(pj_status_t) pj_ice_strans_stop_ice(pj_ice_strans *ice_st) @@ -1556,7 +1588,7 @@ ice_st->ice = NULL; } - ice_st->state = PJ_ICE_STRANS_STATE_INIT; + set_ice_state(ice_st, PJ_ICE_STRANS_STATE_INIT); pj_grp_lock_release(ice_st->grp_lock); @@ -1877,8 +1909,8 @@ } } - ice_st->state = (status==PJ_SUCCESS) ? PJ_ICE_STRANS_STATE_RUNNING : - PJ_ICE_STRANS_STATE_FAILED; + set_ice_state(ice_st, (status==PJ_SUCCESS) ? PJ_ICE_STRANS_STATE_RUNNING : + PJ_ICE_STRANS_STATE_FAILED); pj_log_push_indent(); (*cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_NEGOTIATION, status); diff -ruN pjproject-2.10/pjsip/include/pjsip/sip_msg.h pjsip/pjsip/include/pjsip/sip_msg.h --- pjproject-2.10/pjsip/include/pjsip/sip_msg.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjsip/include/pjsip/sip_msg.h 2021-02-06 19:48:20.570302003 +0100 @@ -675,6 +675,23 @@ }; /** + * General purpose function to print a SIP message body. + * Uses the appropriate internal functions to print the string representation + * of a SIP message body. It sets the output buffer to a statically allocated + * buffer, so the caller is responsible to copy it. + * + * @param msg_body The message body. + * @param buf Pointer to get the result buffer (statically allocated). + * @param size The size of the buffer. + * + * @return The length copied to the buffer, or -1. + */ +PJ_DECL(int) pjsip_print_body( pjsip_msg_body *msg_body, + char **buf, int *len); + + + +/** * General purpose function to textual data in a SIP body. Attach this function * in a SIP message body only if the data in pjsip_msg_body is a textual * message ready to be embedded in a SIP message. If the data in the message @@ -894,6 +911,20 @@ PJ_DECL(void*) pjsip_msg_find_remove_hdr( pjsip_msg *msg, pjsip_hdr_e hdr, void *start); +/** + * Find and remove a header in the message. + * + * @param msg The message. + * @param name The header name to find. + * @param start The first header field where the search should begin, + * or NULL to search from the first header in the message. + * + * @return The header field, or NULL if not found. + */ +PJ_DECL(void*) pjsip_msg_find_remove_hdr_by_name( pjsip_msg *msg, + pj_str_t *name, + void *start); + /** * Add a header to the message, putting it last in the header list. * diff -ruN pjproject-2.10/pjsip/include/pjsip-simple/evsub.h pjsip/pjsip/include/pjsip-simple/evsub.h --- pjproject-2.10/pjsip/include/pjsip-simple/evsub.h 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjsip/include/pjsip-simple/evsub.h 2021-02-06 19:49:09.127691820 +0100 @@ -524,6 +524,18 @@ pj_uint32_t seconds); +/* Update evbsub internal refresh_time with the given interval */ +PJ_DECL(void) pjsip_evsub_update_expires( pjsip_evsub *sub, + pj_uint32_t interval ); + + +/* Set the specified timer (UAC or UAS) to the specified time */ +PJ_DECL(void) pjsip_evsub_set_timer( pjsip_evsub *sub, + int timer_id, + pj_int32_t seconds ); + + + PJ_END_DECL /** diff -ruN pjproject-2.10/pjsip/src/pjsip/sip_msg.c pjsip/pjsip/src/pjsip/sip_msg.c --- pjproject-2.10/pjsip/src/pjsip/sip_msg.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjsip/src/pjsip/sip_msg.c 2021-02-06 19:39:21.510782607 +0100 @@ -394,6 +394,18 @@ return hdr; } +PJ_DEF(void*) pjsip_msg_find_remove_hdr_by_name( pjsip_msg *msg, + pj_str_t *name, + void *start) +{ + pjsip_hdr *hdr = (pjsip_hdr*) pjsip_msg_find_hdr_by_name(msg, name, start); + if (hdr) { + pj_list_erase(hdr); + } + return hdr; +} + + PJ_DEF(pj_ssize_t) pjsip_msg_print( const pjsip_msg *msg, char *buf, pj_size_t size) { @@ -2146,6 +2158,21 @@ /* * Message body manipulations. */ + +PJ_DEF(int) pjsip_print_body(pjsip_msg_body *msg_body, char **buf, int *len) +{ + static char s_buf[PJSIP_MAX_PKT_LEN]; + int res; + + res = (*msg_body->print_body)(msg_body, s_buf, PJSIP_MAX_PKT_LEN); + if (res < 0) { + return -1; + } + *buf = s_buf; + *len = res; + return 0; +} + PJ_DEF(int) pjsip_print_text_body(pjsip_msg_body *msg_body, char *buf, pj_size_t size) { if (size < msg_body->len) diff -ruN pjproject-2.10/pjsip/src/pjsip-simple/evsub.c pjsip/pjsip/src/pjsip-simple/evsub.c --- pjproject-2.10/pjsip/src/pjsip-simple/evsub.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjsip/src/pjsip-simple/evsub.c 2021-02-06 19:40:40.321065814 +0100 @@ -495,6 +495,12 @@ } +PJ_DEF(void) pjsip_evsub_update_expires( pjsip_evsub *sub, pj_uint32_t interval ) +{ + update_expires(sub, interval); +} + + /* * Schedule timer. */ @@ -538,6 +544,13 @@ } +PJ_DEF(void) pjsip_evsub_set_timer( pjsip_evsub *sub, int timer_id, + pj_int32_t seconds) +{ + set_timer(sub, timer_id, seconds); +} + + /* * Destructor. */ diff -ruN pjproject-2.10/pjsip/src/pjsip-simple/evsub_msg.c pjsip/pjsip/src/pjsip-simple/evsub_msg.c --- pjproject-2.10/pjsip/src/pjsip-simple/evsub_msg.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjsip/src/pjsip-simple/evsub_msg.c 2021-02-06 19:41:44.190911650 +0100 @@ -293,6 +293,45 @@ } /* + * Parse Allow-Events header. + */ +static pjsip_hdr* parse_hdr_allow_events(pjsip_parse_ctx *ctx) +{ + pjsip_allow_events_hdr *allow_events = + pjsip_allow_events_hdr_create(ctx->pool); + const pjsip_parser_const_t *pc = pjsip_parser_const(); + pj_scanner *scanner = ctx->scanner; + + /* Some header fields allow empty elements in the value: + * Accept, Allow, Supported + */ + if (pj_scan_is_eof(scanner) || + *scanner->curptr == '\r' || *scanner->curptr == '\n') + { + goto end; + } + + pj_scan_get( scanner, &pc->pjsip_NOT_COMMA_OR_NEWLINE, + &allow_events->values[0]); + allow_events->count++; + + while (*scanner->curptr == ',') { + pj_scan_get_char(scanner); + pj_scan_get( scanner, &pc->pjsip_NOT_COMMA_OR_NEWLINE, + &allow_events->values[allow_events->count]); + allow_events->count++; + + if (allow_events->count >= PJSIP_MAX_ALLOW_EVENTS) + break; + } + +end: + pjsip_parse_end_hdr_imp(scanner); + return (pjsip_hdr*)allow_events; +} + + +/* * Register header parsers. */ PJ_DEF(void) pjsip_evsub_init_parser(void) diff -ruN pjproject-2.10/pjsip/src/pjsip-ua/sip_inv.c pjsip/pjsip/src/pjsip-ua/sip_inv.c --- pjproject-2.10/pjsip/src/pjsip-ua/sip_inv.c 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/pjsip/src/pjsip-ua/sip_inv.c 2021-02-06 19:46:19.602835344 +0100 @@ -2006,6 +2006,20 @@ return PJMEDIA_SDP_EINSDP; } + /* Only accept SDP in INVITE, UPDATE and ACK requests, 18x (reliable) and 183 provisional responses + * and 200 final response. + */ + if (!(msg->type == PJSIP_REQUEST_MSG && msg->line.req.method.id == PJSIP_INVITE_METHOD) && + !(msg->type == PJSIP_REQUEST_MSG && msg->line.req.method.id == PJSIP_ACK_METHOD) && + !(msg->type == PJSIP_REQUEST_MSG && pjsip_method_cmp(&msg->line.req.method, &pjsip_update_method)==0) && + !(msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code/10==18 && pjsip_100rel_is_reliable(rdata)) && + !(msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code == 183) && + !(msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code == 200)) { + PJ_LOG(4,(inv->obj_name, "ignored SDP body")); + return PJ_SUCCESS; + } + + /* Get/attach invite session's transaction data */ tsx_inv_data = (struct tsx_inv_data*) tsx->mod_data[mod_inv.mod.id]; if (tsx_inv_data == NULL) { @@ -2233,6 +2247,11 @@ { status = pjmedia_sdp_neg_set_local_answer(inv->pool_prov, inv->neg, local_sdp); + } else if (pjmedia_sdp_neg_get_state(inv->neg)== + PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) + { + /* Go forward with our local offer */ + status = PJ_SUCCESS; } else { /* Can not specify local SDP at this state. */ @@ -3976,8 +3995,9 @@ if (inv->state != PJSIP_INV_STATE_DISCONNECTED && ((tsx->status_code == PJSIP_SC_CALL_TSX_DOES_NOT_EXIST && tsx->method.id != PJSIP_CANCEL_METHOD) || - tsx->status_code == PJSIP_SC_REQUEST_TIMEOUT || - tsx->status_code == PJSIP_SC_TSX_TIMEOUT)) + (inv->state != PJSIP_INV_STATE_CONFIRMED && + (tsx->status_code == PJSIP_SC_TSX_TIMEOUT || + tsx->status_code == PJSIP_SC_TSX_TRANSPORT_ERROR)))) { pjsip_tx_data *bye; pj_status_t status; diff -ruN pjproject-2.10/third_party/build/os-auto.mak.in pjsip/third_party/build/os-auto.mak.in --- pjproject-2.10/third_party/build/os-auto.mak.in 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/third_party/build/os-auto.mak.in 2021-02-06 16:09:49.029693848 +0100 @@ -73,6 +73,8 @@ endif endif +DIRS += zsrtp + ifneq (@ac_no_webrtc@,1) ifeq (@ac_external_webrtc@,1) # External webrtc diff -ruN pjproject-2.10/build.mak.in pjsip/build.mak.in --- pjproject-2.10/build.mak.in 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/build.mak.in 2021-02-06 16:09:49.029693848 +0100 @@ -148,6 +148,19 @@ endif endif +#ifneq (@ac_no_zsrtp@,1) +ifeq (@ac_external_zsrtp@,1) +APP_THIRD_PARTY_EXT += -lzsrtp +else +APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libzsrtp-$(LIB_SUFFIX) +ifeq ($(PJ_SHARED_LIBRARIES),) +APP_THIRD_PARTY_LIBS += -lzsrtp-$(TARGET_NAME) -lsqlite3 -lstdc++ +else +APP_THIRD_PARTY_LIBS += -lzsrtp +APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libzsrtp.$(SHLIB_SUFFIX).$(PJ_VERSION_MAJOR) $(PJ_DIR)/third_party/lib/libzsrtp.$(SHLIB_SUFFIX) +endif +endif +#endif # Additional flags @ac_build_mak_vars@ diff -ruN pjproject-2.10/third_party/build/os-darwinos.mak pjsip/third_party/build/os-darwinos.mak --- pjproject-2.10/third_party/build/os-darwinos.mak 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/third_party/build/os-darwinos.mak 2021-02-06 16:00:34.131596843 +0100 @@ -5,5 +5,6 @@ DIRS += g7221 DIRS += srtp DIRS += resample +DIRS += zsrtp diff -ruN pjproject-2.10/third_party/build/os-linux.mak pjsip/third_party/build/os-linux.mak --- pjproject-2.10/third_party/build/os-linux.mak 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/third_party/build/os-linux.mak 2021-02-06 15:59:58.326708529 +0100 @@ -5,5 +5,6 @@ DIRS += g7221 DIRS += srtp DIRS += resample +DIRS += zsrtp diff -ruN pjproject-2.10/third_party/build/os-win32.mak pjsip/third_party/build/os-win32.mak --- pjproject-2.10/third_party/build/os-win32.mak 2020-02-14 10:48:27.000000000 +0100 +++ pjsip/third_party/build/os-win32.mak 2021-02-06 16:00:20.023246385 +0100 @@ -5,5 +5,6 @@ DIRS += g7221 DIRS += srtp DIRS += resample +DIRS += zsrtp --- pjproject-2.10/pjmedia/src/pjmedia-audiodev/audiodev.c 2021-02-04 11:19:25.357096871 +0100 +++ pjsip/pjmedia/src/pjmedia-audiodev/audiodev.c 2021-02-04 11:09:10.120174258 +0100 @@ -143,6 +143,18 @@ aud_subsys->drv[aud_subsys->drv_cnt++].create = &pjmedia_null_audio_factory; #endif + /* Initialize audio device observer objects */ + pj_status_t st; + aud_subsys->dev_observer.pool = pj_pool_create(pf, "aud_dev_observer_pool", 512, 512, NULL); + if (!aud_subsys->dev_observer.pool) { + return PJ_ENOMEM; + } + st = pj_mutex_create_simple(aud_subsys->dev_observer.pool, "aud_dev_observer_lock", &aud_subsys->dev_observer.lock); + if (st != PJ_SUCCESS) { + return st; + } + aud_subsys->dev_observer.cb = NULL; + /* Initialize each factory and build the device ID list */ for (i=0; idrv_cnt; ++i) { status = pjmedia_aud_driver_init(i, PJ_FALSE); @@ -229,6 +241,9 @@ pjmedia_aud_driver_deinit(i); } + pj_mutex_destroy(aud_subsys->dev_observer.lock); + pj_pool_release(aud_subsys->dev_observer.pool); + aud_subsys->pf = NULL; } return PJ_SUCCESS; --- pjproject-2.10/pjmedia/src/pjmedia/audiodev.c 2021-02-04 11:19:25.357096871 +0100 +++ pjsip/pjmedia/src/pjmedia/audiodev.c 2021-02-04 11:09:10.120174258 +0100 @@ -74,6 +74,60 @@ return &aud_subsys; } +/* callback for device change operations */ +static void process_aud_dev_change_event(pjmedia_aud_dev_change_event event) +{ + pj_status_t status; + + if (!pj_thread_is_registered()) { + status = pj_thread_register("aud_dev_observer", aud_subsys.dev_observer.thread_desc, &aud_subsys.dev_observer.thread); + if (status != PJ_SUCCESS) { + return; + } + PJ_LOG(5, (THIS_FILE, "Audio device change thread registered")); + } + + status = pj_mutex_lock(aud_subsys.dev_observer.lock); + if (status != PJ_SUCCESS) { + PJ_LOG(5, (THIS_FILE, "Could not acquire audio device change lock")); + return; + } + + if (!aud_subsys.dev_observer.cb) { + /* there is no registered callback to call */ + goto end; + } + + switch(event) { + case DEFAULT_INPUT_CHANGED: + PJ_LOG(5, (THIS_FILE, "Default input device changed")); + pjmedia_aud_dev_refresh(); + (*aud_subsys.dev_observer.cb)(PJMEDIA_AUD_DEV_DEFAULT_INPUT_CHANGED); + break; + case DEFAULT_OUTPUT_CHANGED: + PJ_LOG(5, (THIS_FILE, "Default output device changed")); + pjmedia_aud_dev_refresh(); + (*aud_subsys.dev_observer.cb)(PJMEDIA_AUD_DEV_DEFAULT_OUTPUT_CHANGED); + break; + case DEVICE_LIST_CHANGED: + PJ_LOG(5, (THIS_FILE, "Device list changed")); + (*aud_subsys.dev_observer.cb)(PJMEDIA_AUD_DEV_LIST_WILL_REFRESH); + pjmedia_aud_dev_refresh(); + (*aud_subsys.dev_observer.cb)(PJMEDIA_AUD_DEV_LIST_DID_REFRESH); + break; + default: + PJ_LOG(5, (THIS_FILE, "Unknown event: %d", event)); + break; + } + +end: + status = pj_mutex_unlock(aud_subsys.dev_observer.lock); + if (status != PJ_SUCCESS) { + PJ_LOG(5, (THIS_FILE, "Could not release audio device change lock")); + } + +} + /* API: init driver */ PJ_DEF(pj_status_t) pjmedia_aud_driver_init(unsigned drv_idx, pj_bool_t refresh) @@ -99,6 +152,11 @@ f = drv->f; } + /* TODO Does device change observer still need to be registered? */ + //if (!refresh) { + // f->op->set_dev_change_cb(f, &process_aud_dev_change_event); + //} + if (!f) return PJ_EUNKNOWN; @@ -123,8 +181,8 @@ */ /* Fill in default devices */ - drv->play_dev_idx = drv->rec_dev_idx = - drv->dev_idx = PJMEDIA_AUD_INVALID_DEV; + drv->rec_dev_idx = f->op->get_default_rec_dev(f); + drv->play_dev_idx = f->op->get_default_play_dev(f); for (i=0; irec_dev_idx = i; } - if (drv->dev_idx < 0 && info.input_count && - info.output_count) - { - /* Set default capture and playback device */ - drv->dev_idx = i; - } - if (drv->play_dev_idx >= 0 && drv->rec_dev_idx >= 0 && - drv->dev_idx >= 0) + if (drv->play_dev_idx >= 0 && drv->rec_dev_idx >= 0) { /* Done. */ break; @@ -183,13 +248,13 @@ pjmedia_aud_driver *drv = &aud_subsys.drv[drv_idx]; if (drv->f) { + drv->f->op->set_dev_change_cb(drv->f, NULL); drv->f->op->destroy(drv->f); drv->f = NULL; } pj_bzero(drv, sizeof(*drv)); - drv->play_dev_idx = drv->rec_dev_idx = - drv->dev_idx = PJMEDIA_AUD_INVALID_DEV; + drv->play_dev_idx = drv->rec_dev_idx = PJMEDIA_AUD_INVALID_DEV; } /* API: get capability name/info */ @@ -374,11 +426,7 @@ for (i=0; idev_idx >= 0) { - id = drv->dev_idx; - make_global_index(i, &id); - break; - } else if (id==PJMEDIA_AUD_DEFAULT_CAPTURE_DEV && + if (id==PJMEDIA_AUD_DEFAULT_CAPTURE_DEV && drv->rec_dev_idx >= 0) { id = drv->rec_dev_idx; @@ -390,7 +455,7 @@ id = drv->play_dev_idx; make_global_index(i, &id); break; - } + } } if (id < 0) { @@ -625,4 +590,24 @@ return strm->op->destroy(strm); } +/* API: Register device change observer. */ +PJ_DEF(pj_status_t) pjmedia_aud_dev_set_observer_cb(pjmedia_aud_dev_observer_callback cb) +{ + pj_status_t status; + + status = pj_mutex_lock(aud_subsys.dev_observer.lock); + if (status != PJ_SUCCESS) { + PJ_LOG(5, (THIS_FILE, "Could not acquire audio device change lock")); + return status; + } + + aud_subsys.dev_observer.cb = cb; + + status = pj_mutex_unlock(aud_subsys.dev_observer.lock); + if (status != PJ_SUCCESS) { + PJ_LOG(5, (THIS_FILE, "Could not release audio device change lock")); + } + + return status; +} --- pjsip/pjmedia/src/pjmedia/transport_zrtp.c 2021-02-05 12:38:15.913793992 +0100 +++ pjsip/pjmedia/src/pjmedia/transport_zrtp.c 2021-03-11 09:20:35.134754145 +0100 @@ -78,6 +78,8 @@ pjmedia_dir dir, unsigned pct_lost); static pj_status_t transport_destroy(pjmedia_transport *tp); +static pj_status_t transport_attach2(pjmedia_transport *tp, + pjmedia_transport_attach_param *att_param); /* The transport operations */ @@ -94,7 +96,8 @@ &transport_media_start, &transport_media_stop, &transport_simulate_lost, - &transport_destroy + &transport_destroy, + &transport_attach2 }; /* The transport zrtp instance */ @@ -108,6 +111,7 @@ void (*stream_rtp_cb)(void *user_data, void *pkt, pj_ssize_t); + void (*stream_rtp_cb2)(pjmedia_tp_cb_param *param); void (*stream_rtcp_cb)(void *user_data, void *pkt, pj_ssize_t); @@ -794,7 +798,7 @@ int32_t newLen = 0; pj_status_t rc = PJ_SUCCESS; - pj_assert(zrtp && zrtp->stream_rtcp_cb && pkt); + pj_assert(zrtp && zrtp->stream_rtp_cb && pkt); // check if this could be a real RTP/SRTP packet. if ((*buffer & 0xf0) != 0x10) @@ -876,6 +880,106 @@ } } +/* This is our RTP callback, that is called by the slave transport when it + * receives RTP packet. + */ +static void transport_rtp_cb2(pjmedia_tp_cb_param *param) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)param->user_data; + pj_uint8_t* buffer = (pj_uint8_t*)param->pkt; + int32_t newLen = 0; + pj_status_t rc = PJ_SUCCESS; + pjmedia_tp_cb_param cbparam; + + pj_assert(zrtp && zrtp->stream_rtp_cb2 && param->pkt); + + // check if this could be a real RTP/SRTP packet. + if ((*buffer & 0xf0) != 0x10) + { + // Could be real RTP, check if we are in secure mode + if (zrtp->srtpReceive == NULL || param->size < 0) + { + pj_memcpy(&cbparam, param, sizeof(cbparam)); + cbparam.user_data = zrtp->stream_user_data; + zrtp->stream_rtp_cb2(&cbparam); + } + else + { + rc = zsrtp_unprotect(zrtp->srtpReceive, param->pkt, param->size, &newLen); + if (rc == 1) + { + zrtp->unprotect++; + param->size = newLen; + pj_memcpy(&cbparam, param, sizeof(cbparam)); + cbparam.user_data = zrtp->stream_user_data; + zrtp->stream_rtp_cb2(&cbparam); + //zrtp->stream_rtp_cb(zrtp->stream_user_data, pkt, + // newLen); + zrtp->unprotect_err = 0; + } + else + { + if (zrtp->cb.show_message) + { + if (rc == -1) + zrtp->cb.show_message(&zrtp->base, zrtp_Warning, zrtp_WarningSRTPauthError); + else + zrtp->cb.show_message(&zrtp->base, zrtp_Warning, zrtp_WarningSRTPreplayError); + } + zrtp->unprotect_err = rc; + /* We failed to decrypt the packet, but forward it regardless to the slave + * transport, it might not have been encrypted after all */ + pj_memcpy(&cbparam, param, sizeof(cbparam)); + cbparam.user_data = zrtp->stream_user_data; + zrtp->stream_rtp_cb2(&cbparam); + } + } + if (!zrtp->started && zrtp->enableZrtp) + pjmedia_transport_zrtp_startZrtp((pjmedia_transport *)zrtp); + + return; + } + + // We assume all other packets are ZRTP packets here. Process + // if ZRTP processing is enabled. Because valid RTP packets are + // already handled we delete any packets here after processing. + if (zrtp->enableZrtp && zrtp->zrtpCtx != NULL) + { + // Get CRC value into crc (see above how to compute the offset) + pj_uint16_t temp = param->size - CRC_SIZE; + pj_uint32_t crc = *(uint32_t*)(buffer + temp); + crc = pj_ntohl(crc); + + if (!zrtp_CheckCksum(buffer, temp, crc)) + { + if (zrtp->cb.show_message) + zrtp->cb.show_message(&zrtp->base, zrtp_Warning, zrtp_WarningCRCmismatch); + return; + } + + pj_uint32_t magic = *(pj_uint32_t*)(buffer + 4); + magic = pj_ntohl(magic); + + // Check if it is really a ZRTP packet, return, no further processing + if (magic != ZRTP_MAGIC) + return; + + // cover the case if the other party sends _only_ ZRTP packets at the + // beginning of a session. Start ZRTP in this case as well. + if (!zrtp->started) + { + pjmedia_transport_zrtp_startZrtp((pjmedia_transport *)zrtp); + } + // this now points beyond the undefined and length field. + // We need them, thus adjust + unsigned char* zrtpMsg = (buffer + 12); + + // store peer's SSRC in host order, used when creating the CryptoContext + zrtp->peerSSRC = *(pj_uint32_t*)(buffer + 8); + zrtp->peerSSRC = pj_ntohl(zrtp->peerSSRC); + zrtp_processZrtpMessage(zrtp->zrtpCtx, zrtpMsg, zrtp->peerSSRC, param->size); + } +} /* This is our RTCP callback, that is called by the slave transport when it * receives RTCP packet. @@ -1256,6 +1359,45 @@ return PJ_SUCCESS; } +/* + * attach2() is called by stream to register callbacks that we should + * call on receipt of RTP and RTCP packets. + */ +static pj_status_t transport_attach2(pjmedia_transport *tp, + pjmedia_transport_attach_param *att_param) +{ + struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; + pj_status_t status; + + /* In this example, we will save the stream information and callbacks + * to our structure, and we will register different RTP/RTCP callbacks + * instead. + */ + pj_assert(zrtp->stream_user_data == NULL); + zrtp->stream_user_data = att_param->user_data; + if (att_param->rtp_cb2) { + zrtp->stream_rtp_cb2 = att_param->rtp_cb2; + } else { + zrtp->stream_rtp_cb = att_param->rtp_cb; + } + zrtp->stream_rtcp_cb = att_param->rtcp_cb; + + att_param->rtp_cb2 = &transport_rtp_cb2; + att_param->rtp_cb = NULL; + att_param->rtcp_cb = &transport_rtcp_cb; + att_param->user_data = zrtp; + + status = pjmedia_transport_attach2(zrtp->slave_tp, att_param); + if (status != PJ_SUCCESS) { + zrtp->stream_user_data = NULL; + zrtp->stream_rtp_cb = NULL; + zrtp->stream_rtp_cb2 = NULL; + zrtp->stream_rtcp_cb = NULL; + return status; + } + + return PJ_SUCCESS; +} +--- pjsip/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c 2021-03-19 17:02:04.749861806 +0100 ++++ pjsip/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c 2021-03-19 17:03:03.618376942 +0100 +@@ -58,9 +58,9 @@ + + #define MAX_DEV_CNT 8 + +-#ifndef PJMEDIA_USE_OLD_FFMPEG ++//#ifndef PJMEDIA_USE_OLD_FFMPEG + # define av_close_input_stream(ctx) avformat_close_input(&ctx) +-#endif ++//#endif + + + typedef struct ffmpeg_dev_info --- pjsip/pjmedia/build/os-darwinos.mak 2021-03-22 07:33:02.058097009 +0100 +++ pjsip/pjmedia/build/os-darwinos.mak 2021-03-22 07:48:59.147298176 +0100 @@ -139,4 +139,5 @@ export CFLAGS += -DPJMEDIA_SOUND_IMPLEMENTATION=PJMEDIA_SOUND_NULL_SOUND endif +export PJMEDIA_VIDEODEV_OBJS += avf_dev.o --- pjsip/pjmedia/build/os-auto.mak.in 2021-03-26 22:08:01.739146366 +0100 +++ pjsip/pjmedia/build/os-auto.mak.in 2021-03-26 22:07:16.701862482 +0100 @@ -302,7 +302,8 @@ # ifneq (,$(filter $(AC_PJMEDIA_VIDEO),darwin_os)) # Mac and iPhone OS specific, use obj-c -export PJMEDIA_VIDEODEV_OBJS += sdl_dev_m.o +export PJMEDIA_VIDEODEV_OBJS += sdl_dev_m.o avf_dev.o +export CFLAGS += -DPJMEDIA_VIDEO_DEV_HAS_AVF=1 -DPJMEDIA_VIDEO_HAS_VTOOLBOX=1 -DPJMEDIA_HAS_VID_TOOLBOX_CODEC=1 -DPJMEDIA_USE_OLD_FFMPEG=0 else # Other platforms, compile .c export PJMEDIA_VIDEODEV_OBJS += sdl_dev.o --- pjsip/pjsip/src/pjsip/sip_transport_tls.c +++ pjsip/pjsip/src/pjsip/sip_transport_tls.c @@ -88,6 +88,7 @@ struct tls_transport pjsip_transport base; pj_bool_t is_server; pj_str_t remote_name; + pj_str_t tls_name; pj_bool_t is_registered; pj_bool_t is_closing; @@ -162,6 +163,7 @@ static pj_status_t tls_create(struct tls_listener *listener, const pj_sockaddr *local, const pj_sockaddr *remote, const pj_str_t *remote_name, + const pj_str_t *tls_name, pj_grp_lock_t *glock, struct tls_transport **p_tls); @@ -796,6 +798,7 @@ static pj_status_t tls_create( struct tls_listener *listener, const pj_sockaddr *local, const pj_sockaddr *remote, const pj_str_t *remote_name, + const pj_str_t *tls_name, pj_grp_lock_t *glock, struct tls_transport **p_tls) { @@ -839,6 +842,9 @@ static pj_status_t tls_create( struct tls_listener *listener, if (remote_name) pj_strdup(pool, &tls->remote_name, remote_name); + if (tls_name) + pj_strdup(pool, &tls->tls_name, tls_name); + tls->base.key.type = listener->factory.type; pj_sockaddr_cp(&tls->base.key.rem_addr, remote); tls->base.type_name = (char*)pjsip_transport_get_type_name( @@ -1142,7 +1148,11 @@ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, pj_ssl_sock_param ssock_param; pj_sockaddr local_addr; pj_str_t remote_name; + pj_str_t tls_name; pj_status_t status; + pjsip_param *p = NULL; + pjsip_sip_uri *sip_uri = NULL; + pjsip_route_hdr *route_hdr = NULL; /* Sanity checks */ PJ_ASSERT_RETURN(factory && mgr && endpt && rem_addr && @@ -1162,8 +1172,27 @@ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); /* Get remote host name from tdata */ - if (tdata) - remote_name = tdata->dest_info.name; + if (tdata) { + remote_name = tdata->dest_info.name; + /* Get tls_name from sip route if defined */ + route_hdr = (pjsip_route_hdr*)pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL); + if (route_hdr != NULL) { + if (PJSIP_URI_SCHEME_IS_SIPS(route_hdr->name_addr.uri) || + PJSIP_URI_SCHEME_IS_SIP(route_hdr->name_addr.uri)) { + sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(route_hdr->name_addr.uri); + p = sip_uri->other_param.next; + while (p != &sip_uri->other_param) { + const pj_str_t st_name = {"tls_name", 8}; + if (pj_stricmp(&p->name, &st_name) == 0) { + pj_strdup(pool, &tls_name, &p->value); + //tls_name.ptr[tls_name.slen] = '\x00'; + break; + } + p = p->next; + } + } + } + } else pj_bzero(&remote_name, sizeof(remote_name)); @@ -1237,7 +1266,7 @@ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, /* Create the transport descriptor */ status = tls_create(listener, pool, ssock, PJ_FALSE, &local_addr, - rem_addr, &remote_name, glock, &tls); + rem_addr, &remote_name, &tls_name, glock, &tls); if (status != PJ_SUCCESS) return status; @@ -1398,7 +1427,7 @@ static pj_bool_t on_accept_complete2(pj_ssl_sock_t *ssock, */ status = tls_create( listener, NULL, new_ssock, PJ_TRUE, &ssl_info.local_addr, &tmp_src_addr, NULL, - ssl_info.grp_lock, &tls); + NULL, ssl_info.grp_lock, &tls); if (status != PJ_SUCCESS) { if (listener->tls_setting.on_accept_fail_cb) { @@ -1821,8 +1850,11 @@ static pj_bool_t on_connect_complete(pj_ssl_sock_t *ssock, pj_bool_t matched = PJ_FALSE; unsigned i; - /* Remote name may be hostname or IP address */ - if (tls->remote_name.slen) + /* Remote name may be hostname or IP address + * but might as well be defined in tls_name */ + if (tls->tls_name.slen) + remote_name = &tls->tls_name; + else if (tls->remote_name.slen) remote_name = &tls->remote_name; else remote_name = &tls->base.remote_name.host; --- pjsip/aconfigure 2021-04-17 03:33:50.992738038 +0200 +++ pjsip/aconfigure 2021-04-17 03:35:46.528046741 +0200 @@ -8072,7 +8072,7 @@ if test "x$ac_ssl_backend" = "xdarwin"; then $as_echo "#define PJ_HAS_SSL_SOCK 1" >>confdefs.h - $as_echo "#define PJ_SSL_SOCK_IMP PJ_SSL_SOCK_IMP_DARWIN" >>confdefs.h + $as_echo "#define PJ_SSL_SOCK_IMP PJ_SSL_SOCK_IMP_OPENSSL" >>confdefs.h LIBS="$LIBS -framework Security" { $as_echo "$as_me:${as_lineno-$LINENO}: result: Checking if Darwin SSL is available... yes" >&5 --- pjsip/pjsip/src/pjsip/sip_transport_tls.c +++ pjsip/pjsip/src/pjsip/sip_transport_tls.c @@ -1206,7 +1206,10 @@ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, ssock_param.async_cnt = 1; ssock_param.ioqueue = pjsip_endpt_get_ioqueue(listener->endpt); ssock_param.timer_heap = pjsip_endpt_get_timer_heap(listener->endpt); - ssock_param.server_name = remote_name; + if (tls_name.slen) + ssock_param.server_name = tls_name; + else + ssock_param.server_name = remote_name; ssock_param.timeout = listener->tls_setting.timeout; ssock_param.user_data = NULL; /* pending, must be set later */ ssock_param.verify_peer = PJ_FALSE; /* avoid SSL socket closing the socket --- pjsip/pjlib/src/pj/ssl_sock_ossl.c +++ pjsip/pjlib/src/pj/ssl_sock_ossl.c @@ -1702,6 +1702,9 @@ ossl_sock_t *ossock = (ossl_sock_t *)ssock; X509 *x; STACK_OF(X509) *chain; + pj_parsed_time v; + pj_time_val end; + pj_bool_t gmt; pj_assert(ssock->ssl_state == SSL_STATE_ESTABLISHED); @@ -1709,6 +1712,12 @@ x = SSL_get_certificate(ossock->ossl_ssl); if (x) { get_cert_info(ssock->pool, &ssock->local_cert_info, x, PJ_FALSE); + parse_ossl_asn1_time(&end, &gmt, X509_get_notAfter(x)); + + pj_time_decode(&end, &v); + + PJ_LOG(1, (THIS_FILE, "[local TLS certificate] subject:%s | issuer:%s | valid until:%d-%d-%d", ssock->local_cert_info.subject.info.ptr, ssock->local_cert_info.issuer.info.ptr, v.year, v.mon, v.day)); + /* Don't free local's X509! */ } else { pj_bzero(&ssock->local_cert_info, sizeof(pj_ssl_cert_info)); @@ -1718,6 +1727,12 @@ x = SSL_get_peer_certificate(ossock->ossl_ssl); if (x) { get_cert_info(ssock->pool, &ssock->remote_cert_info, x, PJ_TRUE); + parse_ossl_asn1_time(&end, &gmt, X509_get_notAfter(x)); + + pj_time_decode(&end, &v); + + PJ_LOG(1, (THIS_FILE, "[remote TLS certificate] subject:%s | issuer:%s | valid until:%d-%d-%d | host:%s:%d", ssock->remote_cert_info.subject.info.ptr, ssock->remote_cert_info.issuer.info.ptr, v.year, v.mon, v.day, pj_inet_ntoa(ssock->rem_addr.ipv4.sin_addr), ssock->rem_addr.ipv4.sin_port)); + /* Free peer's X509 */ X509_free(x); } else { @@ -1766,6 +1770,7 @@ static void ssl_set_peer_name(pj_ssl_sock_t *ssock) /* Set server name to connect */ if (ssock->param.server_name.slen) { /* Server name is null terminated already */ + PJ_LOG(1,(ssock->pool->obj_name, "[SSL_set_tlsext_host_name] server_name:%s", ssock->param.server_name.ptr)); if (!SSL_set_tlsext_host_name(ossock->ossl_ssl, ssock->param.server_name.ptr)) { diff --git a/deps/patches/m1/readme.txt b/deps/patches/m1/readme.txt index 1ba32208..6b41503b 100644 --- a/deps/patches/m1/readme.txt +++ b/deps/patches/m1/readme.txt @@ -1,88 +1,86 @@ -Compiling on M1 processor -------------------------- +Compiling on Apple Apple Silicon processor +------------------------------------------ 1. aconfigure needs to be modified (the one from pjsip 2.11 worked) for webrtc echo canceller to build, without the Intel sse2 instructions. 2. FFMPEG related patches 001 and 005 don't work anymore. This is an overview of the changes made by the large patch 001_pjsip_210.patch cp -r ../pjsip/third_party/zsrtp third_party/ cp -r ../pjsip/third_party/build/zsrtp third_party/build/ cp ../pjsip/aconfigure aconfigure cp ../pjsip/aconfigure.ac aconfigure.ac cp ../pjsip/base_rev base_rev cp ../pjsip/pjsip/src/pjsip/sip_transport.c pjsip/src/pjsip/ cp ../pjsip/pjsip/src/pjsip/sip_transport_tls.c pjsip/src/pjsip/ cp ../pjsip/pjlib/src/pj/os_core_unix.c pjlib/src/pj/os_core_unix.c cp ../pjsip/pjsip/include/pjsip/sip_msg.h pjsip/include/pjsip/sip_msg.h cp ../pjsip/pjsip/include/pjsip-simple/evsub.h pjsip/include/pjsip-simple/evsub.h cp ../pjsip/pjsip/src/pjsip/sip_msg.c pjsip/src/pjsip/sip_msg.c cp ../pjsip/pjsip/src/pjsip-simple/evsub.c pjsip/src/pjsip-simple/evsub.c cp ../pjsip/pjsip/src/pjsip-simple/evsub_msg.c pjsip/src/pjsip-simple/evsub_msg.c cp ../pjsip/pjsip/src/pjsip-ua/sip_inv.c pjsip/src/pjsip-ua/sip_inv.c cp ../pjsip/pjnath/include/pjnath/ice_strans.h pjnath/include/pjnath/ice_strans.h cp ../pjsip/pjnath/src/pjnath/ice_strans.c pjnath/src/pjnath/ice_strans.c cp ../pjsip/third_party/build/os-darwinos.mak third_party/build/os-darwinos.mak cp ../pjsip/third_party/build/os-linux.mak third_party/build/os-linux.mak cp ../pjsip/third_party/build/os-win32.mak third_party/build/os-win32.mak cp ../pjsip/pjlib/src/pj/ssl_sock_ossl.c pjlib/src/pj/ssl_sock_ossl.c cp ~/config_site.h pjlib/include/pj/config_site.h cp ~/config_auto.h pjmedia/include/pjmedia/config_auto.h cp ../pjsip/pjmedia/build/Makefile pjmedia/build/Makefile cp ../pjsip/pjmedia/include/pjmedia/audiodev.h pjmedia/include/pjmedia/audiodev.h cp ../pjsip/pjmedia/include/pjmedia/event.h pjmedia/include/pjmedia/event.h cp ../pjsip/pjmedia/include/pjmedia/format.h pjmedia/include/pjmedia/format.h cp ../pjsip/pjmedia/include/pjmedia/mixer_port.h pjmedia/include/pjmedia/mixer_port.h cp ../pjsip/pjmedia/include/pjmedia/rtcp.h pjmedia/include/pjmedia/rtcp.h cp ../pjsip/pjmedia/include/pjmedia/signatures.h pjmedia/include/pjmedia/signatures.h cp ../pjsip/pjmedia/include/pjmedia/sound_port.h pjmedia/include/pjmedia/sound_port.h cp ../pjsip/pjmedia/include/pjmedia/transport_ice.h pjmedia/include/pjmedia/transport_ice.h cp ../pjsip/pjmedia/include/pjmedia/transport_zrtp.h pjmedia/include/pjmedia/transport_zrtp.h cp ../pjsip/pjmedia/include/pjmedia/vid_stream.h pjmedia/include/pjmedia/vid_stream.h cp ../pjsip/pjmedia/include/pjmedia-audiodev/audiodev_imp.h pjmedia/include/pjmedia-audiodev/audiodev_imp.h cp ../pjsip/pjmedia/include/pjmedia.h pjmedia/include/pjmedia.h cp ../pjsip/pjmedia/include/pjmedia-videodev/fb_dev.h pjmedia/include/pjmedia-videodev/fb_dev.h cp ../pjsip/pjmedia/include/pjmedia_videodev.h pjmedia/include/pjmedia_videodev.h cp ../pjsip/pjmedia/src/pjmedia-audiodev/audiodev.c pjmedia/src/pjmedia-audiodev/audiodev.c cp ../pjsip/pjmedia/src/pjmedia-audiodev/alsa_dev.c pjmedia/src/pjmedia-audiodev/alsa_dev.c cp ../pjsip/pjmedia/src/pjmedia-audiodev/coreaudio_dev.m pjmedia/src/pjmedia-audiodev/coreaudio_dev.m cp ../pjsip/pjmedia/src/pjmedia-audiodev/wmme_dev.c pjmedia/src/pjmedia-audiodev/wmme_dev.c cp ../pjsip/pjmedia/src/pjmedia/audiodev.c pjmedia/src/pjmedia/audiodev.c cp ../pjsip/pjmedia/src/pjmedia/converter.c pjmedia/src/pjmedia/converter.c cp ../pjsip/pjmedia/src/pjmedia/echo_common.c pjmedia/src/pjmedia/echo_common.c cp ../pjsip/pjmedia/src/pjmedia/echo_webrtc_aec.c pjmedia/src/pjmedia/echo_webrtc_aec.c cp ../pjsip/pjmedia/src/pjmedia/endpoint.c pjmedia/src/pjmedia/endpoint.c cp ../pjsip/pjmedia/src/pjmedia/format.c pjmedia/src/pjmedia/format.c cp ../pjsip/pjmedia/src/pjmedia/mixer_port.c pjmedia/src/pjmedia/mixer_port.c cp ../pjsip/pjmedia/src/pjmedia/rtcp.c pjmedia/src/pjmedia/rtcp.c cp ../pjsip/pjmedia/src/pjmedia/sound_port.c pjmedia/src/pjmedia/sound_port.c cp ../pjsip/pjmedia/src/pjmedia/transport_ice.c pjmedia/src/pjmedia/transport_ice.c cp ../pjsip/pjmedia/src/pjmedia/transport_zrtp.c pjmedia/src/pjmedia/transport_zrtp.c cp ../pjsip/pjmedia/src/pjmedia/vid_stream.c pjmedia/src/pjmedia/vid_stream.c cp ../pjsip/pjmedia/src/pjmedia/vid_tee.c pjmedia/src/pjmedia/vid_tee.c cp ../pjsip/pjmedia/src/pjmedia-codec/opus.c pjmedia/src/pjmedia-codec/opus.c cp ../pjsip/pjmedia/src/pjmedia-codec/vpx.c pjmedia/src/pjmedia-codec/vpx.c cp ../pjsip/pjmedia/src/pjmedia-videodev/avf_dev.m pjmedia/src/pjmedia-videodev/avf_dev.m cp ../pjsip/pjmedia/src/pjmedia-videodev/dshow_dev.c pjmedia/src/pjmedia-videodev/dshow_dev.c cp ../pjsip/pjmedia/src/pjmedia-videodev/dshow_filter.cpp pjmedia/src/pjmedia-videodev/dshow_filter.cpp cp ../pjsip/pjmedia/src/pjmedia-videodev/fb_dev.c pjmedia/src/pjmedia-videodev/fb_dev.c cp ../pjsip/pjmedia/src/pjmedia-videodev/null_dev.c pjmedia/src/pjmedia-videodev/null_dev.c cp ../pjsip/pjmedia/src/pjmedia-videodev/videodev.c pjmedia/src/pjmedia-videodev/videodev.c # bad stuff removed from new patch in M1 folder #cp ../pjsip/pjmedia/src/pjmedia/ffmpeg_util.h pjmedia/src/pjmedia/ffmpeg_util.h #cp ../pjsip/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c #cp ../pjsip/pjmedia/src/pjmedia/ffmpeg_util.c pjmedia/src/pjmedia/ffmpeg_util.c #cp ../pjsip/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c pjmedia/src/pjmedia-videodev/ffmpeg_dev.c With the new 001 patch, and removing 005 patch, pjsip 2.10 was built manually on M1: export CFLAGS="-arch arm64 -mmacosx-version-min=10.11 -I/opt/local/include" export LDFLAGS="-arch arm64 -mmacosx-version-min=10.11 -L/opt/local/lib -headerpad_max_install_names" ./configure --host=arm-apple-darwin --disable-openh264 --disable-l16-codec \ --disable-g7221-codec --disable-sdl --disable-ilbc-codec --disable-speex-codec \ --disable-gsm-codec --disable-speex-aec make - - diff --git a/get_dependencies.sh b/get_dependencies-210.sh similarity index 96% copy from get_dependencies.sh copy to get_dependencies-210.sh index 75b6cc2f..e989d03f 100755 --- a/get_dependencies.sh +++ b/get_dependencies-210.sh @@ -1,83 +1,80 @@ #!/bin/bash - unamestr=$(uname) if [[ "$unamestr" == 'Linux' ]]; then sudo apt install python3 dh-python python3-all-dev cython3 libasound2-dev \ python3-dateutil python3-dnspython libssl-dev libv4l-dev libavcodec-dev \ libavformat-dev libopencore-amrnb-dev libopencore-amrwb-dev libavutil-dev \ libswscale-dev libx264-dev libvpx-dev libopus-dev libsqlite3-dev pkg-config \ uuid-dev cython3 cython3-dbg python3-setuptools devscripts debhelper dh-python \ python3-all-dev python3-all-dbg libasound2-dev libssl-dev libsqlite3-dev fi cd deps # # Update PJSIP # -echo "Preparing PJSIP sources..." +echo "Preparing PJSIP 2.10 sources..." if [ ! -f 2.10.tar.gz ]; then echo Downloading PJSIP 2.10... wget https://github.com/pjsip/pjproject/archive/2.10.tar.gz if [ $? -eq 0 ]; then echo "PJSIP downloaded" else echo Fail to download PJSIP exit 1 fi fi tar xzf 2.10.tar.gz if [ -d pjsip ]; then rm -r pjsip fi mkdir pjsip mv pjproject*/* ./pjsip/ # # Update ZSRTP # # Clone latest version from github if [ ! -d ZRTPCPP ]; then echo Downloading ZRTP... git clone https://github.com/wernerd/ZRTPCPP.git if [ $? -eq 0 ]; then echo "ZRTP downloaded" cd ZRTPCPP git checkout 6b3cd8e6783642292bad0c21e3e5e5ce45ff3e03 cd .. else echo Fail to download ZRTP exit 1 fi fi # Copy wrapper from old version to third_party/zsrtp/ echo "Preparing ZRTP sources..." mkdir ./pjsip/third_party/zsrtp cp -r zsrtp/include ./pjsip/third_party/zsrtp/ cp -r zsrtp/srtp ./pjsip/third_party/zsrtp/ cp -r zsrtp/build ./pjsip/third_party/build/zsrtp # Copy new version to third_party/zsrtp/ mkdir ./pjsip/third_party/zsrtp/zrtp cp -r ZRTPCPP/bnlib ./pjsip/third_party/zsrtp/zrtp/ cp -r ZRTPCPP/common ./pjsip/third_party/zsrtp/zrtp/ cp -r ZRTPCPP/cryptcommon ./pjsip/third_party/zsrtp/zrtp/ cp -r ZRTPCPP/srtp ./pjsip/third_party/zsrtp/zrtp/ cp -r ZRTPCPP/zrtp ./pjsip/third_party/zsrtp/zrtp/ cp ZRTPCPP/COPYING ./pjsip/third_party/zsrtp/zrtp/ cp ZRTPCPP/README.md ./pjsip/third_party/zsrtp/zrtp/ - -for p in patches/0*.patch; do +for p in patches/2.10/0*.patch; do echo "Applying patch $p" patch -p0 < $p > /dev/null done cd - > /dev/null - diff --git a/get_dependencies.sh b/get_dependencies-211.sh old mode 100755 new mode 100644 similarity index 89% rename from get_dependencies.sh rename to get_dependencies-211.sh index 75b6cc2f..e2c8fff8 --- a/get_dependencies.sh +++ b/get_dependencies-211.sh @@ -1,83 +1,80 @@ #!/bin/bash - unamestr=$(uname) if [[ "$unamestr" == 'Linux' ]]; then sudo apt install python3 dh-python python3-all-dev cython3 libasound2-dev \ python3-dateutil python3-dnspython libssl-dev libv4l-dev libavcodec-dev \ libavformat-dev libopencore-amrnb-dev libopencore-amrwb-dev libavutil-dev \ libswscale-dev libx264-dev libvpx-dev libopus-dev libsqlite3-dev pkg-config \ uuid-dev cython3 cython3-dbg python3-setuptools devscripts debhelper dh-python \ python3-all-dev python3-all-dbg libasound2-dev libssl-dev libsqlite3-dev fi cd deps # # Update PJSIP # -echo "Preparing PJSIP sources..." -if [ ! -f 2.10.tar.gz ]; then - echo Downloading PJSIP 2.10... - wget https://github.com/pjsip/pjproject/archive/2.10.tar.gz +echo "Preparing PJSIP 2.11 sources..." +if [ ! -f 2.11.tar.gz ]; then + echo Downloading PJSIP 2.11... + wget https://github.com/pjsip/pjproject/archive/2.11.tar.gz if [ $? -eq 0 ]; then echo "PJSIP downloaded" else echo Fail to download PJSIP exit 1 fi fi -tar xzf 2.10.tar.gz +tar xzf 2.11.tar.gz if [ -d pjsip ]; then rm -r pjsip fi mkdir pjsip mv pjproject*/* ./pjsip/ # # Update ZSRTP # # Clone latest version from github if [ ! -d ZRTPCPP ]; then echo Downloading ZRTP... git clone https://github.com/wernerd/ZRTPCPP.git if [ $? -eq 0 ]; then echo "ZRTP downloaded" cd ZRTPCPP git checkout 6b3cd8e6783642292bad0c21e3e5e5ce45ff3e03 cd .. else echo Fail to download ZRTP exit 1 fi fi # Copy wrapper from old version to third_party/zsrtp/ echo "Preparing ZRTP sources..." mkdir ./pjsip/third_party/zsrtp cp -r zsrtp/include ./pjsip/third_party/zsrtp/ cp -r zsrtp/srtp ./pjsip/third_party/zsrtp/ cp -r zsrtp/build ./pjsip/third_party/build/zsrtp # Copy new version to third_party/zsrtp/ mkdir ./pjsip/third_party/zsrtp/zrtp cp -r ZRTPCPP/bnlib ./pjsip/third_party/zsrtp/zrtp/ cp -r ZRTPCPP/common ./pjsip/third_party/zsrtp/zrtp/ cp -r ZRTPCPP/cryptcommon ./pjsip/third_party/zsrtp/zrtp/ cp -r ZRTPCPP/srtp ./pjsip/third_party/zsrtp/zrtp/ cp -r ZRTPCPP/zrtp ./pjsip/third_party/zsrtp/zrtp/ cp ZRTPCPP/COPYING ./pjsip/third_party/zsrtp/zrtp/ cp ZRTPCPP/README.md ./pjsip/third_party/zsrtp/zrtp/ - -for p in patches/0*.patch; do +for p in patches/2.11/0*.patch; do echo "Applying patch $p" patch -p0 < $p > /dev/null done cd - > /dev/null - diff --git a/setup_pjsip.py b/setup_pjsip.py index 8ef335f9..3c642e30 100644 --- a/setup_pjsip.py +++ b/setup_pjsip.py @@ -1,272 +1,272 @@ import errno import itertools import os import platform import re import shutil import subprocess import sys if sys.platform.startswith('linux'): sys_platform = 'linux' elif sys.platform.startswith('freebsd'): sys_platform = 'freebsd' else: sys_platform = sys.platform # Hack to set environment variables before importing distutils # modules that will fetch them and set the compiler and linker # to be used. -Saul if sys_platform == "darwin": min_osx_version = "11.3" try: osx_sdk_path = subprocess.check_output(["xcodebuild", "-version", "-sdk", "macosx", "Path"]).decode().strip() except subprocess.CalledProcessError as e: raise RuntimeError("Could not locate SDK path: %s" % str(e)) # OpenSSL (installed with Port) ossl_cflags = "-I/opt/local/include" ossl_ldflags = "-L/opt/local/lib" # SQLite (installed with Port) sqlite_cflags = "-I/opt/local/include" sqlite_ldflags = "-L/opt/local/lib" # Opus flags (installed with Port) opus_cflags = "-I/opt/local/include" opus_ldflags = "-L/opt/local/lib" # VPX (installed with Port) vpx_cflags = "-I/opt/local/include" vpx_ldflags = "-L/opt/local/lib" # Prepare final flags - arch_flags = "-arch x86_64 -mmacosx-version-min=%s" % min_osx_version + arch_flags = "-mmacosx-version-min=%s" % min_osx_version local_cflags = " %s %s %s %s %s -mmacosx-version-min=%s -isysroot %s" % (arch_flags, ossl_cflags, sqlite_cflags, opus_cflags, vpx_cflags, min_osx_version, osx_sdk_path) local_ldflags = " %s %s %s %s %s -headerpad_max_install_names -isysroot %s" % (arch_flags, ossl_ldflags, sqlite_ldflags, opus_ldflags, vpx_ldflags, osx_sdk_path) os.environ['CFLAGS'] = os.environ.get('CFLAGS', '') + local_cflags os.environ['LDFLAGS'] = os.environ.get('LDFLAGS', '') + local_ldflags os.environ['ARCHFLAGS'] = arch_flags os.environ['MACOSX_DEPLOYMENT_TARGET'] = min_osx_version if sys_platform == "win32": offmpeg_cflags = "-I/mingw64/include" offmepg_ldflags = "-L/mingw64/lib/" local_cflags = " %s" % (offmpeg_cflags) local_ldflags = " %s" % (offmepg_ldflags) os.environ['CFLAGS'] = os.environ.get('CFLAGS', '') + local_cflags os.environ['LDFLAGS'] = os.environ.get('LDFLAGS', '') + local_ldflags from distutils import log from distutils.dir_util import copy_tree from distutils.errors import DistutilsError from Cython.Distutils import build_ext class PJSIP_build_ext(build_ext): config_site = ["#define PJ_SCANNER_USE_BITWISE 0", "#define PJSIP_SAFE_MODULE 0", "#define PJSIP_MAX_PKT_LEN 262144", "#define PJSIP_UNESCAPE_IN_PLACE 1", "#define PJMEDIA_AUDIO_DEV_HAS_COREAUDIO %d" % (1 if sys_platform=="darwin" else 0), "#define PJMEDIA_AUDIO_DEV_HAS_ALSA %d" % (1 if sys_platform=="linux" else 0), "#define PJMEDIA_AUDIO_DEV_HAS_WMME %d" % (1 if sys_platform=="win32" else 0), "#define PJMEDIA_HAS_SPEEX_AEC 0", "#define PJMEDIA_SRTP_HAS_AES_CM_256 1", "#define PJMEDIA_HAS_SPEEX_CODEC 0", "#define PJMEDIA_HAS_GSM_CODEC 0", "#define PJMEDIA_HAS_ILBC_CODEC 0", "#define PJMEDIA_HAS_OPENCORE_AMRNB_CODEC 0", "#define PJMEDIA_HAS_OPENCORE_AMRWB_CODEC 0", "#define PJMEDIA_HAS_WEBRTC_AEC 1", "#define PJMEDIA_RTP_PT_TELEPHONE_EVENTS 101", "#define PJMEDIA_RTP_PT_TELEPHONE_EVENTS_STR \"101\"", "#define PJMEDIA_STREAM_ENABLE_KA PJMEDIA_STREAM_KA_EMPTY_RTP", "#define PJMEDIA_STREAM_VAD_SUSPEND_MSEC 0", "#define PJMEDIA_CODEC_MAX_SILENCE_PERIOD -1", "#define PJ_ICE_MAX_CHECKS 256", "#define PJ_LOG_MAX_LEVEL 6", "#define PJ_IOQUEUE_MAX_HANDLES 1024", "#define PJ_DNS_RESOLVER_MAX_TTL 0", "#define PJ_DNS_RESOLVER_INVALID_TTL 0", "#define PJSIP_TRANSPORT_IDLE_TIME 7200", "#define PJ_ENABLE_EXTRA_CHECK 1", "#define PJSIP_DONT_SWITCH_TO_TCP 1", "#define PJMEDIA_VIDEO_DEV_HAS_SDL 0", "#define PJMEDIA_VIDEO_DEV_HAS_AVI 0", "#define PJMEDIA_VIDEO_DEV_HAS_FB 1", "#define PJMEDIA_VIDEO_DEV_HAS_V4L2 %d" % (1 if sys_platform=="linux" else 0), "#define PJMEDIA_VIDEO_DEV_HAS_AVF %d" % (1 if sys_platform=="darwin" else 0), "#define PJMEDIA_VIDEO_DEV_HAS_DSHOW %d" % (1 if sys_platform=="win32" else 0), "#define PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC 1", "#define PJMEDIA_VIDEO_DEV_HAS_NULL 1"] user_options = build_ext.user_options user_options.extend([ ("clean", None, "Clean PJSIP tree before compilation"), ("verbose", None, "Print output of PJSIP compilation process") ]) boolean_options = build_ext.boolean_options boolean_options.extend(["clean", "verbose"]) @staticmethod def distutils_exec_process(cmdline, silent=True, input=None, **kwargs): """Execute a subprocess and returns the returncode, stdout buffer and stderr buffer. Optionally prints stdout and stderr while running.""" try: sub = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) stdout, stderr = sub.communicate(input=input) returncode = sub.returncode if not silent: sys.stdout.write(stdout.decode()) sys.stderr.write(stderr.decode()) except OSError as e: if e.errno == errno.ENOENT: raise RuntimeError('"%s" is not present on this system' % cmdline[0]) else: raise if returncode != 0: raise RuntimeError('Got return value %d while executing "%s", stderr output was:\n%s' % (returncode, " ".join(cmdline), stderr.decode())) return stdout.decode() @staticmethod def get_make_cmd(): if sys_platform == "freebsd": return "gmake" else: return "make" @staticmethod def get_opts_from_string(line, prefix): """Returns all options that have a particular prefix on a commandline""" chunks = [chunk.strip() for chunk in line.split()] return [chunk[len(prefix):] for chunk in chunks if chunk.startswith(prefix)] @classmethod def get_makefile_variables(cls, makefile, silent=True): """Returns all variables in a makefile as a dict""" stdout = cls.distutils_exec_process([cls.get_make_cmd(), "-f", makefile, "-pR", makefile], silent=silent) return dict(tup for tup in re.findall("(^[a-zA-Z]\w+)\s*:?=\s*(.*)$", stdout, re.MULTILINE)) @classmethod def makedirs(cls, path): try: os.makedirs(path) except OSError as e: if e.errno==errno.EEXIST and os.path.isdir(path) and os.access(path, os.R_OK | os.W_OK | os.X_OK): return raise def initialize_options(self): build_ext.initialize_options(self) self.clean = 0 self.verbose = 0 self.pjsip_dir = os.path.join(os.path.dirname(__file__), "deps", "pjsip") def configure_pjsip(self, silent=True): path = os.path.join(self.build_dir, "pjlib", "include", "pj", "config_site.h") log.info("Configuring PJSIP in %s" % path) with open(path, "w") as f: s = "\n".join(self.config_site+[""]) f.write(s) cflags = "-DNDEBUG -g -fPIC -fno-omit-frame-pointer -fno-strict-aliasing -Wno-unused-label" if self.debug or hasattr(sys, 'gettotalrefcount'): log.info("PJSIP will be built without optimizations") cflags += " -O0" else: cflags += " -O2" env = os.environ.copy() env['CFLAGS'] = ' '.join(x for x in (cflags, env.get('CFLAGS', None)) if x) if sys_platform == "win32": cmd = ["bash", "configure"] else: cmd = ["./configure"] cmd.extend(["--disable-openh264", "--disable-l16-codec", "--disable-g7221-codec", "--disable-sdl"]) cmd.extend(["--disable-ilbc-codec", "--disable-speex-codec", "--disable-gsm-codec", "--disable-speex-aec"]) if sys_platform == "win32": cmd.extend(["--enable-video=yes"]) ffmpeg_path = env.get("SIPSIMPLE_FFMPEG_PATH", None) if ffmpeg_path is not None: cmd.append("--with-ffmpeg=%s" % os.path.abspath(os.path.expanduser(ffmpeg_path))) libvpx_path = env.get("SIPSIMPLE_LIBVPX_PATH", None) if libvpx_path is not None: cmd.append("--with-vpx=%s" % os.path.abspath(os.path.expanduser(libvpx_path))) amr_nb_path = env.get("SIPSIMPLE_AMR_NB_PATH", None) if amr_nb_path is not None: cmd.append("--with-opencore-amr=%s" % os.path.abspath(os.path.expanduser(amr_nb_path))) amr_wb_path = env.get("SIPSIMPLE_AMR_WB_PATH", None) if amr_wb_path is not None: cmd.append("--with-opencore-amrwbenc=%s" % os.path.abspath(os.path.expanduser(amr_wb_path))) if self.verbose: log.info(" ".join(cmd)) self.distutils_exec_process(cmd, silent=not self.verbose, cwd=self.build_dir, env=env) if "#define PJ_HAS_SSL_SOCK 1\n" not in open(os.path.join(self.build_dir, "pjlib", "include", "pj", "compat", "os_auto.h")).readlines(): os.remove(os.path.join(self.build_dir, "build.mak")) raise DistutilsError("PJSIP TLS support was disabled, OpenSSL development files probably not present on this system") def compile_pjsip(self): log.info("Compiling PJSIP") if self.verbose and sys_platform == "darwin": log.info(os.environ['CFLAGS']) log.info(os.environ['LDFLAGS']) self.distutils_exec_process([self.get_make_cmd()], silent=not self.verbose, cwd=self.build_dir) def clean_pjsip(self): log.info("Cleaning PJSIP") try: shutil.rmtree(self.build_dir) except OSError as e: if e.errno == errno.ENOENT: return raise def update_extension(self, extension, silent=True): build_mak_vars = self.get_makefile_variables(os.path.join(self.build_dir, "build.mak")) extension.include_dirs = self.get_opts_from_string(build_mak_vars["PJ_CFLAGS"], "-I") extension.library_dirs = self.get_opts_from_string(build_mak_vars["PJ_LDFLAGS"], "-L") extension.libraries = self.get_opts_from_string(build_mak_vars["PJ_LDLIBS"], "-l") extension.define_macros = [tuple(define.split("=", 1)) for define in self.get_opts_from_string(build_mak_vars["PJ_CFLAGS"], "-D")] extension.define_macros.append(("PJ_SVN_REVISION", open(os.path.join(self.build_dir, "base_rev"), "r").read().strip())) #extension.define_macros.append(("__PYX_FORCE_INIT_THREADS", 1)) extension.extra_compile_args.append("-Wno-unused-function") # silence warning if sys_platform == "darwin": extension.define_macros.append(("MACOSX_DEPLOYMENT_TARGET", min_osx_version)) frameworks = re.findall("-framework (\S+)(?:\s|$)", build_mak_vars["PJ_LDLIBS"]) extension.extra_link_args = list(itertools.chain(*(("-framework", val) for val in frameworks))) extension.extra_link_args.append("-mmacosx-version-min=%s" % min_osx_version) extension.extra_compile_args.append("-mmacosx-version-min=%s" % min_osx_version) extension.library_dirs.append("%s/usr/lib" % osx_sdk_path) extension.include_dirs.append("%s/usr/include" % osx_sdk_path) extension.depends = build_mak_vars["PJ_LIB_FILES"].split() self.libraries = extension.depends[:] def cython_sources(self, sources, extension, silent=True): log.info("Compiling Cython extension %s" % extension.name) if extension.name == "sipsimple.core._core": self.build_dir = os.path.join(self.build_temp, "pjsip") if self.clean: self.clean_pjsip() copy_tree(self.pjsip_dir, self.build_dir, verbose=0) try: if not os.path.exists(os.path.join(self.build_dir, "build.mak")): self.configure_pjsip(silent=silent) self.update_extension(extension, silent=silent) self.compile_pjsip() except RuntimeError as e: log.info("Error building %s: %s" % (extension.name, str(e))) return None return build_ext.cython_sources(self, sources, extension)